Spring DI (Dependency Injection) and Bean

The foundation of Spring Framework is Dependency Injection. This article explains what DI is, how to use it at a basic level, and how DI works.

DI Separates “Dependencies”

What Is DI (Dependency Injection)?

Spring Framework started as a framework called a “DI container”. DI is the feature known as “dependency injection”.

Programs often use components that provide various functions. Those components are used after setting their properties and configuration. If all of those detailed settings are written directly in code, later changes and testing become very complicated.

The configuration and conditions needed by a component are called “dependencies”. Simply put, this means a relationship where an object depends on another object when it is created or used. Because of these dependencies, code can end up usable only in a specific situation.

Therefore, the basic idea of dependency injection is to separate dependency information, such as component configuration, from the code and inject it from the outside. There are several ways to do this, but it is easiest to understand if you think of the basic approach as building a program with Beans and configuration files.

A Bean is a simple class that has various values as properties. Normally, you create a Bean instance, set its properties, and use it. If this setup process, which is the dependency part, can be separated from the code, the code becomes simpler and testing becomes easier.

Spring Framework can write dependency parts in an XML file, read that file, and automatically create Bean instances. There are also methods that use annotations, but using Bean configuration files is the most basic approach.

Three DI Styles in Spring

Field Injection

  • This method uses Spring’s @Autowired to inject an object into a field declared inside the object.
  • It is convenient, but the reference relationship is hard to see.
  • If overused, reference relationships can become tangled.
public class Foo {
    @Autowired
    private Bar bar;
}

Advantages of Field Injection

  • Concise code.

Disadvantages of Field Injection

  • Unit testing is difficult.
  • final cannot be used.
  • Circular dependencies are not detected. A circular dependency is a case such as A -> B and B -> A.

Setter Injection (Type 2 IoC)

  • This method uses Spring’s @Autowired to inject through a setter method.
  • In Spring Framework XML Bean configuration files, property is used.
  • A NullPointerException can occur.
public class Foo {
    private Bar bar;

    @Autowired
    public void setBar (Bar bar) {
        this.bar = bar;
    }
}

Constructor Injection

  • This method connects dependencies between classes by using a constructor.
  • In Spring Framework XML Bean configuration files, constructor-arg is used.
  • Dependencies are injected through a required constructor and can be declared final.
  • Unlike field injection, the reference relationship can be seen easily.
  • Since dependencies must be assigned at creation time, a NullPointerException does not occur from a missing dependency.
public class Foo {
    private final Bar bar;

    @Autowired
    public Foo(Bar bar) {
        this.bar = bar;
    }
}

Advantages of Constructor Injection

  • Prevents circular references.
    • A circular reference occurs when A refers to B and B refers to A.
    • Constructor injection looks for the Bean to inject before creating the Bean, so an error occurs when the application starts and the problem can be found quickly.
  • Allows final.
    • Since dependency injection starts while the class is instantiated, fields can be declared final. This makes the object immutable.
  • Makes unit tests easier to write.
    • Test code can be written more conveniently without help from the Spring container.

Using Lombok’s @RequiredArgsConstructor

With constructor injection, you need to modify the constructor code whenever injected objects change.

This inconvenience can be solved by using Lombok’s @RequiredArgsConstructor. @RequiredArgsConstructor automatically creates a constructor for fields marked as final or @NotNull.

@RequiredArgsConstructor
public class Foo {
    private final Bar bar;
}

DI in Spring

It is similar to assembling parts according to a specification.

  • What the programmer does
    • Create parts by implementing classes.
    • Define the part assembly specification, preferably with constructor injection.
  • What Spring Framework does
    • Assemble the parts. In other words, Spring automatically performs DI by calling constructors or setters.
    • Automatically uses “products” it knows how to use.
      • For example, when a request for a specific URL arrives, Spring automatically calls the processing logic, such as a Controller, for that request.

Create an Interface and Bean Class

Now let’s create a simple example and explain the basics of DI. First, create a Bean class. This time, prepare a simple Bean that stores one message.

Under the com.devkuma.spring package of the previously created MySpringApp project, create the interface and class shown below.

SampleBeanInterface is an interface that defines the contents of the Bean. It only allows two methods for exchanging messages: getMessage and setMessage.

SampleBeanInterface interface

package com.devkuma.spring;
 
public interface SampleBeanInterface {
    public String getMessage();
    public void setMessage(String message);
}

The class that implements this interface is SampleBean. It has a message String property and overrides the toString method. It is a simple Bean without any special features.

You may wonder why such a simple class needs to be created from an interface. Spring Framework Beans can be used without creating a separate interface. However, to make the general usage of Beans easier to imagine, this example starts with an interface.

SampleBean class

package com.devkuma.spring;
 
public class SampleBean implements SampleBeanInterface {
    private String message;
     
    public SampleBean() {
        message = "(no message)";
    }
     
    public SampleBean(String message) {
        this.message = message;
    }
 
    public String getMessage() {
        return message;
    }
 
    public void setMessage(String message) {
        this.message = message;
    }
 
    @Override
    public String toString() {
        return "SampleBean [message=" + message + "]";
    }
}

Write the Bean Configuration File

Next, create a configuration file for using the Bean. In the project’s src/main folder, create a resources folder, and inside it create the Bean configuration file.

The following is an example. Save it in the resources folder with the name bean.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="bean1" class="com.devkuma.spring.SampleBean">
        <property name="message" value="Hello, this is Bean Sample!!" />
    </bean>
 
</beans>

This Bean configuration file contains Bean information through a <bean> tag inside the <beans> tag. Its structure means the following.

<bean id="name" class="class">
    <property name="property name" value="value"/>
    ...... add as many <property> entries as needed ......
</bean>

This SampleBean has one property named message, so one <property> tag with name="message" is prepared. The value to set for the property is written there. This allows a Bean instance with the configured property value to be created automatically.

Use the Bean in an Application

Now use the Bean defined in bean.xml from the application. In the com.devkuma.spring package of MySpringApp, create App.java and write the following source code. When it runs, it prints SampleBean and displays Hello, this is Bean Sample!!.

package com.devkuma.spring;
 
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
 
public class App {
 
    public static void main(String[] args) {
        ApplicationContext app = new ClassPathXmlApplicationContext("bean.xml");
        SampleBeanInterface bean1 = (SampleBeanInterface)app.getBean("bean1");
        System.out.println(bean1);
    }
 
}

Execution result:

9월 03, 2017 4:38:49 오후 org.springframework.context.support.AbstractApplicationContext prepareRefresh
정보: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@5ce65a89: startup date [Sun Sep 03 16:38:49 KST 2017]; root of context hierarchy
9월 03, 2017 4:38:49 오후 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
정보: Loading XML bean definitions from class path resource [bean.xml]
9월 03, 2017 4:38:50 오후 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
정보: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@79b4d0f: defining beans [bean1]; root of factory hierarchy
SampleBean [message=Hello, this is Bean Sample!!]

Here is a brief explanation of what happened.

1. Create an ApplicationContext from the Bean configuration file.

ApplicationContext app = new ClassPathXmlApplicationContext("bean.xml");

The basic step for using Beans is first to obtain an instance of ApplicationContext. As the name suggests, this class manages the application context. In this case, you can think of the context as the Beans.

There are several ways to create an ApplicationContext. One way is to read a Bean configuration file, such as the bean.xml file just created, and create the context based on it. The ApplicationContext created from a Bean configuration file is a ClassPathXmlApplicationContext. It is a subclass that adds XML file processing functionality. The argument specifies the Bean configuration file name.

2. Obtain the Bean.

SampleBeanInterface bean1 = (SampleBeanInterface) app.getBean("bean1");

Once the ApplicationContext instance is ready, the rest is simple. Call the getBean method. This retrieves the Bean instance with the name specified as the argument. Recall that bean.xml contains <bean id="bean1"...>. That id value is used as the argument to getBean.

The extracted Bean can be used like a normal instance. What matters is that the message property value is already set on the Bean. The <property> tag in bean.xml is what configured it.

This means you can change the contents of the SampleBean being used simply by changing the value in bean.xml, without changing the source code at all. This is dependency injection. In other words, Bean contents can be manipulated from the outside without changing the code that uses the Bean.

Add Another Bean

Now that you understand the basic mechanism of dependency injection, take one more step and create another Bean.

Create a simple sample as follows. This time, create a class named SomeBean. It also implements SampleBeanInterface and has a message property. In reality, however, it does not have a field named message of type String. Instead, it stores Date and SimpleDateFormat internally and allows a date to be exchanged temporarily as text through message.

package com.devkuma.spring;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class SomeBean implements SampleBeanInterface {
  private Date date;
  private SimpleDateFormat format;

  public SomeBean() {
   date = Calendar.getInstance().getTime();
    format = new SimpleDateFormat("yyyy/MM/dd");
  }

 public String getMessage() {
    return format.format(date);
 }

 public void setMessage(String message) {
    try {
     date = format.parse(message);
   } catch (ParseException e) {
      e.printStackTrace();
      date = null;
    }
 }

 @Override
 public String toString() {
    return "SomeBean [date=" + format.format(date) + "]";
 }
}

After preparing the class, open bean.xml and modify the previously written <bean> tag as follows.

<bean id="bean1" class="com.devkuma.string.SomeBean">
    <property name="message" value="2017/9/3"/>
</bean>

When you run it now, the printed text changes to SomeBean [date=2017/09/03]. You can see that a SomeBean instance has been created and made available, even though the App source code was not touched at all.

9월 03, 2017 4:56:10 오후 org.springframework.context.support.AbstractApplicationContext prepareRefresh
정보: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@5ce65a89: startup date [Sun Sep 03 16:56:10 KST 2017]; root of context hierarchy
9월 03, 2017 4:56:10 오후 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
정보: Loading XML bean definitions from class path resource [bean.xml]
9월 03, 2017 4:56:10 오후 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
정보: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@79b4d0f: defining beans [bean1]; root of factory hierarchy
SomeBean [date=2017/09/03]

As in this example, if you define an interface and prepare multiple implementation classes, you can not only set property values easily but also freely change how those properties are processed. The class to use and the property values can be changed without touching the code. This shows the advantage of automatically creating Bean instances from a configuration file.