[Spring] Three major dependency injections (@Autowired, Setter, construction method)

Directory

1. Property injection (@Autowired)

1.1 Advantage Analysis

1.2 Shortcoming Analysis

1.2.1 Unable to implement final modified variable injection.

1.2.2 Poor compatibility

1.2.3 (Possibly Violating) Design Principle Issues

1.2.4 Code example:

1.2.5 What should I do if there is a circular dependency?

1.2.6 The difference between @Resource and @Autowired

2. Setter injection

2.1 Advantage Analysis

2.2 Shortcoming Analysis

2.2.1 Immutable objects cannot be injected

2.2.2 The injected object can be modified

3. Construction method injection

3.1 Advantage Analysis

3.1.1 Injectable immutable objects

3.1.2 The injected object will not be modified

3.1.3 The injected object will be fully initialized

3.1.4 Better versatility


1. Attribute injection (@Autowired)

Attribute injection is implemented using @Autowired, as follows: Inject the UserService class into the UserController class:

The startup class is as follows:

The result of the operation is as follows:

1.1 Advantage Analysis

The biggest advantage of attribute injection is that it is easy to implement and easy to use. You only need to add an @Autowired to the variable, and you can directly get the injected object without the new object-this is exactly the function and charm of DI.

1.2 Shortcoming Analysis

1.2.1 Final modified variable injection cannot be realized.

The reason is also very simple. In Java, the final object (immutable) is either directly assigned or assigned in the constructor, so when the final object is injected using attributes, it does not conform to the final usage specification in Java, so it cannot be injected successfully. .

Q: So if you want to inject an immutable object, how to achieve it?

Answer; use constructor injection.

1.2.2 bad compatibility

Applies only to IoC containers. If the code of property injection is ported to other non-IoC frameworks, the code will be invalid, so the generality of property injection is not very good.

1.2.3 (Possible Violation) Design Principle Issue

Attribute injection is easy to violate the single design principle because it will lead to insufficient responsibilities of the class. In attribute injection, a class may have multiple dependencies at the same time. These dependencies may not be related to the main responsibilities of the class, resulting in insufficient responsibilities of the class. , making the code difficult to maintain.

The core idea of the single design principle is: a class should have only one responsibility. Only in this way can the code be easy to maintain and expand. related to the main responsibilities.

Of course, it does not mean that there will be violations of the single principle, But it is undeniable that: the simpler the injection implementation, the greater the probability of abuse, so the greater the probability of violation of the single responsibility principle strong>. Note: The emphasis here is on the possibility of violating the design principle (single responsibility), not necessarily violating the design principle. There is an essential difference between the two.

1.2.4 code example:

When using @Autowired injection, a class may depend on multiple other classes or interfaces, which will cause the class to have too many responsibilities and violate the single design principle. Below is an example:

Suppose there is an OrderService class, which needs to rely on a UserService and a ProductService to complete some business logic. If @Autowired is used to inject these two dependencies, then OrderService will depend on the two classes UserService and ProductService, resulting in the heavy responsibility of OrderService.

@Service
public class OrderService {
 
    @Autowired
    private UserService userService;
 
    @Autowired
    private ProductService productService;
 
    public void placeOrder() {
        // use userService and productService to place order
    }
}

The responsibilities of the OrderService class include user management and product management, which violates the single design principle. If UserService and ProductService are passed as method parameters instead of using @Autowired injection, then OrderService only needs to focus on the logic related to order management without relying on other classes or interfaces, which conforms to the single design principle.

1.2.5 What should I do if there is a circular dependency?

What is a circular dependency?

In Spring, when a bean is initialized, if the dependent bean has not been initialized, Spring will put the bean into a cache dedicated to storing the bean being initialized, so that it can be saved after the dependent bean is initialized. injection. But if two beans are dependent on each other, then their initialization sequence will create a deadlock, causing the application to fail to start.

@Autowired injection can make the relationship between classes and classes closer, prone to circular dependencies and complex dependencies, thus violating the principle of “high cohesion, low coupling” in the single design principle.

Here is an example:

public class OrderService {
    @Autowired
    private UserService userService;
    
    public void createOrder() {
        // create order code
        User user = userService. getUser();
        // order related code
    }
}

public class UserService {
    @Autowired
    private OrderService orderService;
    
    public User getUser() {
        // get user code
        Order order = orderService. getOrder();
        // user related code
    }
}

In this example, OrderService and UserService depend on each other, and there is a circular dependency relationship (specifically, when the container creates bean A, it will find that A needs to depend on B, so the container will first create bean B, but creating B also finds that B needs to depend on A, so the container will go back to create bean A, thus forming a circular dependency problem.). This makes the dependencies between classes in the system complicated, which is not conducive to code maintenance and expansion.

To solve this problem, constructor injection can be used instead of @Autowired injection. Constructor injection avoids circular dependency issues and is easier to maintain and test. For example, improvements can be made by:

public class OrderService {
    private UserService userService;
    
    public OrderService(UserService userService) {
        this. userService = userService;
    }
    
    public void createOrder() {
        // create order code
        User user = userService. getUser();
        // order related code
    }
}

public class UserService {
    private OrderService orderService;
    
    public UserService(OrderService orderService) {
        this. orderService = orderService;
    }
    
    public User getUser() {
        // get user code
        Order order = orderService. getOrder();
        // user related code
    }
}

In this improved code, the circular dependency problem is avoided through constructor injection, and it is more in line with the principle of “high cohesion, low coupling” in the single design principle.

Some people may say, doesn’t the Spring framework provide a three-level cache solution to solve circular dependencies?

In order to solve the problem of circular dependency, Spring does use a three-level cache to manage the bean creation and initialization process:

Specifically, when Spring creates a bean, it will put the bean into the three-level cache, in which the first-level cache stores beans that have been instantiated, the second-level cache stores beans that have completed attribute injection, and the third-level cache stores beans that have completed attribute injection. The level cache stores beans that have not yet completed property injection. When injecting properties, Spring will first look for the bean from the first-level cache, if it cannot find it, it will continue to look in the second-level cache, if it still cannot find it, it will continue to look in the third-level cache, if it finally finds If not, an exception will be thrown.

Since the three-level cache is provided, why should we care about whether the injection will cause dependency injection?

Although the Spring framework provides a three-level cache solution to resolve circular dependencies, using the @Autowired annotation may indeed cause circular dependencies. Moreover, if the circular dependency chain is long, the efficiency of the cache will be affected, and even the system performance will be degraded.

In addition, for large-scale systems, circular dependency is a design problem. It should not be “bypassed” in code implementation, but should be solved by optimizing code structure and splitting modules.

In general, although the Spring framework provides a solution to circular dependencies, in actual use, circular dependencies should be avoided, avoiding this problem from the design level, and improving system stability and maintainability.

Note that Setter injection cannot be used here to solve the circular dependency problem:

Setter injection can avoid the problem of circular dependencies to a certain extent, but it does not completely solve it. Setter injection is done by assigning values to the properties one by one after the bean initialization is completed, so it can avoid the circular dependency problem that may occur when dependency injection is performed in the constructor.

However, if there are multiple beans that depend on each other in setter injection, circular dependencies may still occur. For example, there is attribute a in beanA, which needs to be completed through setter injection, and attribute a needs to refer to attribute b in beanB, then there will be a circular dependency problem.

In order to avoid the problem of circular dependencies, there are three ways to solve it:

  1. Constructor injection: Inject dependencies in constructors to avoid circular dependency problems that may arise in setter injection.

  2. Use Delayed Dependency Injection: Instead of directly injecting dependencies during injection, a proxy object is used to implement delayed injection, and then inject when the dependency is really needed.

  3. Use factory mode: manage bean creation and dependency injection through factory mode, so as to solve the circular dependency problem.

1.2.6 Difference between @Resource and @Autowired

When performing class injection, in addition to using @Autowired, you can also use @Resource for injection, as shown in the following code:

The difference between @Autowired and @Resource

  • Different origins: @Autowired comes from Spring, and Resource comes from JDK annotations.
  • The parameters set when using are different: Compared with @Autowired, @Resource supports more parameter settings, such as name setting – you can get the bean according to the name.
  • @Autowired can be used for Setter injection, constructor injection and familiarity injection, while @Resource can only be used for Setter injection and property injection, not for constructor injection.
  • The search order is different: @Autowired searches by type first, then by name, while @Resource searches by name, then by type.

Multiple @Bean of the same type report an error

Code error:

This is because we do not store the Bean object of user1 in Spring:

@Resource can be used to find classes in Spring with the parameter name:

And @Autowired can be used with @Qualifier:

?

2. Setter injection

The implementation code is as follows:

@RestController
public class UserController {
    // Setter injection
    private UserService userService;

    @Autowired
    public void setUserService(UserService userService) {
        this. userService = userService;
    }

    public void sayHi() {
        System.out.println("com.java.demo -> do UserController sayHI()");
        userService.sayHi();
    }
}

2.1 Advantage Analysis

It can be seen that Setter injection is actually more troublesome than property injection, but it also has advantages, that is, it fully complies with the design principle of single responsibility, because each Setter only targets one object.

2.2 Shortcoming Analysis

2.2.1 Immutable objects cannot be injected

Same as @Autowired, Setter injection cannot inject immutable objects:

2.2.2 Injected object can be modified

Setter injection provides the setXXX method, which means that developers can change the injected object at any time and anywhere by calling the setXXX method.

3. Constructor injection

Constructor injection is the injection method officially recommended by Spring since 4.x. Its implementation code is as follows:

@Controller
public class UserController {
    // constructor injection
    private UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this. userService = userService;
    }
    public void sayHi() {
        System.out.println("com.java.demo -> do UserController sayHI()");
        userService.sayHi();
    }
   
}

Note that if there is only one constructor in the current class, @Autowired can also be omitted, so the above code can also be written like this:

@Controller
public class UserController {
    // constructor injection
    private UserService userService;

   
    public UserController(UserService userService) {
        this. userService = userService;
    }
    public void sayHi() {
        System.out.println("com.java.demo -> do UserController sayHI()");
        userService.sayHi();
    }
   
}

3.1 Advantage Analysis

3.1.1 Immutable objects can be injected

It is possible to inject immutable objects using the construction method, the following code implements:

@Controller
public class UserController {

    private final UserService userService;
    
    @Autowired
    public UserController(UserService userService) {
        this. userService = userService;
    }


    public void sayHi() {
        System.out.println("com.java.demo -> do UserController sayHI()");
        userService.sayHi();
    }
}

3.1.2 The injected object will not be modified

Since the construction method will only be executed once when the object is created, it avoids the situation that the injected object is not modified at any time (called) like Setter injection.

3.1.3 The injected object will be fully initialized

Because the dependent object is executed in the construction method, and the construction method is executed at the beginning of object creation, the injected object will be fully initialized before use, which is also one of the advantages of construction method injection.

3.1.4 Better versatility

Constructor injection is different from attribute injection. Constructor injection can be applied to any environment. Whether it is an IoC framework or a non-IoC framework, the code injected by the constructor is common, so its versatility is better.