Tag

dependency injection in Spring

Browsing

Before learning about Spring Dependency Injection we’ll introduce the concepts of IoC (Inversion of Control) and DI (Dependency Injection). These are two important definitions in Spring Framework that you must to know.

Singleton pattern

Singleton pattern is a design pattern that lets you ensure that a class has only one instance, while providing a global access point to this instance.

package com.learncode24h.singleton;
 
public class LearnCode24hSingleton {
 
    private static final LearnCode24hSingleton  INSTANCE = new LearnCode24hSingleton ();
 
    // Private constructor to avoid client applications to use constructor
    private LearnCode24hSingleton () {
         
    }
 
    public static LearnCode24hSingleton getInstance() {
        return INSTANCE;
    }
}

Spring IoC Container

In Java, if you initialize an object with new keyword, JVM will create for you an instance, with the large projects you can create so many objects that you can’t manage theme and lead to memory leak for your web application. Spring resolve this problem by restricting a singleton to one object per Spring IoC container. In practice, this means Spring will only create one bean for each type per application context.

Spring’s approach differs from the strict definition of a singleton since an application can have more than one Spring container. Therefore, multiple objects of the same class can exist in a single application if we have multiple containers.

In Spring We can implement dependency injection with:

  • constructor-based dependency injection
  • setter-based dependency injection
  • field-based dependency injection.

Constructor-Based Dependency Injection

In constructor-based dependency injection, the dependencies required for the class are provided as arguments to the constructor. The container will invoke a constructor with arguments each representing a dependency we want to set.

@Component
class Cake {

  private Flavor flavor;
  private Sugar sugar;

  Cake(Flavor flavor, Sugar sugar) {
    this.flavor = flavor;
    this.sugar = sugar;
  }

  Flavor getFlavor() {
    return flavor;
  }

  Sugar getSugar(){
    return sugar;
  }
}

Before Spring 4.3, we had to add an @Autowired annotation to the constructor. With newer versions, this is optional if the class has only one constructor. When using Autowired you notify to Spring container create and keep and singleton instance (bean) and ready to inject to an another object require it.

In the Cake class above, since we have only one constructor, we don’t have to specify the @Autowired annotation. Consider the below example with two constructors:

@Component
class Cake {

  private Flavor flavor;
  private Sugar sugar;

   @Autowired
   Cake(Flavor flavor) {
    this.flavor = flavor;
  }

  Cake(Flavor flavor, Sugar sugar) {
    this.flavor = flavor;
    this.sugar = sugar;
  }

  Flavor getFlavor() {
    return flavor;
  }

  Sugar getSugar(){
    return sugar;
  }
}

When you have a class with multiple constructors, you must explicitly add the @Autowired annotation to any one of the constructors that you want to notify for Spring knows which constructor to use to inject the dependencies, Spring’ll look for the correct dependency in Spring Container and inject them to the constructor marked with @Autowired annotation .

By using @Component annotation you have created a bean in Spring Container. And then this bean will be available in Spring container to inject to any constructors or fields.

Setter-based injection

For setter-based DI, the Spring container will call setter methods of our class after invoking a no-argument constructor or no-argument static factory method to instantiate the bean. Let’s look at the following example, in this example we’ll inject bean via xml configuration:

@Configuration
public class Order{
 private Product product;

@Bean
public Order create() {
    Order order = new Order();
    order.addProduct(this.product);
    return order;
}
 public Product getProduct() {
  return product;
 }
 public void setProduct(Product product) {
  this.product= product;
 }
<?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-4.0.xsd">
 
  <!-- defining Product bean -->
  <bean id="productBean" class="com.learncode24h.Product" />
 
  <!-- Defining Order bean and inject Product bean-->
  <bean id="orderBean" class="com.learncode24h.Order">
      <property name="product" ref="productBean" />
  </bean>

</beans>

Below is an exampe about how to create multiple beans without using @Component annotation

@Configuration
public class AppConfig {

    @Bean
    public Bean1 createBean1() {
        return new Bean1();
    }

    @Bean
    public Bean2 createBean2() {
        return new Bean2(createBean1());
    }
}

Use @Configuration annotation on top of any class to declare that this class provides one or more @Bean methods and may be processed by the Spring container to generate bean definitions and service requests for those beans at runtime. You will probably be wondering when using @Configuration and when using @Component to declare bean? We’ll look at the following example

DatabaseConnector.java
public abstract class DatabaseConnector {
    public abstract Connection connect(String url, String user, String pass);
}

From this parent class, we create 3 child classes corresponding to 3 types of database.

MongoDbConnector.java
public class MongoDbConnector extends DatabaseConnector {
    @Override
    public Connection connect(String url, String user, String pass) {
        Class.forName("mongodb.jdbc.MongoDriver");
        return DriverManager.getConnection(url, user, pass);
    }
}
MySqlConnector.java
public class MySqlConnector extends DatabaseConnector {
    @Override
    public Connection  connect(String url) {
        Class.forName("com.mysql.jdbc.Driver");  
        return DriverManager.getConnection(url, user, pass);
    }
}
DB2SqlConnector.java
public class DB2SqlConnector  extends DatabaseConnector{
    @Override
    public Connection connect(String url) {
        Class.forName("com.ibm.db2.jdbc.app.DB2Driver");
        return DriverManager.getConnection(url, user, pass);
    }
}
AppConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean("mysqlConnector")
    DatabaseConnector mysqlConfigure() {
        DatabaseConnector mySqlConnector = new MySqlConnector();
        mySqlConnector.connect("jdbc:mysql://localhost:3306/database","user","pass");
        return mySqlConnector;
    }

    @Bean("mongodbConnector")
    DatabaseConnector mongodbConfigure() {
        DatabaseConnector mongoDbConnector = new MongoDbConnector();
        mongoDbConnector.connect("jdbc:mongo://localhost:27017/database","user","pass");
        return mongoDbConnector;
    }

    @Bean("db2Connector")
    DatabaseConnector db2Configure(){
        DatabaseConnector db2Connector = new DB2SqlConnector();
        db2Connector.connect("jdbc:db2://localhost:50001/database","user","pass");
        return db2Connector ;
    }

}

Via this example, we realize that when we want to initialize multiple beans and setup for these beans at the same time before running, we should use @Configuration and @Bean

Field-Based Dependency Injection

In field-based dependency injection, fields/properties are annotated with @Autowired. Spring container will set these fields once the class is instantiated.

@Component
public class ConstructorBasedInjection {
 
    @Autowired
    private InjectedBean injectedBean;
 
}

Field-based dependency injection won’t work on fields that are declared final/immutable as this fields must be instantiated at class instantiation. The only way to declare immutable dependencies is by using constructor-based dependency injection.

Field injection is not recommended because:

  • You cannot create immutable objects, as you can with constructor injection
  • Your classes have tight coupling with your DI container and cannot be used outside of it
  • Your classes cannot be instantiated (for example in unit tests) without reflection. You need the DI container to instantiate them, which makes your tests more like integration tests
  • Your real dependencies are hidden from the outside and are not reflected in your interface (either constructors or methods)
  • It is really easy to have like ten dependencies. If you were using constructor injection, you would have a constructor with ten arguments, which would signal that something is fishy. But you can add injected fields using field injection indefinitely. Having too many dependencies is a red flag that the class usually does more than one thing, and that it may violate the Single Responsibility Principle.