preface

Spring, as its name suggests, is a framework designed to address the complexities of enterprise application development. It is designed to simplify development.

The core ideas in the Spring framework are:

  • IOC (Inversion of Control) : Transferring control of object creation from the developer to the developerSpringFramework.
  • AOP (faceted programming) : Encapsulate common behavior (such as logging, permission validation, etc.) into reusable modules, leaving the original module to focus only on its own personalized behavior.

In this article, I will focus on IOC’s dependency injection in Spring,

Inversion of control IOC

IOC itself is not a new technology, just an idea. The core of IOC is to create an object originally, and we need to create it by ourselves directly through new. IOC is equivalent to someone helping us to create an object, and we can take it directly when we need to use it. There are two main ways to achieve IOC:

  • DL (Dependency Lookup) : Dependency Lookup.

This means that the container helps us to create the object, we need to use the time to take the initiative to find in the container, such as:

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

Dependency injection is another kind of optimization compared with dependency search, that is, we do not need to search ourselves, just need to tell the container to inject the current object, the container will automatically create the object injection (assignment).

Dependency injection DI

We won’t discuss XML injection methods. We will focus on annotation-based injection methods here. There are three common annotation-based injection methods:

  • Property-based injection
  • Injection based on setter methods
  • Constructor based injection

Three conventional injection methods

Let’s take a look at each of the three common injection methods.

Properties into

Property injection is a common method, and this should be a familiar one:

@Service
public class UserService {
    @Autowired
    private Wolf1Bean wolf1Bean;// Via property injection
}
Copy the code

Setter method injection

In addition to property injection, injection can also be done through setter methods:

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

Constructor injection

When two classes are strongly related, we can also use constructor to implement injection:

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

Interface injection

In the three normal injection methods above, if we want to inject an interface and the current interface has multiple implementation classes, we will get an error because Spring has no way of knowing which implementation class to inject. For example, if the three classes above all implement the same interface, IWolf, then we inject the interface IWolf directly using normal injection without any annotation metadata.

@Autowired
private IWolf iWolf;
Copy the code

An error occurs when the service is started:

This means that one class should have been injected, but Spring found three, so it was impossible to determine which one to use. How can this problem be solved?

There are five main solutions:

This is done via the configuration file and @conditionalonProperty annotation

This can be done in conjunction with the configuration file via the @conditionalonProperty annotation. If lonely. Wolf =test1 is configured in the configuration file, Wolf1Bean will be initialized to the container. The other implementation classes will not be initialized to the IOC container because they do not meet the criteria, so the interface can be injected normally:

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

Of course, the compiler might still prompt for multiple beans in this configuration, but as long as we make sure that the conditions of each implementation class are not consistent, this works fine.

Annotated by other @condition conditions

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

  • ConditionalOnBean: When there is oneBeanInitializes this class to the container.
  • ConditionalOnClass: Initialize a container for a class when it exists.
  • ConditionalOnMissingBean: When one does not existBeanInitializes this class to the container.
  • @ ConditionalOnMissingClass: when does not exist a certain class, initialize such to the container.
  • .

This kind of implementation can also be very flexible to implement dynamic configuration.

However, the methods described above seem to inject only one implementation class at a time, so what if we want to inject multiple classes at the same time, and different scenarios can be dynamically switched without needing to restart or modify configuration files?

Dynamically retrieved via the @Resource annotation

If you don’t want to get it manually, you can specify BeanName dynamically as an @resource annotation:

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

As shown above, only BeanName is injected as the implementation class of wolf1Bean.

Through collection injection

In addition to specifying Bean injection, we can also inject all implementation classes of the interface at once via a collection:

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

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

Both of the above forms inject all of the implementation classes in IWolf into the collection. If we were using a List collection, we could pull it out and use the instanceof keyword to determine the type. With Map collection injection, Spring stores the Bean’s name (lowercase by default) as the key, so we can dynamically retrieve 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 the @primary annotation to one of the implementation classes to indicate that the Bean currently annotated with the @primary annotation will be injected first when more than one Bean meets the criteria:

@Component
@Primary
public class Wolf1Bean implements IWolf{}Copy the code

In this way, Spring will inject wolf1Bean by default, while we can still manually get the other implementation classes from the context, because the other implementation classes also exist in the container.

Several ways to get beans manually

In Spring projects, you need to manually get beans through the ApplicationContext object, which can be obtained in one of the following five ways:

Direct injection

The simplest way to do this is to get the ApplicationContext object directly by injecting it, and then get the Bean from the ApplicationContext object:

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

    public Object getBean(a){
        return applicationContext.getBean("wolf1Bean");/ / get a bean}}Copy the code
Obtained through the ApplicationContextAware interface

Get the Bean by implementing the ApplicationContextAware interface to get the ApplicationContext object. Note that classes that implement the ApplicationContextAware interface also need to be annotated so that they can be managed uniformly by Spring (which is one of the most common methods used in projects) :

@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); }}Copy the code

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

Wolf2Bean wolf2Bean = SpringContextUtil.getBeanByName("wolf2Bean");
Wolf3Bean wolf3Bean = SpringContextUtil.getBeanByType(Wolf3Bean.class);
Copy the code
Through ApplicationObjectSupport and WebApplicationObjectSupport access

Of the two objects, WebApplicationObjectSupport inherited ApplicationObjectSupport, so there is no substantial difference.

Similarly, the following utility class also needs annotations 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(a){
        applicationContext = super.getApplicationContext(); }}Copy the code

With utility classes, you can call directly from a method:

@RestController
@RequestMapping("/hello")
@Qualifier
public class HelloController {
    @GetMapping("/bean3")
    public Object getBean3(a){
        Wolf1Bean wolf1Bean = SpringUtil.getBean("wolf1Bean");
        returnwolf1Bean.toString(); }}Copy the code
Get it via HttpServletRequest

Through it, and then combined with the tool provided class WebApplicationContextUtils Spring also can obtain the ApplicationContext object, The HttpServletRequest object can be retrieved either actively (getBean2) or passively (getBean1) :

@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(a){
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();// Manually get the Request object
        ApplicationContext applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());

        Wolf2Bean wolf2Bean = (Wolf2Bean)applicationContext.getBean("wolf2Bean");
        returnwolf2Bean.toString(); }}Copy the code
Obtaining by other means

Of course, in addition to the methods mentioned above, we could also manually new an ApplicationContext using the DL code example originally mentioned, but this would mean a re-initialization, so this is not recommended, but it is appropriate when writing unit tests.

Talk about the differences between @Autowrite and @Resource and @Qualifier annotations

As we saw above, injecting a Bean can be done through @autowrite or @Resource annotations. What is the difference between the two annotations?

  • @Autowrite: injection by type, can be used for constructor and parameter injection. When we inject an interface, all of its implementation classes are of the same type, so there is no way to know which implementation class to choose for injection.
  • @Resource: Is injected by name by default and cannot be used for constructor and parameter injection. If you can’t find a unique by nameBean, will look up by type. The following can be specifiednameortypeTo determine the unique implementation:
@Resource(name = "wolf2Bean",type = Wolf2Bean.class)
 private IWolf iWolf;
Copy the code

The @qualifier annotation is used to identify the Qualifier. When used with @autowrite and @qualifier, it is equivalent to identifying uniqueness by name:

@Qualifier("wolf1Bean")
@Autowired
private IWolf iWolf;
Copy the code

One might say, “I could have just used @resource, why bother combining two annotations together?” it might seem like the @qualifier annotation is a bit redundant.

Are @qualifier annotations redundant

Let’s look at the following Bean declaration scenario, where a method is used to declare a Bean (MyElement), and the method parameter has a Wolf1Bean object, Spring will automatically inject Wolf1Bean for us:

@Component
public class InterfaceInject2 {
    @Bean
    public MyElement test(Wolf1Bean wolf1Bean){
        return newMyElement(); }}Copy the code

However, if we change the above code slightly and change the argument to an interface that has multiple implementation classes, we will get an error:

@Component
public class InterfaceInject2 {
    @Bean
    public MyElement test(IWolf iWolf){// The IWolf interface has multiple implementation classes, so an error is reported
        return newMyElement(); }}Copy the code

The @qualifier annotation cannot be used in parameters, so the @qualifier annotation is used to confirm the unique implementation (such as when configuring multiple data sources) :

@Component
public class InterfaceInject2 {
    @Bean
    public MyElement test(@Qualifier("wolf1Bean") IWolf iWolf){
        return newMyElement(); }}Copy the code

conclusion

This article describes how to use flexible methods to implement injection in various scenarios in Spring, and focuses on how to inject when an interface has more than one implementation class. Finally, it introduces the differences between several common injection annotations. I’m sure you’ll become more familiar with how to use dependency injection in Spring.