preface

The purpose of this chapter is to explore some aspects of dependency injection during Spring development. Interested readers should take a look at the following questions:

  • @Autowired.@Resource.@InjectThe difference between the three notes
  • When you’re using@Autowired, whether there has beenField injection is not recommendedThe warning? Do you know why?
  • What are the options for Spring dependency injection? What is the official recommendation?

If you know all of the above, I think your development experience is good ๐Ÿ‘.

Here we will answer the above questions in turn and summarize the knowledge points.

@Autowired.@Resource.@InjectThe difference between the three notes

Spring supports dependency injection using @autoWired, @Resource, and @Inject annotations. Here are the differences between the three annotations.

@Autowired

The @autowired provide annotations to the Spring framework, you need to import the package org. Springframework. Beans. Factory. The annotation. Autowired.

Here is a sample code for easy explanation:

public interface Svc {

    void sayHello(a);
}

@Service
public class SvcA implements Svc {

    @Override
    public void sayHello(a) {
        System.out.println("hello, this is service A"); }}@Service
public class SvcB implements Svc {

    @Override
    public void sayHello(a) {
        System.out.println("hello, this is service B"); }}@Service
public class SvcC implements Svc {

    @Override
    public void sayHello(a) {
        System.out.println("hello, this is service C"); }}Copy the code

The test class:

@SpringBootTest
public class SimpleTest {

    @Autowired
    // @Qualifier("svcA")
    Svc svc;

    @Test
    void rc(a) { Assertions.assertNotNull(svc); svc.sayHello(); }}Copy the code

Assembly sequence:

  1. Find matching beans in context by type

    Find beans of type SvcCopy the code
  2. If there are multiple beans, they are matched by name

    1. If there is an @qualifier annotation, it matches the name specified by @qualifier

      Find the bean with name svcACopy the code
    2. If not, match by variable name

      Find the bean with name SVCCopy the code
  3. If no match is found, an error is reported. (@autowired (Required =false), if set to False (the default is true), no exception will be thrown if the injection fails)

@Inject

Under the environment of Spring, @ Inject and @autowired is the same, because their dependency injection AutowiredAnnotationBeanPostProcessor are used to deal with it.

Inject is the specification defined by JSR-330, and it is also possible to switch to Guice if you use it this way.

Guice is Google’s open source lightweight DI framework

If we insist on the difference between the two, first of all, @Inject is in the Java EE package and needs to be introduced separately in the SE environment. Another difference is that @AutoWired can set Required =false while @Inject does not.

@Resource

@resource is an annotation of the JSR-250 definition. Spring in CommonAnnotationBeanPostProcessor implements the handling of JSR – 250 annotations, including @ the Resource.

The @Resource has two important attributes: name and type, and Spring resolves the @Resource annotation’s name attribute to the bean’s name, and the Type attribute to the bean’s type.

Assembly sequence:

  1. If both are specifiednameandtype, find a unique matching bean from the Spring context for assembly, and throw an exception if no matching bean is found.
  2. If you specifyname, looks for the bean whose name (ID) matches from the context for assembly, and throws an exception if it cannot find it.
  3. If you specifytype, find the unique bean matching type from the context for assembly, can not find or find more than one, will throw an exception.
  4. If neither is specifiedname, it’s not specifiedtype“Is used by defaultbyNameAssembly; If no match is found, pressbyTypeAssemble.

The IDEA of promptField injection is not recommended

When using IDEA for Spring development, if you use the @autowired annotation on a field, you will see a warning in IDEA:

Field injection is not recommended

Inspection info: Spring Team Recommends: “Always use constructor based dependency injection in your beans. Always use assertions for mandatory dependencies”.

Translation:

Field-based injection is not recommended.

The Spring development team recommends that you always use a constructor-based approach for dependency injection in your Spring beans. For required dependencies, always use assertions to confirm them.

For example:

@Service
public class HelpService {
    @Autowired
    @Qualifier("svcB")
    private Svc svc;

    public void sayHello(a) { svc.sayHello(); }}public interface Svc {
    void sayHello(a);
}

@Service
public class SvcB implements Svc {
    @Override
    public void sayHello(a) {
        System.out.println("hello, this is service B"); }}Copy the code

After placing the cursor at @autowired and using the Alt + Enter shortcut to modify the code, the Constructor based injection method will be changed:

@Service
public class HelpService {
    private final Svc svc;
    
    @Autowired
    public HelpService(@Qualifier("svcB") Svc svc) {
        // Assert.notNull(svc, "svc must not be null");
        this.svc = svc;
    }
    
    public void sayHello(a) { svc.sayHello(); }}Copy the code

If you follow the Spring team’s advice, you should use assert.notnull (SVC, “SVC must not be null”) if SVC is a mandatory dependency.

It’s easy to fix this warning, but I think it’s more important to understand why the Spring team is suggesting this. What’s wrong with using field-based injection directly?


First, we need to know that there are three dependency injection methods in Spring:

  • Field based injection (property injection)
  • Setter-based injection
  • Constructor based injection

1. Field based injection

Field-based injection is dependency injection using annotations on bean variables. It is essentially injected directly into the field by reflection. This is one of the most familiar and familiar approaches I see in development, and one that the Spring team doesn’t recommend. Such as:

@Autowired
private Svc svc;
Copy the code

2. Injection based on setter methods

Dependency injection is done by using the setXXX() method of the corresponding variable and using annotations on the method. Such as:

private Helper helper;

@Autowired
public void setHelper(Helper helper) {
    this.helper = helper;
}
Copy the code

Note: In Spring 4.3 and later, the @Autowired annotation on setters is optional.

3. Constructor based injection

Constructor based injection is done by placing all required dependencies in parameters with annotated constructors and initializing the corresponding variables in the constructor. Such as:

private final Svc svc;
    
@Autowired
public HelpService(@Qualifier("svcB") Svc svc) {
    this.svc = svc;
}
Copy the code

In Spring 4.3 and later, if the class has only one constructor, then the constructor may not have the @autowired annotation on it.

Benefits of field-based injection

As you can see, this approach is very succinct and the code looks simple and easy to understand. Your classes can focus on the business without being tainted by dependency injection. You just throw @autoWired on top of the variable, no special constructor or set method is required, and the dependency injection container will provide the dependencies you need.

Disadvantages based on field injection

Success is nothing, failure is nothing

Field-based injection is simple, but it can cause a lot of problems. This is a problem I often encounter when I read code for projects.

  • With field injection, adding dependencies is easy. Even if you have a dozen dependencies in your class, you might be fine with that. The average developer will probably unconsciously add too many dependencies to a class. But when constructor injection is used, at a certain point, there are so many arguments in the constructor that it becomes obvious that something is wrong. Having too many dependencies usually means more responsibility for your classes, a clear violation of the SRP (Single responsibility principle).

    This problem is really common in our project code.

  • Dependency injection is coupled to the container itself

    One of the core ideas of the dependency injection framework is that container-managed classes should not rely on dependencies used by containers. In other words, the class should be a simple POJO(Plain Ordinary Java Object) that can be instantiated separately and you can provide the dependencies it needs.

    This problem can be expressed as follows:

    • Your classes are strongly coupled to their dependencies and cannot be used outside the container
    • Your classes cannot be instantiated without reflection (for example, when unit testing) and must be instantiated through a dependency container, which is more like integration testing
  • Immutable objects cannot be built using property injection (finalModified variable)

Recommendations from the Spring development team

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.

In a nutshell, it is

  • Enforce dependencies using constructors

  • Optional, mutable dependencies are injected using setters

    Of course you can use both methods in the same class. Constructor injection is better for mandatory injection, which is designed to be immutable, and Setter injection, which is better for mutable injection.

Let’s look at the reasons for Spring’s recommendation, first based on constructor injection,

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. As a side note, a large number of constructor arguments is a bad code smell, implying that the class likely has too many responsibilities and should be refactored to better address proper separation of concerns.

The Spring team advocates construction-based injection because it allows you to inject dependencies into immutable variables while ensuring that the values of those variables are not null. In addition, components that have been constructively injected (e.g., services) can be guaranteed to be fully ready when called. At the same time, from a code quality perspective, a large constructor often represents code smell, and the class may be taking too much responsibility.

For setter-based injection, they say something like this:

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. One benefit of setter injection is that setter methods make objects of that class amenable to reconfiguration or re-injection later.

Setter-based injection should only be used to inject non-essential dependencies and should provide a reasonable default value for this dependency in the class. If you use setters to inject the necessary dependencies, you will have too many null checks flooding your code. One advantage of using setter injection is that the dependency can be easily changed or re-injected.

summary

That’s all for this article, which hopefully gives you a better understanding of Spring’s dependency injection.

If this article is helpful to you, please give me a thumbs up. This is my biggest motivation ๐Ÿค๐Ÿค๐Ÿค—๐Ÿค—.

reference

  • Setter-based dependency injection

  • Field Dependency Injection Considered Harmful

  • Field injection is not recommended