Spring-Framework: Method-level dependency injection with @Lookup

in #programming5 years ago (edited)

Spring Framework

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:

  1. Injecting a prototype-scoped bean into a singleton bean (similar to Provider) - this Post
  2. 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


ruler

Sort:  
 5 years ago  Reveal Comment