Dependency Injection (DI)

DI (Dependency Injection), Spring IoC is not a technology, but an idea, through which we can guide the design of loosely coupled program code. The function of the Spring IoC idea is reflected in two aspects: first, how to assemble beans into containers and how to obtain beans from containers; second, how to resolve the dependencies between beans. In other words, if the dependencies are managed by IoC containers, when a Bean needs to rely on another Bean, How the IoC container implements such a dependency.

The implementation approach to resolve the dependencies between beans in Spring is called Dependency Injection (DI) in Spring concept. There are three commonly accepted implementations of Spring dependency injection: constructor injection, setter injection, and annotation injection. However, as far as I’m concerned, I think it should be divided into two forms — XML-based injection and annotation-based injection — and then subdivided into the following forms:

Xml-based injection is the first way we learn and use, and the most familiar way, just a brief introduction, for example.

  • Injection via constructor

    public class UserServiceImpl implements UserService {

    private UserDao userDao; public UserServiceImpl(UserDao userDao) { this.userDao = userDao; } /** inherits from the UserService method **/Copy the code

    }

First define a service layer, UserServiceImpl, and then add a reference to the DAO layer, userDao, within it.

The next step is to add a constructor public UserServiceImpl(UserDao UserDao) for Spring to inject an instance of the UserDao.

<! - registered userDao - > < bean id = "userDao" class = "com. Klasdq. Sb. C1. Di. Dao. The impl. UserDaoImpl" > < / bean > <! - registered userService and inject userDao - > < bean id = "userService" class = "com. Klasdq. Sb. C1. Di. Service. The impl. UserServiceImpl" > <constructor-arg name="userDao" ref="userDao"></constructor-arg> </bean>Copy the code

Finally, inject the corresponding bean instance into the Spring XML configuration file.

With constructor injection, the injected class must have a corresponding constructor. If there is no corresponding constructor, an error will be reported.

  • Injection via setter methods

Change userServiceImp.java to:

public class UserServiceImpl implements UserService { private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } /** Inherit method from UserService **/}Copy the code

Then modify the content of the XML file to:

<! - registered userDao - > < bean id = "userDao" class = "com. Klasdq. Sb. C1. Di. Dao. The impl. UserDaoImpl" > < / bean > <! - registered userService and inject userDao - > < bean id = "userService" class = "com. Klasdq. Sb. C1. Di. Service. The impl. UserServiceImpl" > < property  name="userDao" ref="userDao"></property> </bean>Copy the code

The difference between the two methods is that UserServiceImpl. Java does not need to add a constructor, but it must have a constructor with no arguments (such as public UserServiceImpl()). Because Java provides a no-argument constructor by default) for the Spring container to register generated beans (such as userService).
In setter method injection, the tag is used.

In XML injection, in addition to using ‘ref=””‘ references, you can use ‘value=””‘ to set specific values, similar to using the ‘@value’ annotation.

Annotation-based dependency injection

@Autowired
  • The source code

    @Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Autowired { boolean required() default true; }

@autoWired is the key to annotation-based dependency injection. Its source code is very simple. It has only one parameter, request(), which identifies whether the injected Bean must be injected. If the value is false, there is no exception, but during use, null-pointer exceptions can occur if the container keeps not injecting the Bean.

Another point is that the @target parameter in the source code is exactly the type of annotation based dependency injection. @target determines which types @AutoWired can be tagged on.

  • Injection via constructor

    @Service(“userService”) public class UserServiceImpl implements UserService {

    private UserDao userDao; @Autowired public UserServiceImpl(UserDao userDao) { this.userDao = userDao; } /** inherits from the UserService method **/Copy the code

    }

According to the development documentation, this is the case with only one constructor. Since Spring4.3, the @autowired annotation is no longer needed, but is fine. However, if you have multiple constructors, you must annotate @autoWired for one of them or Spring will raise an exception.

  • Injection via setter methods

    @Service(“userService”) public class UserServiceImpl implements UserService {

    private UserDao userDao; @Autowired public void setUserDao(UserDao userDao) { this.userDao = userDao; } /** inherits from the UserService method **/Copy the code

    }

  • By field injection

    @Service(“userService”) public class UserServiceImpl implements UserService {

    @Autowired private UserDao userDao; /** inherits the UserService method **/Copy the code

    }

  • Injection by method input parameter

The above three injection methods are more familiar and will not be elaborated. To focus on parameter injection, method injection feels similar to constructor and setter injection. It is equivalent to putting @autowired, the annotation on the constructor and setter method, in place of the input parameter. This may sound abstract, but let’s look at an example:

@Component public class UserDaoImpl implements UserDao {// @override public User getUser(Long id, String name){User User = new User(); user.setId(id); user.setName(name); user.setAccount("12345678911"); user.setPassword("******"); user.setOtherInfo("this is a test account"); return user; UserService @service (" UserService ") public class UserServiceImpl implements UserService {private UserDao userDao; Public UserServiceImpl(@autoWired UserDao UserDao, @autowired User User) {system.out.println ("UserServiceImpl: "+user); this.userDao = userDao; } @Override public User getUser(Long id, String name){ return userDao.getUser(id,name); } // A simple Configuration class is used to generate beans for classes annotated with @Componet (@Service also count) and inject @Configuration@ComponentScan for beans under the @AutoWired flag Public class DIConfig {public user getUser(){user u = new user (); u.setName("user inject into service"); return u; }} // Test class // Note: When testing with JUnit4, If you want to use @autoWired injection then you must add the // @runwith annotation to start with Spring (or SpringBootRunner) //@ContextConfiguration to scan the configuration class @runwith (springrunner.class) @contextConfiguration (classes = DIConfig. Class) public class DITest { @autoWired private UserService UserService; @Test public void testAutowired(){ System.out.println(userService.getUser(1L,"name")); }}Copy the code

After running the test method, you get the following results:

Public UserServiceImpl(@autoWired UserDao UserDao, @autoWired User User)

Public void testAutowired()

Note that therePublic UserServiceImpl(@autoWired UserDao UserDao, @autowired User User)The arguments:

UserDao is the field of UserServiceImpl, but user is not. That is, we can add any parameter to the constructor that we want, but it doesn’t have to be an attribute field in the class.

It is also important to note that this method is not an arbitrary method, but a constructor or setter method, which cannot be injected by public void initService(@autoWired UserDao UserDao) custom method.

@ Primary and @ the Qualifier

In the above example, we inject the beans we use only when there is only one instance of the bean in the container. What happens when there are multiple beans of the same type in the container?

Modify the configuration class code as follows:

@Configuration @ComponentScan public class DIConfig { @Bean public User getUser(){ User u = new User(); u.setName("this is user"); return u; } @Bean public User getUser2(){ User u = new User(); u.setName("this is user2"); return u; }}Copy the code

Modify test class:

@RunWith(SpringRunner.class) @ContextConfiguration(classes = DIConfig.class) public class DITest { @Autowired private User user; @Test public void testAutowiredPriamry(){ System.out.println(user); }}Copy the code

When no other processing is done, the result is:

Because there are two User beans (getUser, getUser2, and if @bean is not specified, the default method Name is Bean Name), Spring cannot determine which one to use for injection.

Modification mode:

  • in@BeanSet name, as shown in@Bean(name="user")When the names matchprivate User user;Can also complete injection.
  • willprivate User userRewrite intogetUserorgetUser2Any one of these can also be injected. As above, Spring will first match by type and then by name. If no match is found, Spring will throw an exception.

In addition, Spring provides two annotations to disambiguate dependency injection.

  • @Primary

    @Target({ElementType.TYPE, @retention (RetentionPolicy.runtime) @documented Public @interface Primary {}

@primary is an annotation that sets the priority of beans of the same type, that is, once @priamry is added to a type, beans identified by @Priamry will be injected when no Bean is explicitly specified at injection time.

@Configuration @ComponentScan public class DIConfig { @Primary @Bean public User getUser(){ User u = new User(); u.setName("this is user"); return u; } @Bean public User getUser2(){ User u = new User(); u.setName("this is user2"); return u; }}Copy the code

For example, add annotations to getUser() and the test method will work.

The problem with this approach, however, is that @priamry can be used on many classes. If there are multiple beans of the same type labeled @primary, @Priamry loses its effectiveness.

  • @Qualifier

Therefore, Spring also provides the @Qualifier annotation directly on the @AutoWired injected Bean, specifying that it should be injected with a Bean.

@Target({ElementType.FIELD, 
         ElementType.METHOD, 
         ElementType.PARAMETER, 
         ElementType.TYPE, 
         ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier {
    String value() default "";
}
Copy the code

@Qualifier can appear anywhere @AutoWired can appear and be used with it. Like this:

@runwith (springrunner.class) @contextConfiguration (classes = DIConfig. Class) public class DITest {// Directly specify to use getUser2 for injection @Autowired @Qualifier("getUser2") private User user; @Test public void testAutowiredPriamry(){ System.out.println(user); }}Copy the code

Both of these annotations are disambiguating and the combination of @bean (name=” XXX “) and @qualifier (value=” XXX “)** is recommended. However, if there is no ambiguity in the development environment, there is no need to use these.

Of course, the above is just a general introduction to @AutoWired. If you want to learn more, check out the Annotation-based Container Configuration. This reference document provides a more detailed and informative introduction.

conclusion

In general, how does Spring implement IoC? First, Spring provides an IoC container for getting and managing beans. Then, a dependency injection mechanism is provided to help IoC containers better manage the dependencies between beans, so as to better realize IoC ideas. A Bean cannot exist completely independent of the dependencies of other beans, and dependency injection is needed when a Bean needs the introduction of other beans to initialize.

For example, suppose there is A method of class A that wants to call interface B or that needs an instance of interface B.

The traditional program flow is to use A C class to implement the B interface, and then class A creates an instance of class C to invoke its methods.

In Spring dependency injection, class A simply needs to add an injection interface to itself. This interface can be A constructor, setter, or other form. Also add a reference to interface B (private B B;) .

When an instance of class A really needs to be generated, the Spring IoC container injects A Bean based on the interface provided by class A, which can be class C (class C implements B{}), class D (class D implements B{}), and so on. Who is determined based on the Bean assembly strategy and beans in the IoC container is no longer managed by the developer.