Hey Guys,
Today I want to share some Programming-Practices with you, since I had to make a Blog-Post for internal use in our company ^^
Method-level dependency injection with Spring's "@Lookup" Annotation
A method annotated with @Lookup
tells Spring to return an instance of the method's return type when it gets invoked. In this case Spring will override the annotated method and will use the method's return type and parameters as arguments to the call to BeanFactory.getBean()
.
From the official Docs:
An annotation that indicates 'lookup' methods, to be overridden by the container to redirect them back to the BeanFactory for a getBean call.
The resolution of the target bean can either be based on the return type (getBean(Class)) or on a suggested bean name (getBean(String)),
in both cases passing the method's arguments to the getBean call for applying them as target factory method arguments or constructor arguments.
@Lookup
is useful for:
- Injecting a prototype-scoped bean into a singleton bean (similar to Provider) - this Post
- Injecting dependencies procedurally/Method injection - followup Post
Note also that @Lookup is the Java equivalent of the XML element lookup-method
in applicationContext.xml.
In this first Blog-Post I will show you, how to
1. Inject a prototype-scoped bean into a singleton bean
Prototype Bean Injection Problem
In order to describe the problem of injecting a prototype-scoped bean into a singleton, let's look the following beans:
@Configuration
public class AppConfig {
@Bean
@Scope("prototype")
public PrototypeBean prototypeBean() {
return new PrototypeBean();
}
@Bean
public SingletonBean singletonBean() {
return new SingletonBean();
}
}
Notice that the first bean has a prototype
scope, the other one is a singleton
(by default).
Now, let's inject the prototype-scoped bean into the singleton – and then expose if via the getPrototypeBean() method:
public class SingletonBean {
//... member variables, etc.
@Autowired
private PrototypeBean prototypeBean;
public SingletonBean() {
logger.info("Singleton instance created");
}
public PrototypeBean getPrototypeBean() {
logger.info(String.valueOf(LocalTime.now()));
return prototypeBean;
}
//... getters and setters
}
Then, let's load up the ApplicationContext and get the singleton bean twice:
public static void main(String[] args) throws InterruptedException {
AnnotationConfigApplicationContext context
= new AnnotationConfigApplicationContext(AppConfig.class);
SingletonBean firstSingleton = context.getBean(SingletonBean.class);
PrototypeBean firstPrototype = firstSingleton.getPrototypeBean();
// get singleton bean instance one more time
SingletonBean secondSingleton = context.getBean(SingletonBean.class);
PrototypeBean secondPrototype = secondSingleton.getPrototypeBean();
isTrue(firstPrototype.equals(secondPrototype), "The same instance should be returned");
}
Here's the output from the console:
Singleton Bean created
Prototype Bean created
11:06:57.894
// should create another prototype bean instance here
11:06:58.895
Both beans were initialized only once, at the startup of the application context.
Injecting ApplicationContext (bad approach)
There also would be the possibility to inject the ApplicationContext directly into a bean. To achieve this, we could either use the @Autowire annotation or implement the ApplicationContextAware interface. Every time the getPrototypeBean()
method is called, a new instance of PrototypeBean will be returned from the ApplicationContext.
However, I will not dive deeper into this approach, since it has serious disadvantages. It contradicts the principle of inversion of control, as we request the dependencies from the container directly.
Also, we fetch the prototype bean from the applicationContext within the SingletonAppcontextBean class. This means coupling the code to the Spring Framework.
Injecting the bean with @Lookup
If we decide to have a prototype Spring bean, then we faced with the problem of how will our singleton Spring beans access these prototype Spring beans? The most familiar approach is certainly a Provider. But here I will show you how to use the @Lookup annotation, since is more versatile in some respects.
@Component
public class SingletonLookupBean {
@Lookup
public PrototypeBean getPrototypeBean() {
return null; // Stub - Method will be overridden
}
}
The equivalent use of lookup-method
in application.xml would look something like this:
<bean id="prototypeBean" class="net.netconomy.uselookup.PrototypeBean" scope="prototype"/>
<bean id="singletonLookupBean" class="net.netconomy.uselookup.SingletonLookupBean">
<lookup-method name="getPrototypeBean" bean="prototypeBean"/>
</bean>
Spring will override the getPrototypeBean() method annotated with @Lookup. It then registers the bean into the application context. Whenever we request the getPrototypeBean() method, it returns a new PrototypeBean instance.
We can test that with a simple Testcase:
@Test
public void whenLookupMethodCalled_thenNewInstanceReturned() {
// ... initialize context
SingletonLookupBean first = this.context.getBean(SingletonLookupBean.class);
SingletonLookupBean second = this.context.getBean(SingletonLookupBean.class);
assertEquals(first, second);
assertNotEquals(first.getPrototypeBean(), second.getPrototypeBean());
}
Spring will use CGLIB to generate the bytecode responsible for fetching the PrototypeBean from the application context.
Next time I will show you how to Inject dependencies procedurally/use Method injection.
Have a nice day and steem on!
Cheers, @w0olf