June 29, 2022

Constructor Dependency Injection in Spring

In the post dependency injection in Spring we have already gone through the concept of dependency injection, in this post we’ll see in details one of the type of dependency injection- Constructor dependency injection in Spring.

For another type of dependency injection, Setter dependency injection check this post- Setter Dependency Injection in Spring

Spring Constructor dependency injection

In constructor based DI Spring container invokes constructor of a class with a number of arguments, each representing a dependency. For configuring constructor based dependencies you can use XML configuration as well as annotations. We’ll see examples of doing it using both of these ways.

Spring Constructor dependency injection Example

In the example there is a class to place orders called Order and purchase can be done from an online store or a retail store. In Order class dependencies for the constructor arguments has to be injected.

public interface IStore {
  public void doPurchase(int items);
}
public class OnlineStore implements IStore {
  public void doPurchase(int items) {
    System.out.println("Doing online purchase of " + items + " Items");
  }
}
public class RetailStore implements IStore {
  public void doPurchase(int items) {
    System.out.println("Doing purchase of " + items + " Items from a brick and mortar store");
  }
}
public class Order {
  private IStore store;
  private int items;
  public Order(IStore store, int items) {
    this.store = store;
    this.items = items;
  }
  public void setStore(IStore store) {
    this.store = store;
  }
  public void buyItems() {
    store.doPurchase(items);
  }
}

If you are using XML configuration then beans are defined as given in the following 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"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
          http://www.springframework.org/schema/beans/spring-beans.xsd
          http://www.springframework.org/schema/context
          http://www.springframework.org/schema/context/spring-context.xsd">
  <!-- Store bean --> 
  <bean id="store" class="com.knpcode.SpringProject.OnlineStore" />           
    <!-- Order bean with dependencies -->
    <bean id="order" class="com.knpcode.SpringProject.Order">
    <constructor-arg ref="store" />
    <constructor-arg type="int" value="20" />
  </bean>
</beans>

For providing constructor dependencies <constructor-arg> tag is used. When dependency is for another bean "ref" attribute is used, for any primitive type "value" attribute is used.

You can use the following class with main method to read the configuration and call the bean method.

public class App {
  public static void main( String[] args ){
    // create context using configuration
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("appcontext.xml");
    Order order = (Order) context.getBean("order");
    order.buyItems();
    // close the context
    context.close();
  }
}

If you want to configure constructor dependencies in Spring using annotations then you will have to use @Service or @Component annotation with the classes to signify that these are Spring managed component and will be automatically discovered when component scanning is done.

@Service
public class OnlineStore implements IStore {
  public void doPurchase(int items) {
    System.out.println("Doing online purchase of " + items + " Items");
  }
}
@Service
public class RetailStore implements IStore {
  public void doPurchase(int items) {
    System.out.println("Doing purchase of " + items + " Items from a brick and mortar store");
  }
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Service
public class Order {
  private IStore store;
  @Value("20")
  private int items;
  @Autowired	
  public Order(@Qualifier("retailStore")IStore store) {
    this.store = store;
  }
  public void setStore(IStore store) {
    this.store = store;
  }
  public void buyItems() {
    store.doPurchase(items);
  }
}

@Autowired annotation on the constructor denotes that the dependency has to be wired automatically. Since there are two objects of type store so @Qualifier annotation has been used to tell which bean has to be wired otherwise you will get an error “No qualifying bean of type 'com.knpcode.SpringProject.IStore' available”

Autowiring works with references only so primitive value is provided using @Value annotation.

If you want to use XML to set up component scanning for automatically discovering beans then it can be done using following 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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
          http://www.springframework.org/schema/beans/spring-beans.xsd
          http://www.springframework.org/schema/context
          http://www.springframework.org/schema/context/spring-context.xsd">
	<context:component-scan base-package="com.knpcode.SpringProject" />
</beans>

XML configuration won’t have any dependencies now only <context:component-scan> tag.

Constructor argument resolution

In case of constructor dependency the order in which the constructor arguments are defined in a bean definition is the order in which those arguments are supplied to the appropriate constructor when the bean is being instantiated.

There may be ambiguity in the constructor arguments of a bean definition which may be because of having more than one bean of same type (Classes related by inheritance). We have already see how to resolve such ambiguity using @Qualifier annotation.

In case of primitive types Spring cannot determine the type of the value, and so cannot match by type without help. In such scenario you will have to use either “type” attribute or “index” attribute.

Constructor argument type matching

In type matching explicitly specify the type of the constructor argument using the type attribute. For example:

<bean id="orderBean" class="com.knpcode.SpringProject.Order">
  <constructor-arg type="int" value="20000" />
  <constructor-arg type="java.lang.String" value="20"/>
 </bean>
Constructor argument index

Use the index attribute to explicitly specify the index of constructor arguments. Note that the index is 0 based.

<bean id="orderBean" class="com.knpcode.SpringProject.Order">
  <constructor-arg index="0" value="20000" />
  <constructor-arg index="1" value="20"/>
</bean>

Constructor-based or setter-based Dependency injection

As per Spring documentation constructor based DI is preferred over setter-based DI.

"Since you can mix constructor-based and setter-based DI, it is a good rule of thumb to use constructors for mandatory dependencies and setter methods or configuration methods for optional dependencies.
The Spring team generally advocates constructor injection as it enables one to implement application components as immutable objects and to ensure that required dependencies are not null. Furthermore constructor-injected components are always returned to client (calling) code in a fully initialized state.
Setter injection should primarily only be used for optional dependencies that can be assigned reasonable default values within the class. Otherwise, not-null checks must be performed everywhere the code uses the dependency."

That's all for the topic Constructor Dependency Injection in Spring. If something is missing or you have something to share about the topic please write a comment.


You may also like

No comments:

Post a Comment