Reputation: 183
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 !!!
Upvotes: 2
Views: 1566
Reputation: 27255
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)
}
}
Upvotes: 3
Reputation: 7556
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:
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!!
Upvotes: 0