Spring: Method-level dependency injection with @Lookup - Part 2
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()
.
@Lookup
is useful for:
- Injecting a prototype-scoped bean into a singleton bean (similar to Provider) - last Post
- Injecting dependencies procedurally/Method injection - this Post
Note also that @Lookup
is the Java equivalent of the XML element lookup-method
in applicationContext.xml.
Last time I showed you how to Inject a prototype-scoped bean into a singleton bean.
This time I will show you, how to...
2. Inject dependencies procedurally / use Method injection
Still more powerful, then using @Lookup
for Bean Injection, is that @Lookup
allows us to inject a dependency procedurally, something that we cannot do with Provider.
Spring lookup method injection is the process of dynamically overriding a registered bean method.
This time I will demonstrate the Lookup-Functionality with the applicationContext.xml since many (or most?) of our projects here at NC use that approach.
Let's assume we have the following Bean of PopcornShop:
package net.netconomy.popcornfactory;
public abstract class PopcornShop {
public abstract Popcorn makePopcorn();
public abstract Popcorn makeSweetPopcorn();
}
And we have the Following Bean of Popcorn:
package net.netconomy.popcornfactory;
import java.util.concurrent.atomic.AtomicLong;
public class Popcorn {
private static AtomicLong count = new AtomicLong(0);
private boolean sweet;
public Popcorn(sweet) {
this.sweet = sweet;
count.incrementAndGet();
}
public String toString() {
return "Made a bucket of " + (sweet ? "sweet " : "") + "Popcorn. Buckets made overall:" + count.get();
}
public void setSweet(boolean sweet) {
this.sweet = sweet;
}
}
As you can see our example is very simple. The Class Popcorn has a static count variable which gets incremented as we create a new instance. Also it has a boolean member sweet
which will be true if the Popcorn is sweet rather than salty. (I love sweet popcorn! 🤤)
In this example we will make the lookup-methods abstract rather then stub, like we did in the firts post.
Using abstract is a bit nicer-looking than a stub, but we can only use it when we don't component-scan or @Bean-manage the surrounding bean.
We will configure makePopcorn
and makeSweetPopcorn
as lookup-methods.
Additionally we configure a popcorn
and a sweetPopcorn
bean as prototype scoped beans.
Each abstract method will have one <lookup-method../>
element.
The name attribute will be the method name and the bean will point to the bean configured.
applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="popcornShop" class="net.netconomy.popcornfactory.PopcornShop">
<lookup-method name="makePopcorn" bean="popcorn"/>
<lookup-method name="makeSweetPopcorn" bean="sweetPopcorn"/>
</bean>
<bean id="popcorn" class="net.netconomy.popcornfactory.Popcorn" scope="prototype">
<constructor-arg type="boolean">
<value>false</value>
</constructor-arg>
</bean>
<bean id="sweetPopcorn" class="net.netconomy.popcornfactory.Popcorn" scope="prototype">
<constructor-arg type="boolean">
<value>true</value>
</constructor-arg>
</bean>
</beans>
You can also use the @Lookup
annotation like we did in the first Blogpost.
Our PopcornShop
then would look something like this:
package net.netconomy.popcornfactory;
public abstract class PopcornShop {
@Lookup(value="popcorn")
public abstract Popcorn makePopcorn();
@Lookup(value="sweetPopcorn")
public abstract Popcorn makeSweetPopcorn();
}
And the Configuration could look something like this:
@Configuration
public class AppConfig {
@Bean
@Scope("prototype")
public Popcorn popcorn() {
return new Popcorn(false);
}
@Bean
@Scope("prototype")
public Popcorn sweetPopcorn() {
return new Popcorn(true);
}
@Bean
public PopcornShop popcornShop() {
return new PopcornShop();
}
}
So everything is already set up now and we can test our PopcornShop 🤓
We will first load the context and get the PopcornShop bean.
Next, we will make calls popcornShop.makePopcorn()
and popcornShop.makeSweetPopcorn()
.
package net.netconomy.popcornfactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringPopcornLookupMethodExample {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
try {
PopcornShop popcornShop = (PopcornShop) context.getBean("popcornShop");
Popcorn firstPopcorn = popcornShop.makePopcorn();
System.out.println("- First Popcorn: " + firstPopcorn);
Popcorn secondPopcorn = popcornShop.makePopcorn();
System.out.println("- Second Popcorn: " + secondPopcorn);
Popcorn sweetPopcorn = popcornShop.makeSweetPopcorn();
System.out.println("- Yummy, sweet Popcorn!:" + sweetPopcorn);
} finally {
context.close();
}
}
}
Each time we invoke the Function, it creates a new Popcorn
.
That we can see because the count is getting incremented.
(The output of the Popcorn comes from the toString()
Method defined in the Popcorn class.)
Output:
- First Popcorn: Made a bucket of Popcorn. Buckets made overall:
- Second Popcorn: Made a bucket of Popcorn. Buckets made overall:
- Yummy, sweet Popcorn!: Made a bucket sweet of Popcorn. Buckets made overall:
Conclusion
So with this two Blogposts we learned how and when to use Spring's @Lookup annotation, including how to use it to inject prototype-scoped beans into singleton beans and how to use it to inject dependencies procedurally.
If you want to dig deeper into that Topic, you can have a look at the official Documentation.
Have a nice day and steem on!
Cheers, @w0olf
/ᐠ.ᴗ.ᐟ\