Ted Naleid
Ted Naleid

Reputation: 26801

Ask Spring for a Prototype instance of a Singleton Bean?

I have a number of Grails Services that are singleton Spring beans, not prototypes.

Is there any way to retrieve a prototype instance (fully injected with any dependencies) of what is normally a singleton bean?

I'd like this for testing purposes so that I can mess with the prototype instance (potentially changing it's metaClass to mock things out) without the risk of forgetting to clean up after any changes I make to the instance and having them leak into other tests.

I've been playing around with this a little bit and haven't had any luck. I've tried doing something like this:

def ctx = grailsApplication.mainContext

ctx.registerPrototype("foo", MyService)

I'm then able to ask Spring for an instance of MyService

def myServicePrototype = ctx.getBean("foo")

and it does give a new instance every time, but none of the instances have had their properties autowired up so they're all null. I'm guessing theres some way to create a BeanDefinition and feed it to the BeanFactory with some of the autowire stuff that I must be missing turned on.

I'm hoping to come up with some generic solution that doesn't force me to explicitly annotate the target services in any way.

Is there any way to directly ask the Spring applicationContext for a prototype version of what's normally a singleton?

Upvotes: 1

Views: 1530

Answers (2)

Ted Naleid
Ted Naleid

Reputation: 26801

Figured out how to do it using the Grails BeanBuilder to create a temporary ApplicationContext that has the main app context as a parent. This won't work if any unusual wiring/spring configuration has been done to the service, but that's not normally the case with Grails services, so seems like a good default pattern:

// this works in the grails console where grailsApplication is in the binding, 
// need to inject in other situations
import org.springframework.context.ApplicationContext
import grails.spring.BeanBuilder    
import com.example.service.MyService

def getPrototypeInstanceOf(Class clazz) {
    BeanBuilder beanBuilder = new BeanBuilder(grailsApplication.mainContext)
    String beanName = "prototype${clazz.name}"

    beanBuilder.beans {
        "$beanName"(clazz) { bean ->
            bean.autowire = 'byName'
        }
    }

    ApplicationContext applicationContext = beanBuilder.createApplicationContext()

    return applicationContext.getBean(beanName)
}


def prototypeService = getPrototypeInstanceOf(MyService)
def singletonService = grailsApplication.mainContext.getBean("myService")

assert singletonService != prototypeService
assert singletonService.injectedService == prototypeService.injectedService

Upvotes: 2

Andrey Adamovich
Andrey Adamovich

Reputation: 20663

If it is only for testing purposes and there are no conflicting dependencies (e.g. some beans that must only be singltons), then you can just create your Spring context twice.

EDIT: Another aproach could be that you define a factory interface for your singleton service and provide 2 implementations of that factory interface: 1 for singleton only and second for having multiple instances depending on your testing situation.

Upvotes: 1

Related Questions