rushidesai1
rushidesai1

Reputation: 183

Grails 2.4.2 bean spring bean injection

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

Answers (2)

Jeff Scott Brown
Jeff Scott Brown

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

Vinay Prajapati
Vinay Prajapati

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:

  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!!

Upvotes: 0

Related Questions