Detail the differences between the various dependency injection annotations on Java Spring

  • 2020-05-09 18:31:21
  • OfStack

Annotation injection, as the name implies, is implemented through annotations. Common annotations of Spring and injection are Autowired, Resource, Qualifier, Service, Controller, Repository, Component.

Autowired is automatic injection, automatically finding the appropriate bean from the context of spring to inject

Resource is used to specify name injection

Qualifier and Autowired are used together to specify the name of bean
The Service, Controller, Repository tag classes are Service layer classes, Controller layer classes, and data storage layer classes, respectively. When spring scans the annotation configuration, it tags these classes to generate bean.

Component is a generic term, tag classes are components, and when spring scans the annotation configuration, it tags these classes to generate bean.

Spring's dependency injection for Bean supports multiple annotation methods:


@Resource 
javax.annotation 
JSR250 (Common Annotations for Java) 
@Inject 
javax.inject 
JSR330 (Dependency Injection for Java) 
@Autowired 
org.springframework.bean.factory 
Spring 

Intuitively, @Autowired is an annotation provided by Spring, while the others are built-in annotations from JDK itself, which is also supported by Spring. But what's the difference between the three? The author through the method of testing, found 1 some interesting characteristics.

The differences are summarized as follows:

1. @Autowired has an required property that can be configured to false, in which case no exception will be thrown if the corresponding bean is not found. @Inject and @Resource do not provide a corresponding configuration, so you must find it or throw an exception.

2. @Autowired and @Inject are basically 1, because both use AutowiredAnnotationBeanPostProcessor for dependency injection. The exception is @Resource, which USES CommonAnnotationBeanPostProcessor for dependency injection. Of course, both are BeanPostProcessor.


@Autowired and @Inject 
-  The default  autowired by type 
-  can   through @Qualifier  Explicitly specify  autowired by qualifier name .  
-  if  autowired by type  Failure (can't find or multiple implementations are found) degrades to autowired by field name 
@Resource 
-  The default  autowired by field name 
-  if  autowired by field name Failure will degenerate into  autowired by type 
-  can   through @Qualifier  Explicitly specify  autowired by qualifier name 
-  if  autowired by qualifier name Failure will degenerate into  autowired by field name . But at this point if  autowired by field name Failure, will not degenerate into autowired by type .  

TIPS Qualified name VS Bean name

In the Spring design, Qualified name is not equivalent to Bean name, which must be one-only, but the former is similar to tag or group in that it classifies specific bean. It can achieve the effect of getByTag(group). For bean configured with XML, you can specify bean name via the id property (if not specified, the class name is lowercase by default) and qualifier name via the label:


<bean id="lamborghini" class="me.arganzheng.study.spring.autowired.Lamborghini"> 
<qualifier value="luxury"/> 
<!-- inject any dependencies required by this bean --> 
</bean>

If it is annotated, qualifier name can be specified by the @Qualifier annotation, bean name by the value value of @Named or @Component (@Service, @Repository, etc.) :


@Component("lamborghini") 
@Qualifier("luxury") 
public class Lamborghini implements Car { 
} 

or


@Component 
@Named("lamborghini") 
@Qualifier("luxury") 
public class Lamborghini implements Car { 
} 

Similarly, if bean name is not specified, then Spring defaults to lowercase with the first letter of the class name (Lamborghini=) > lamborghini).

3. Inject dependencies via Anotation before XML. If both injection methods are used for the same bean dependency, then XML takes precedence. However, there are different concerns that the dependencies injected through Anotation will not be able to be injected into bean configured in XML after the registration of bean.

4. In the current autowired by type way (the author used the 3.2.3.RELEASE version), Spring AutowiredAnnotationBeanPostProcessor implementation has "bug", that is to say, @Autowired and @Inject have pits (called pits, not bug because it seems to be intentional). . This is an bug from online, which is also the reason for writing this article. The scene is as follows:

application-context.xml has the following definition:


<xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" 
xmlns:context="http://www.springframework.org/schema/context" 
xmlns:util="http://www.springframework.org/schema/util" 
xsi:schemaLocation=" 
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd 
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd 
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd"> 
<context:annotation-config /> 
<context:component-scan base-package="me.arganzheng.study" /> 
<util:constant id="en" 
static-field="me.arganzheng.study.spring.autowired.Constants.Language.EN" /> 
<util:constant id="ja" 
static-field="me.arganzheng.study.spring.autowired.Constants.Language.JP" /> 
<util:constant id="ind" 
static-field="me.arganzheng.study.spring.autowired.Constants.Language.IND" /> 
<util:constant id="pt" 
static-field="me.arganzheng.study.spring.autowired.Constants.Language.PT" /> 
<util:constant id="th" 
static-field="me.arganzheng.study.spring.autowired.Constants.Language.TH" /> 
<util:constant id="ar" 
static-field="me.arganzheng.study.spring.autowired.Constants.Language.AR" /> 
<util:constant id="en-rIn" 
static-field="me.arganzheng.study.spring.autowired.Constants.Language.EN_RIN" /> 
<util:map id="languageChangesMap" key-type="java.lang.String" 
value-type="java.lang.String"> 
<entry key="pt" value="pt" /> 
<entry key="br" value="pt" /> 
<entry key="jp" value="ja" /> 
<entry key="ja" value="ja" /> 
<entry key="ind" value="ind" /> 
<entry key="id" value="ind" /> 
<entry key="en-rin" value="en-rIn" /> 
<entry key="in" value="en-rIn" /> 
<entry key="en" value="en" /> 
<entry key="gb" value="en" /> 
<entry key="th" value="th" /> 
<entry key="ar" value="ar" /> 
<entry key="eg" value="ar" /> 
</util:map> 
</beans> 

The constants applied by static-field are defined in the following class:


package me.arganzheng.study.spring.autowired; 
public interface Constants { 
public interface Language { 
public static final String EN = "CommonConstants.LANG_ENGLISH"; 
public static final String JP = "CommonConstants.LANG_JAPANESE"; 
public static final String IND = "CommonConstants.LANG_INDONESIAN"; 
public static final String PT = "CommonConstants.LANG_PORTUGUESE"; 
public static final String TH = "CommonConstants.LANG_THAI"; 
public static final String EN_RIN = "CommonConstants.LANG_ENGLISH_INDIA"; 
public static final String AR = "CommonConstants.LANG_Arabic"; 
} 
} 

Then if we declare the dependency in the code as follows:


public class AutowiredTest extends BaseSpringTestCase { 
@Autowired 
private Map<String, String> languageChangesMap; 
@Test 
public void testAutowired() { 
notNull(languageChangesMap); 
System.out.println(languageChangesMap.getClass().getSimpleName()); 
System.out.println(languageChangesMap); 
} 
} 

Guess what, something weird has happened!

The results are as follows:


LinkedHashMap 
{en=CommonConstants.LANG_ENGLISH, ja=CommonConstants.LANG_JAPANESE, ind=CommonConstants.LANG_INDONESIAN, pt=CommonConstants.LANG_PORTUGUESE, th=CommonConstants.LANG_THAI, ar=CommonConstants.LANG_Arabic, en-rIn=CommonConstants.LANG_ENGLISH_INDIA} 

So Map

Serious: Caught exception while allowing TestExecutionListener


[org.springframework.test.context.support.DependencyInjectionTestExecutionListener@5c51ee0a] to prepare test instance [me.arganzheng.study.spring.autowired.AutowiredTest@6e301e0] 
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'me.arganzheng.study.spring.autowired.AutowiredTest': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private java.util.Map me.arganzheng.study.spring.autowired.AutowiredTest.languageChangesMap; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [java.lang.String] found for dependency [map with value type java.lang.String]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)} 
... 
ed by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [java.lang.String] found for dependency [map with value type java.lang.String]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)} 
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:986) 
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:843) 
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:768) 
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:486) 
... 28 more 

So debug goes down 1, and it turns out it's actually an bug of Spring. There is something wrong with this method in DefaultListableBeanFactory:


@Autowired and @Inject 
-  The default  autowired by type 
-  can   through @Qualifier  Explicitly specify  autowired by qualifier name .  
-  if  autowired by type  Failure (can't find or multiple implementations are found) degrades to autowired by field name 
@Resource 
-  The default  autowired by field name 
-  if  autowired by field name Failure will degenerate into  autowired by type 
-  can   through @Qualifier  Explicitly specify  autowired by qualifier name 
-  if  autowired by qualifier name Failure will degenerate into  autowired by field name . But at this point if  autowired by field name Failure, will not degenerate into autowired by type .  
0

The key is in this sentence: Map

Serious: Caught exception while allowing TestExecutionListener


@Autowired and @Inject 
-  The default  autowired by type 
-  can   through @Qualifier  Explicitly specify  autowired by qualifier name .  
-  if  autowired by type  Failure (can't find or multiple implementations are found) degrades to autowired by field name 
@Resource 
-  The default  autowired by field name 
-  if  autowired by field name Failure will degenerate into  autowired by type 
-  can   through @Qualifier  Explicitly specify  autowired by qualifier name 
-  if  autowired by qualifier name Failure will degenerate into  autowired by field name . But at this point if  autowired by field name Failure, will not degenerate into autowired by type .  
1

debug 1, found that qualifie name is not specified as 1. Didn't it specify bean name? Why autowired by type? It took me a second to find out. DefaultListableBeanFactory's doResolveDependency method makes a distinction between types:


@Autowired and @Inject 
-  The default  autowired by type 
-  can   through @Qualifier  Explicitly specify  autowired by qualifier name .  
-  if  autowired by type  Failure (can't find or multiple implementations are found) degrades to autowired by field name 
@Resource 
-  The default  autowired by field name 
-  if  autowired by field name Failure will degenerate into  autowired by type 
-  can   through @Qualifier  Explicitly specify  autowired by qualifier name 
-  if  autowired by qualifier name Failure will degenerate into  autowired by field name . But at this point if  autowired by field name Failure, will not degenerate into autowired by type .  
2

If it is Array, Collection, or Map, autowired by type (Map USES the value type) according to the type of the element in the collection class. Why this special treatment? Originally, Spring was designed to allow you to inject all type-compliant implementations at once, which means you can do it like this:

@Autowired
private List < Car > cars;

If you have multiple implementations of car, they will all be injected and will not be reported


org.springframework.beans.factory.NoSuchBeanDefinitionException: 
No unique bean of type [me.arganzheng.study.spring.autowired.Car] is defined: 
expected single matching bean but found 2: [audi, toyota]. 

However, in the above situation if you use @Resource you won't have this problem:


@Autowired and @Inject 
-  The default  autowired by type 
-  can   through @Qualifier  Explicitly specify  autowired by qualifier name .  
-  if  autowired by type  Failure (can't find or multiple implementations are found) degrades to autowired by field name 
@Resource 
-  The default  autowired by field name 
-  if  autowired by field name Failure will degenerate into  autowired by type 
-  can   through @Qualifier  Explicitly specify  autowired by qualifier name 
-  if  autowired by qualifier name Failure will degenerate into  autowired by field name . But at this point if  autowired by field name Failure, will not degenerate into autowired by type .  
4

Normal operation:


@Autowired and @Inject 
-  The default  autowired by type 
-  can   through @Qualifier  Explicitly specify  autowired by qualifier name .  
-  if  autowired by type  Failure (can't find or multiple implementations are found) degrades to autowired by field name 
@Resource 
-  The default  autowired by field name 
-  if  autowired by field name Failure will degenerate into  autowired by type 
-  can   through @Qualifier  Explicitly specify  autowired by qualifier name 
-  if  autowired by qualifier name Failure will degenerate into  autowired by field name . But at this point if  autowired by field name Failure, will not degenerate into autowired by type .  
5

Of course, if you do not specify @Qualifier (" languageChangesMap "), and field name is not languageChangesMap, then the sample is still incorrect.


@Autowired and @Inject 
-  The default  autowired by type 
-  can   through @Qualifier  Explicitly specify  autowired by qualifier name .  
-  if  autowired by type  Failure (can't find or multiple implementations are found) degrades to autowired by field name 
@Resource 
-  The default  autowired by field name 
-  if  autowired by field name Failure will degenerate into  autowired by type 
-  can   through @Qualifier  Explicitly specify  autowired by qualifier name 
-  if  autowired by qualifier name Failure will degenerate into  autowired by field name . But at this point if  autowired by field name Failure, will not degenerate into autowired by type .  
6

Also, @Resource can implement List above to receive all implementations:


@Autowired and @Inject 
-  The default  autowired by type 
-  can   through @Qualifier  Explicitly specify  autowired by qualifier name .  
-  if  autowired by type  Failure (can't find or multiple implementations are found) degrades to autowired by field name 
@Resource 
-  The default  autowired by field name 
-  if  autowired by field name Failure will degenerate into  autowired by type 
-  can   through @Qualifier  Explicitly specify  autowired by qualifier name 
-  if  autowired by qualifier name Failure will degenerate into  autowired by field name . But at this point if  autowired by field name Failure, will not degenerate into autowired by type .  
7

It works fine:


LinkedHashMap 
{pt=pt, br=pt, jp=ja, ja=ja, ind=ind, id=ind, en-rin=en-rIn, in=en-rIn, en=en, gb=en, th=th, ar=ar, eg=ar} 
ArrayList 

[me.arganzheng.study.spring.autowired.Audi@579584da, me.arganzheng.study.spring.autowired.Toyota@19453122]
This is because the @Resource annotation USES the CommonAnnotationBeanPostProcessor processor and is not the same author as AutowiredAnnotationBeanPostProcessor. I'm not going to do the analysis here, but if you're interested, you can look at the code study 1.

The final conclusion is as follows:

1. @Autowired and @Inject

autowired by type can be explicitly specified by @Qualifier autowired by qualifier name (non-collection class). Note: not autowired by bean name!)

If autowired by type fails (can't find or multiple implementations are found), it degrades to autowired by field name (non-collection class)

2, @ Resource

The default is autowired by field name
If autowired by field name fails, it will degenerate to autowired by type
You can explicitly specify autowired by qualifier name via @Qualifier
If autowired by qualifier name fails, it will degenerate to autowired by field name. But if the autowired by field name fails, it will no longer degenerate to autowired by type
The test project is saved on GitHub, which is a standard maven project. Students who are interested in clone can run test 1 locally.

supplement

Some colleagues pointed out that there is a sentence on the official document of Spring which conflicts with my conclusion:

However, although you can use this convention to refer to specific beans by name, @Autowired is fundamentally about type-driven injection with optional semantic qualifiers. This means that qualifier values, even with the bean name fallback, always have narrowing semantics within the set of type matches; they do not semantically express a reference to a unique bean id.

So @Autowired, even with the @Qualifier annotation, is also autowired by type. Qualifier is just a qualifier, filter condition. I re-followed the code for 1 and found that it did look like this. Spring designed this @Qualifier name which is not the same as bean name. It's kind of like an tag. But if this tag is a 1-only version, then it's actually the same as bean name. In the implementation, Spring is first getByType, then list candicates, and then filter according to qualifier name.

Define 1 more lamborghini. Use @Qualifier to specify:


@Autowired and @Inject 
-  The default  autowired by type 
-  can   through @Qualifier  Explicitly specify  autowired by qualifier name .  
-  if  autowired by type  Failure (can't find or multiple implementations are found) degrades to autowired by field name 
@Resource 
-  The default  autowired by field name 
-  if  autowired by field name Failure will degenerate into  autowired by type 
-  can   through @Qualifier  Explicitly specify  autowired by qualifier name 
-  if  autowired by qualifier name Failure will degenerate into  autowired by field name . But at this point if  autowired by field name Failure, will not degenerate into autowired by type .  
9

Define 1 more Rolls-Royce, which is specified here with @Named on purpose:


package me.arganzheng.study.spring.autowired; 
import javax.inject.Named; 
import org.springframework.stereotype.Component; 
@Component 
@Named("luxury") 
public class RollsRoyce implements Car { 
} 

Test 1 below injection definition of luxury car:


package me.arganzheng.study.spring.autowired; 
import static junit.framework.Assert.assertNotNull; 
import java.util.List; 
import me.arganzheng.study.BaseSpringTestCase; 
import org.junit.Test; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.beans.factory.annotation.Qualifier; 
/** 
* 
* @author zhengzhibin 
* 
*/ 
public class AutowiredTest extends BaseSpringTestCase { 
@Autowired 
@Qualifier("luxury") 
private List<Car> luxuryCars; 
@Test 
public void testAutowired() { 
assertNotNull(luxuryCars); 
System.out.println(luxuryCars.getClass().getSimpleName()); 
System.out.println(luxuryCars); 
} 
}

The results are as follows:


ArrayList 
[me.arganzheng.study.spring.autowired.Lamborghini@66b875e1, me.arganzheng.study.spring.autowired.RollsRoyce@58433b76] 

Supplement: Autowiring modes

Spring supports four modes of autowire, which you can specify by the autowire property when using the XML configuration.


<bean id="lamborghini" class="me.arganzheng.study.spring.autowired.Lamborghini"> 
<qualifier value="luxury"/> 
<!-- inject any dependencies required by this bean --> 
</bean>
3

If you use the @Autowired, @Inject, or @Resource annotations, it's a little more complicated 1, with a failed regression process and the introduction of Qualifier. But the basic principle is one.


Related articles: