Spring - Lookup Method or Method Injection

Most common injection mechanisms used in Spring are Constructor and Property injections. In both the mechanisms, the injection happens only once during the initialization of the Bean. They also require a concrete method defined e.g. a constructor method for constructor injections and a setter method for property injection.

What if you would like to inject a prototype bean and get a new instance, every time you invoke the getter. In other words, how do you implement a factory method that would return a new bean instance without explicitly implementing the method (to lookup the ApplicationContext)?

For such scenarios, Spring provides a third injection mechanism viz. Lookup-Method. It is also known as Method Injection. In this mechanism, you would usually create an abstract method to lookup the data and spring would inject a concrete implementation of the method on bean initialization.

Let's explore this injection through a few examples.

Example classes


User POJO that holds the user details:
public class User {

  private String name;
  private String type;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getType() {
    return type;
  }

  public void setType(String type) {
    this.type = type;
  }

}


The User service class that provides operations for getting the user data:
public abstract class UserService {

  /**
   * Initialize a new User instance and return it (always a new instance).
   * @return
   */
  public abstract User getInitializedUserInstance();
  
  /**
   * Return the default Operations User (singleton)
   * @return
   */
  public abstract User getOperationsUser();

}


Notice that we have defined an abstract class with two abstract methods for fetching the user data. The Javadoc for the methods explain the intent.

Bean Definitions


Now, let's define two beans for the User:
    
        
    
    
    
        
        
    

initUserBean is defined as a prototype. So, whenever the ApplicationContext is invoked for this bean, it would return a new instance.
opsUserBean is defined as a singleton (no scope means singleton). The ApplicationContext would always return the same instance when invoked.

Let's define the userServicebean in the following manner:
    
        
        
    


When this bean is instantiated, Spring generates a subclass using CGLIB and implements the abstract methods to return the beans as specified in the lookup-method.
Instead of an abstract method, if the service operation was defined as a concrete method, Spring would have overridden the method and still returned the bean value as configured.

Testing the example


Let's test the User Service example with the code below:

  ClassPathXmlApplicationContext appcontext = new ClassPathXmlApplicationContext("beans.xml");
  
  UserService service = appcontext.getBean("userService", UserService.class);
  User newUser1 = service.getInitializedUserInstance();
  User newUser2 = service.getInitializedUserInstance();
  
  Assert.isTrue(newUser1.getType().equals("admin"));
  Assert.isTrue(newUser1 != newUser2);//scope of prototype should return unique instances
  
  User opsUser1 = service.getOperationsUser();
  User opsUser2 = service.getOperationsUser();
  
  Assert.isTrue(opsUser1.getType().equals("operator"));
  Assert.isTrue(opsUser1.getName().equals("John"));
  Assert.isTrue(opsUser1 == opsUser2);//should return the same instance
  
  appcontext.close();

This test would pass all the Asserts. Notice that getInitializedUserInstance would return a new instance everytime it's called and getOperationsUser would return the same instance.

@Lookup


Spring provides the @Lookup annotation for annotation based configuration.

The User and UserService beans from the example can be defined as below, to achieve the same results. Except, two separate bean instances cannot be defined for User; as @Bean is not supported for Lookup methods.

@Component("initUserBean")
@Scope("prototype")
public class User {

  private String name;
  private String type = "admin";

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getType() {
    return type;
  }

  public void setType(String type) {
    this.type = type;
  }

}


@Component("userService")
public abstract class UserService {

  /**
   * Initialize a new User instance and return it (always a new instance).
   * @return
   */
  @Lookup(value="initUserBean")
  public abstract User getInitializedUserInstance();
  
}


Sample Application


In this post, we explored the Lookup Method or Method Injection through a few examples. It may not be as frequently used as the other injection methods, but it definitely provides a good alternative for the AbstractFactory pattern. Check out a working example in this GitHub project.

No comments:

Post a Comment