2

Sample app located here : https://github.com/rushidesai1/Grails2_4_2_BeanIssue

Question:

In resources.groovy if we declare a bean like this

beans = {
    testObject(TestObject){bean ->
        bean.scope = "prototype"
        map = new HashMap()  // or [:]
        //And also if we declare any object like this
        testA = new TestA()
  }
}

and Now if we DI testObject bean or do 'Holders.grailsApplication.mainContext.getBean("testObject")', then the bean we get will have singleton 'map' and singelton 'testA' object.

Here testObject is declared as 'prototype' and even then both 'map' and 'testA' are singleton

I want to know if this is a bug or it is working as designed. It is completely counter intuitive that it would work like this since we are specifically doing new and so we expect a new bean being injected everytime.

Use the Unit test case to see more detailed version of my question.

Thanks in advance for clarification !!!

rushidesai1
  • 173
  • 1
  • 9

2 Answers2

3

I want to know if this is a bug or it is working as designed.

Yes, I think it is working as designed.

Your testObject bean is a singleton. That singleton bean only has 1 copy of the map and testA properties. The behavior you are describing is exactly what I would expect.

EDIT:

I have reviewed the application in the linked project and this is what is going on...

In resources.groovy you have something like this:

testObject(TestObject) { bean -> bean.scope = "prototype" mapIssue1 = ["key1FromResource.groovy": "value1FromResource.groovy"] }

That testObject bean is a prototype scoped bean so each time you retrieve one, you will get a new instance. However, you have the initialization Map hardcoded in the bean definition so the bean definition that is created has that Map associated with it so every bean created from that bean def will have the same Map. If you want a different Map instance, you could create it in afterPropertiesSet or similar.

The unit test at https://github.com/rushidesai1/Grails2_4_2_BeanIssue/blob/e9b7c9e70da5863f0716b998462eca60924ee717/test/unit/test/SpringBeansSpec.groovy is not very well written. Seeing what is going on relies on interrogating stdout after all of those printlns. The behavior could be more simply verified with something like this:

resources:groovy

import test.TestObject

beans = {
    testObject(TestObject) { bean ->
        bean.scope = "prototype"
        mapIssue1 = ["key1FromResource.groovy":"value1FromResource.groovy"]
    }
}

SpringBeansSpec.groovy

package test

import grails.test.mixin.TestMixin
import grails.test.mixin.support.GrailsUnitTestMixin
import spock.lang.Specification

@TestMixin(GrailsUnitTestMixin)
class SpringBeansSpec extends Specification {
    static loadExternalBeans = true 

    void 'test bean properties'() {
        setup:
        def testObject1 = grailsApplication.mainContext.testObject
        def testObject2 = grailsApplication.mainContext.testObject

        expect: 'test TestObject beans are not the same instance'
        !testObject1.is(testObject2)

        and: 'the TestObject beans share values defined in the bean definition'
        testObject1.mapIssue1.is(testObject2.mapIssue1)
    }
}
Jeff Scott Brown
  • 26,804
  • 2
  • 30
  • 47
  • Even if the bean is declared as "prototype" scope? In the github code, I see the scope as prototype. – Stealth Aug 25 '16 at 13:38
  • I was answering based on the code sample in the question above. I see now there are several things in the github project that are relevant and not mentioned in the question above. This question is poorly formed. He is asking about something that will behave differently than what is described in the question. – Jeff Scott Brown Aug 25 '16 at 14:15
  • Jeff : Sorry for not making clear in the question. I was relying on the Sample application to ask the detailed question. I have edited the question. And as Stealth said, in the app the 'testObject' is declared as prototype and yet we will get singleton back. Thanks for looking into the question !!! – rushidesai1 Aug 25 '16 at 14:25
  • I think part of your confusion may be that the bean definition is only evaluated once, even if the bean is `prototype` scoped. Don't confusing the creation of the bean definition with the creation of the bean. – Jeff Scott Brown Aug 25 '16 at 14:42
  • There is no reason to use `Holders` to get the `GrailsApplication` instance. You marked the test with `@TestMixin(GrailsUnitTestMixin)` so a `GrailsApplication grailsApplication` property is added to the class, and you can simply refer to it, as shown in the test I showed above. – Jeff Scott Brown Aug 25 '16 at 14:47
  • @JeffScottBrown : I agree I could have written unit test more clearly. I understand how it is working now, although I would like to say it is counter intuitive and not documented that doing it like this will have a singleton 'map' or object, we got burnt because of this recently. In spring-xml config when we declare a list using (or any collection tag) we get a new list everytime. If I have to do it in 'afterPropertiesSet' or 'Postconstruct' it defeats purpose of DI. – rushidesai1 Aug 25 '16 at 18:33
  • I think it makes sense given that Bean definition is only evaluated once. Thanks for simplifying the test case. I will go ahead and accept you answer in case someone else encounters the same confusion as I did :) – rushidesai1 Aug 25 '16 at 18:41
  • "In spring-xml config when we declare a list using (or any collection tag) we get a new list everytime." - That is different than what your sample app is doing. Your sample app isn't defining a `List` or `Map` bean. Your sample app is just creating a `Map`. If you wanted a new one created for each `testObject` bean, you can do that by defining a corresponding `Map` bean and having Spring inject that into the `testObject` beans. – Jeff Scott Brown Aug 25 '16 at 19:11
  • The pull request at https://github.com/rushidesai1/Grails2_4_2_BeanIssue/pull/2 may demonstrate how to do what you are describing. – Jeff Scott Brown Aug 25 '16 at 19:15
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/121873/discussion-between-rushidesai1-and-jeff-scott-brown). – rushidesai1 Aug 25 '16 at 23:20
  • @rushidesai1 "Let us continue this discussion in chat" - As far as I know, there isn't more discussion to be had. If there is and you would like my input, please visit http://grails.slack.com or post to our mailing list at https://groups.google.com/forum/#!forum/grails-dev-discuss. – Jeff Scott Brown Aug 26 '16 at 00:49
  • That 'chat' statement was stack-overflow since we had lot of comments !! – rushidesai1 Aug 26 '16 at 01:35
0

On one hand it might be confusing that even if you are using new it should be creating a new Object each time you get testA bean and on the other hand it is working as expected. How?

Alright! So the answer lies in Spring java Configuration. The resources.groovy is using DSL which internally is a Configuration file.

Not sure if you know or remember about springs @Configuration annotation. Using this we are making POJO a configuration file. Now the rules of Spring are:

  1. Any bean created is singleton by default until unless specified.
  2. Even if you are using new in java configuration file. Spring is made wise enough that it is a spring config file and hence new doesn't mean a new Object always.

Hence, for equivalent configuration file if I skip testObject and map for now is below:

@Configuration
public class JavaConfiguration {

    @Bean
    public Teacher teacher(){
        TestA testA =  new TestA();
        return teacher;
    }
}

Here, we have used new TestA(). But spring will always return same object until you specify explicitly to use scope Prototype. Hence, above Configuration file would be like below after enabling prototype scope:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
@Configuration
public class JavaConfiguration {

    @Bean
    @Scope(value="prototype")
    public Teacher teacher(){
        TestA testA =  new TestA();
        return teacher;
    }
}

and corresponding DSL would be:

testA(TestA){bean->
 bean.scope='prototype'
}

Hope it helps!!

Vinay Prajapati
  • 7,199
  • 9
  • 45
  • 86