Analysis of Three Injection Modes Based on spring DI

  • 2021-10-16 01:50:50
  • OfStack

1. Foreword: IOC (inversion of control) and DI (dependency injection)

The importance of Spring framework to Java development is self-evident. Its core features are IOC (Inversion of Control, inversion of control) and AOP, among which IOC is commonly used. We control the dependencies of objects by Spring by handing over components to IOC container of Spring, thus avoiding excessive program coupling caused by hard coding.

Before we go into dependency injection, I think it's necessary to understand the relationship between IOC (inversion of control) and DI (dependency injection) under 1, which is described in detail in this article: spring, IOC and DI.

2. Three common injection methods of 2. DI

There are three common injection methods of DI: setter injection, constructor injection and annotation-based injection (also called field injection). Let's talk about their characteristics respectively.

2.1 Based on annotation injection

First, let's look at its implementation under 1:


@RestController
@RequestMapping("/annotation")
public class AnnotationController {
    @Autowired
    private DiService diService;
 
    @GetMapping("/test001")
    public String test001() {
        return diService.test001("annotation");
    }
}

This method should be the most common injection method at present, for a simple reason:

1. The injection method is very simple: add @ Autowired annotation and add the field to be injected, and it can be completed.

2. Make the whole code concise and clear, and look beautiful and generous.

Before introducing the way of annotation injection, we should briefly understand one attribute autowire of bean, and autowire mainly has three attribute values: constructor, byName and byType.

constructor: Automatic injection by constructor, spring will match bean with constructor parameter type 1 for injection. If there is a multi-parameter constructor, a constructor with only one parameter, and multiple bean matching multi-parameter constructor are found in the container, spring will preferentially inject bean into multi-parameter constructor. byName: The id name injected into bean must match the second half of the set method, and the first letter of the id name must be lowercase, which is a little different from manual set injection. byType: Find all set methods and inject bean matching the matching parameter type.

Let's get down to business:

Register bean by annotation:

In previous development, we registered bean mainly with four annotations, each of which can be used arbitrarily, but with semantic differences:

@ Component: Can be used to register all bean @ Repository: Mainly used to register bean of dao layer @ Controller: bean mainly used to register the control layer @ Service: Mainly used to register bean of service layer

With the popularity of springboot, @ Bean annotation is gradually used by us. The @ Bean annotation of Spring is used to tell the method to generate an Bean object, which is then handed over to Spring management. The method that produces the Bean object, Spring, will only be called once, and then the Spring will place the Bean object in its own IOC container.

Annotation injects dependencies (there are two main ways):

@ Resource: Annotation of java. By default, byName matches id of bean with the same attribute name. If it is not found, it will be searched by byType. If byType finds more than one, use @ Qualifier Annotation (spring Annotation) to specify bean with a specific name. @ Autowired: spring annotation. By default, bean of the same type is matched in byType mode, which can be matched according to byName mode with @ Qualifier annotation.

For their specific usage and differences, because there are many contents, please write in another blog: @ Autowired and @ Resource for details

2.2 Constructor injection

As usual, let's start with the code example:


@RestController
@RequestMapping("/constructor")
public class ConstructorController {
    private final DiService diService;
    private final String result; 
    public ConstructorController(DiService diService) {
        this.diService = diService;
        this.result = diService.test001("constructor");
    }
 
    @GetMapping("/test001")
    public String test001() {
        return diService.test001(this.result);
    }
}

One problem here is that if there is only one constructor with parameters and the type of the parameter matches the type of the injected bean, it will be injected into that constructor. If there are multiple constructors with parameters and each constructor has an attribute to inject in its parameter list, where will userDaoJdbc be injected?

This is the recommended injection method in Spring4. x, which is a bit ugly compared with the above field injection method, especially when there are many injection dependencies (more than 5), it will obviously find that the code is bloated. For students who have transferred from field injection and have obsessive-compulsive disorder, it can be said to be Shi Lezhi, but why does spring officially recommend it?

The official document says this:

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.

The Spring team generally advocates constructor injection because it allows application components to be implemented as immutable objects and ensures that required dependencies are not null. In addition, the component injected into the constructor is always returned to the client (calling) code in a fully initialized state.

Simple explanation 1:

Immutable object: In fact, it is the final keyword, which will not be explained here. Dependence is not empty: it saves us from checking it. When you want to instantiate ConstructorController, because you implement a constructor with parameters, you will not call the default constructor, so you need the Spring container to pass in the required parameters, so there are two cases: 1. There are parameters of this type- > Incoming, OK. 2: No parameter of this type- > Report an error. This ensures that it will not be empty. Fully initialized state: This can be combined with the above dependency is not null. Before passing parameters to the constructor, to ensure that the injected content is not null, you must call the dependency component's constructor to complete instantiation. In the process of loading and instantiating Java class, the constructor is the last step (before, if there is a parent class to initialize the parent class first, then its own member variables, and finally the constructor, which will not be expanded in detail here.) . So what is returned is the state after initialization.

Compared with annotation injection, constructor injection has high reusability. If field injection is used, the disadvantages are obvious. For environments other than IOC container, the implementation class cannot be reused except using reflection to provide the dependencies it needs. And going 1 straight is a potential hazard, because you can't find the existence of NPE without calling going 1 straight.

Compared with annotation injection, constructor injection can prevent the problem of circular dependency, such as the following code:


public class A {
    @Autowired
    private B b;
}
 
public class B {
    @Autowired
    private A a;
}

If constructor injection is used, when the spring project starts, it will throw:

BeanCurrentlyInCreationException: Requested bean is currently in creation: Is there an unresolvable circular reference?

This reminds you to avoid cyclic dependencies. If it is annotation injection, it will not report an error when starting, but will report an error when using that bean.

2.3 setter injection

This is the officially recommended injection method when spring3. x came out, but it has not been recommended since spring4. x, and it is rarely seen in actual development.

Let's take a look at its implementation:


@RestController
@RequestMapping("/setter")
public class SetterController {
    private DiService diService; 
    @Autowired
    public void setDiService(DiService diService) {
        this.diService = diService;
    }
 
    @GetMapping("/test001")
    public String test001() {
        return diService.test001("setter");
    }
}

Imagine 1, 1 once needs to inject a lot of components, then we will be exhausted, so it is reasonable for everyone not to like to use it.

There is one point to note here: if attributes are injected through set method, spring will instantiate the object through the default empty parameter constructor, so if a constructor with parameters is written in the class, 1 must write the empty parameter constructor, otherwise spring has no way to instantiate the object, resulting in an error.

Summarize

With so many dependency injection methods, how should we choose? Which is the best way?

In fact, there is an old saying that it is right that what suits us is the best. We need to choose which injection method to use according to the situation.

Benefits of using constructor injection:

Guaranteed dependency immutability (final keyword) Ensure that the dependency is not empty (omitting us to check it) Ensure that the code of the client (calling) is fully initialized when it is returned Circular dependencies are avoided Improved code reusability

In addition, when one dependency is used by multiple implementations, it is recommended to use annotation injection to specify the injection type or name and setter injection to specify the type. This is the spring official blog on setter injection and constructor injection comparison.


Related articles: