Do you know the most complete Spring dependency injection method?

Posted by cheekydump on Mon, 28 Feb 2022 02:51:24 +0100

preface

Spring, just as its name, brings spring to developers. Spring is a framework designed to solve the complexity of enterprise application development. Its design concept is to simplify development.

The core idea of Spring framework is:

  • IOC (inversion of control): that is, transfer the control of creating objects, and transfer the control of creating objects from the developer to the Spring framework.
  • AOP (aspect programming): encapsulate public behaviors (such as logging, permission verification, etc.) into reusable modules, so that the original module only needs to pay attention to its own personalized behavior.

This article will mainly introduce IOC dependency injection in Spring,

Control inversion IOC

As far as IOC itself is concerned, it is not a new technology, but an idea. The core of IOC is to create an object originally. We need to create it directly through new, and IOC is equivalent to someone creating the object for us. When we need to use it, we can get it directly. IOC mainly has two implementation methods:

  • DL (Dependency Lookup): Dependency Lookup.

This means that the container helps us create objects. When we need to use them, we will take the initiative to find them in the container, such as:

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/application-context.xml");
Object bean = applicationContext.getBean("object");
  • DI (Dependency Inject): dependency injection.

Compared with dependency injection, dependency search is an optimization, that is, we don't need to find it ourselves. We just need to tell the container the object that needs to be injected, and the container will automatically inject (assign) the created object.

Dependency injection DI

We will not discuss the injection methods through xml. Here we mainly discuss the annotation based injection methods. There are usually three kinds of annotation based conventional injection methods:

  • Attribute based injection
  • setter based method injection
  • Constructor based injection

Three conventional injection methods

Next, let's introduce three conventional injection methods.

Attribute injection

Attribute injection is very common. It should be a familiar method:

@Service
public class UserService {
    @Autowired
    private Wolf1Bean wolf1Bean;//Through attribute injection
}

setter method injection

In addition to property injection, it can also be injected through setter method:

@Service
public class UserService {
    private Wolf3Bean wolf3Bean;
    
    @Autowired  //Implement injection through setter method
    public void setWolf3Bean(Wolf3Bean wolf3Bean) {
        this.wolf3Bean = wolf3Bean;
    }
}

Constructor Injection

When the two classes are strongly related, we can also implement injection through the constructor:

@Service
public class UserService {
  private Wolf2Bean wolf2Bean;
    
     @Autowired //Injection through constructor
    public UserService(Wolf2Bean wolf2Bean) {
        this.wolf2Bean = wolf2Bean;
    }
}

Interface injection

In the above three conventional injection methods, if we want to inject an interface and the current interface has multiple implementation classes, an error will be reported at this time, because Spring cannot know which implementation class to inject.

For example, all the above three classes implement the same interface IWolf. At this time, the conventional injection method without any annotation metadata is directly used to inject the interface IWolf.

@Autowired
private IWolf iWolf;

When the service is started, an error will be reported:

This means that one class should have been injected, but Spring found three, so it is impossible to confirm which one should be used. How to solve this problem?

There are five main solutions:

Implemented through configuration file and @ ConditionalOnProperty annotation

The @ ConditionalOnProperty annotation can be combined with the configuration file to achieve unique injection. The following example is to say that if lonely is configured in the configuration file Wolf = test1, then Wolf1Bean will be initialized to the container. At this time, because other implementation classes do not meet the conditions, they will not be initialized to the IOC container, so the interface can be injected normally:

@Component
@ConditionalOnProperty(name = "lonely.wolf",havingValue = "test1")
public class Wolf1Bean implements IWolf{
}

Of course, in this configuration mode, the compiler may still prompt that there are multiple beans, but as long as we ensure that the conditions of each implementation class are inconsistent, it can be used normally.

Annotation through other @ Condition conditions

In addition to the above configuration file conditions, other similar conditions can be annotated, such as:

  • @ConditionalOnBean: initializes this class to the container when a Bean exists.
  • @ConditionalOnClass: initializes the container of a class when it exists.
  • @When a conditionbean does not exist, this class cannot be initialized.
  • @ConditionalOnMissingClass: when a class does not exist, initialize the class to the container.
  • ...

Similarly, this implementation method can also be very flexible to realize dynamic configuration.

However, it seems that these methods described above can only inject one implementation class at a time. If we just want to inject multiple classes at the same time, and different scenarios can be dynamically switched without restarting or modifying the configuration file, how can we implement it?

Get dynamically through @ Resource annotation

If we don't want to get it manually, we can also get it by dynamically specifying BeanName in the form of @ Resource annotation:

@Component
public class InterfaceInject {
    @Resource(name = "wolf1Bean")
    private IWolf iWolf;
}

As shown above, only the implementation class with BeanName wolf1Bean will be injected.

Injection through collection

In addition to specifying Bean injection, we can also inject all implementation classes of the interface at one time through collection:

@Component
public class InterfaceInject {
    @Autowired
    List<IWolf> list;

    @Autowired
    private Map<String,IWolf> map;
}

The above two forms will inject all the implementation classes in IWolf into the collection. If the List set is used, we can take it out and determine the type through the instanceof keyword; If injected through the Map collection, Spring will store the Bean name (the initial lowercase of the default class name) as a key, so that we can dynamically obtain the implementation class we want when we need it.

@The Primary annotation implements default injection

In addition to the above methods, we can also add @ Primary annotation to one of the implementation classes to indicate that when multiple beans meet the conditions, the current beans with @ Primary annotation will be injected first:

@Component
@Primary
public class Wolf1Bean implements IWolf{
}

In this way, Spring will inject wolf1Bean by default, and at the same time, we can still get other implementation classes manually through the context, because other implementation classes also exist in the container.

Several ways to manually obtain beans

In the Spring project, you need to obtain the Bean manually through the ApplicationContext object. At this time, you can obtain it in the following five ways:

Direct injection

The simplest method is to obtain the ApplicationContext object through direct injection, and then obtain the Bean through the ApplicationContext object:

@Component
public class InterfaceInject {
    @Autowired
    private ApplicationContext applicationContext;//injection

    public Object getBean(){
        return applicationContext.getBean("wolf1Bean");//Get bean
    }
}

Obtained through ApplicationContextAware interface

Obtain the ApplicationContext object by implementing the ApplicationContextAware interface, so as to obtain the Bean. It should be noted that the classes that implement the ApplicationContextAware interface also need to be annotated in order to hand over to Spring for unified management (this method is also a more commonly used method in the project):

@Component
public class SpringContextUtil implements ApplicationContextAware {
    private static ApplicationContext applicationContext = null;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * Get bean by name
     */
    public static <T>T getBeanByName(String beanName){
        return (T) applicationContext.getBean(beanName);
    }

    /**
     * Get bean by type
     */
    public static <T>T getBeanByType(Class<T> clazz){
        return (T) applicationContext.getBean(clazz);
    }
}

After encapsulation, we can directly call the corresponding method to obtain the Bean:

Wolf2Bean wolf2Bean = SpringContextUtil.getBeanByName("wolf2Bean");
Wolf3Bean wolf3Bean = SpringContextUtil.getBeanByType(Wolf3Bean.class);

Obtained through ApplicationObjectSupport and WebApplicationObjectSupport

In these two objects, WebApplicationObjectSupport inherits ApplicationObjectSupport, so there is no substantive difference.

Similarly, the following tool classes also need to be annotated to be managed by Spring:

@Component
public class SpringUtil extends /*WebApplicationObjectSupport*/ ApplicationObjectSupport {
    private static ApplicationContext applicationContext = null;

    public static <T>T getBean(String beanName){
        return (T) applicationContext.getBean(beanName);
    }

    @PostConstruct
    public void init(){
        applicationContext = super.getApplicationContext();
    }
}

With a tool class, you can call it directly in the method:

@RestController
@RequestMapping("/hello")
@Qualifier
public class HelloController {
    @GetMapping("/bean3")
    public Object getBean3(){
        Wolf1Bean wolf1Bean = SpringUtil.getBean("wolf1Bean");
        return wolf1Bean.toString();
    }
}

Obtained through HttpServletRequest

Through the # HttpServletRequest # object, combined with the tool class # WebApplicationContextUtils # provided by Spring, you can also obtain the # ApplicationContext # object, and the # HttpServletRequest # object can be obtained actively (getBean2 method below) or passively (getBean1 method below):

@RestController
@RequestMapping("/hello")
@Qualifier
public class HelloController {

    @GetMapping("/bean1")
    public Object getBean1(HttpServletRequest request){
        //Directly through the HttpServletRequest object in the method
        ApplicationContext applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());
        Wolf1Bean wolf1Bean = (Wolf1Bean)applicationContext.getBean("wolf1Bean");

        return wolf1Bean.toString();
    }

    @GetMapping("/bean2")
    public Object getBean2(){
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();//Get request object manually
        ApplicationContext applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());

        Wolf2Bean wolf2Bean = (Wolf2Bean)applicationContext.getBean("wolf2Bean");
        return wolf2Bean.toString();
    }
}

Obtained by other means

Of course, in addition to the methods mentioned above, we can also use the code example in DL mentioned at the beginning to manually new an ApplicationContext object, but this means that it is reinitialized, so it is not recommended to do so, but this method is more suitable when writing unit tests.

Talk about the difference between @ Autowrite and @ Resource and @ Qualifier annotation

As we can see above, a Bean can be injected through @ Autowrite or @ Resource annotation. What's the difference between these two annotations?

  • @Autowrite: through type de injection, it can be used for constructor and parameter injection. When we inject an interface, all its implementation classes belong to the same type, so we can't know which implementation class to choose to inject.
  • @Resource: it is injected by name by default and cannot be used for constructor and parameter injection. If a unique Bean cannot be found by name, it will be found by type. The unique implementation can be determined by specifying name or type as follows:
@Resource(name = "wolf2Bean",type = Wolf2Bean.class)
 private IWolf iWolf;

The @ Qualifier , annotation is used to identify qualified ones. When @ Autowrite , and @ Qualifier , are used together, it is equivalent to determining the uniqueness by name:

@Qualifier("wolf1Bean")
@Autowired
private IWolf iWolf;

Then someone might say, I'll just use @ Resource directly. Why bother to combine two annotations? It seems that @ Qualifier annotation is a little redundant?

@Is the Qualifier annotation redundant

Let's first look at the following scenario of declaring a Bean. Here, a Bean (MyElement) is declared through a method, and the parameters in the method have Wolf1Bean objects. At this time, Spring will help us inject Wolf1Bean automatically:

@Component
public class InterfaceInject2 {
    @Bean
    public MyElement test(Wolf1Bean wolf1Bean){
        return new MyElement();
    }
}

However, if we change the above code slightly, change the parameter to an interface, and the interface has multiple implementation classes, an error will be reported:

@Component
public class InterfaceInject2 {
    @Bean
    public MyElement test(IWolf iWolf){//At this time, because the IWolf interface has multiple implementation classes, an error will be reported
        return new MyElement();
    }
}

The @ Resource annotation cannot be used in parameters, so @ Qualifier annotation is needed to confirm the unique implementation at this time (for example, @ Qualifier annotation is often used when configuring multiple data sources):

@Component
public class InterfaceInject2 {
    @Bean
    public MyElement test(@Qualifier("wolf1Bean") IWolf iWolf){
        return new MyElement();
    }
}

summary

This paper mainly describes how to use flexible methods to realize the injection methods of various scenarios in Spring, and focuses on how to inject when an interface has multiple implementation classes. Finally, it also introduces the differences of several common injection annotations. Through this paper, I believe you will be more familiar with how to use dependency injection in Spring.

Topics: Java Spring