As the name suggests, annotation injection is to realize injection through annotations. Common annotations related to Spring and injection include Autowired, Resource, Qualifier, Service, Controller, Repository, and Component.
Autowired is automatic injection, automatically finds the appropriate bean from the context of spring to inject
Resource is used to specify name injection
Qualifier and Autowired work together to specify the name of the bean
Service, Controller, and Repository respectively mark the class as Service layer class, Controller layer class, and data storage layer class. When spring scan annotation configuration, these classes will be marked to generate beans.
Component is a general term. Marking classes are components. When spring scans and annotations configuration, these classes will be marked to generate beans.
Spring supports multiple annotation methods for dependency injection of beans:
@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, and the others are built-in annotations by JDK itself, and Spring also supports these annotations. But what is the difference between these three when used? After testing the method, the author found some interesting features.
The difference is summarized as follows:
1. @Autowired has a required property, which can be configured as false. In this case, if the corresponding bean is not found, an exception will not be thrown. @Inject and @Resource do not provide corresponding configurations, so you must find them otherwise an exception will be thrown.
2. @Autowired and @Inject are basically the same, because both use AutowiredAnnotationBeanPostProcessor to handle dependency injection. But @Resource is an exception, which uses CommonAnnotationBeanPostProcessor to handle dependency injection. Of course, both are BeanPostProcessors.
@Autowired and @Inject - Default autowired by type - Autowired by qualifier name can be explicitly specified via @Qualifier. - If autowired by type fails (not found or multiple implementations are found), degenerates to autowired by field name @Resource - Default autowired by field name - If autowired by field name fails, degenerates to autowired by type - Autowired by qualification name can be explicitly specified via @Qualifier - If autowired by qualification name fails, degenerates to autowired by field name. But at this time if the autowired by field name fails, it will no longer degenerate into autowired by type.
TIPS Qualified name VS Bean name
In Spring design, Qualified name is not equivalent to Bean name, the latter must be unique, but the former is similar to the function of a tag or group, classifying specific beans. It can achieve the effect of getByTag(group). For XML-configured beans, you can specify the bean name through the id attribute (if not specified, the class name is lowercase by default), and the qualification name is specified through the tag:
<bean id="lamborghini"> <qualifier value="luxury"/> <!-- inject any dependencies required by this bean --> </bean>
If it is through annotation, you can specify the qualification name through the @Qualifier annotation, and specify the bean name through the 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 the bean name is not specified, Spring will default to lowercase in the class name (Lamborghini=>lamborghini).
3. Use the Anotation injection dependency method to perform before the XML injection method. If two injection methods are used at the same time on the dependency on the same bean, then XML is preferred. However, I am not worried that the dependencies injected through Anotation cannot inject beans configured in XML. Dependency injection is performed after the bean is registered.
4. The current autowired by type method (the author uses the 3.2.3.RELEASE version), Spring's AutowiredAnnotationBeanPostProcessor implementation has "bugs", which means that @Autowired and @Inject have pitfalls (called pits, not bugs because they seem to be intentional...). This is a bug that comes from online and is also the reason for writing articles here. The scene is as follows:
There are the following definitions in application-context.xml:
<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 http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/context/spring-context-3.0.xsd 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 happened!
The operation 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}That is to say, 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 moreI debugged it and found that it was indeed a bug in Spring. There is a problem with this method in DefaultListableBeanFactory:
protected Object doResolveDependency(DependencyDescriptor descriptor, Class<?> type, String beanName, Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException { ... else if (Map.class.isAssignableFrom(type) && type.isInterface()) { Class<?> keyType = descriptor.getMapKeyType(); if (keyType == null || !String.class.isAssignableFrom(keyType)) { if (descriptor.isRequired()) { throw new FatalBeanException("Key type [" + keyType + "] of map [" + type.getName() + "] must be assigned to [java.lang.String]"); } return null; } Class<?> valueType = descriptor.getMapValueType(); if (valueType == null) { if (descriptor.isRequired()) { throw new FatalBeanException("No value type declared for map [" + type.getName() + "]"); } return null; } Map<String, Object> matchingBeans = findAutowireCandidates(beanName, valueType, descriptor); if (matchingBeans.isEmpty()) { if (descriptor.isRequired()) { raiseNoSuchBeanDefinitionException(valueType, "map with value type " + valueType.getName(), descriptor); } return null; } if (autowiredBeanNames != null) { autowiredBeanNames.addAll(matchingBeans.keySet()); } return matchingBeans; } ... }The key is this sentence: Map
Serious: Caught exception while allowing TestExecutionListener
[org.springframework.test.context.support.DependencyInjectionTestExecutionListener@9476189] to prepare test instance [me.arganzheng.study.spring.autowired.AutowiredTest@2d546e21] ... Caused 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), @org.springframework.beans.factory.annotation.Qualifier(value=languageChangesMap)} 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 moreAfter debugging, I found that the execution path is the same as the qualfie name not specified. Didn’t you specify the bean name? Why is it still autowired by type? After a closer look, I found out. The doResolveDependency method of DefaultListableBeanFactory makes the difference between types first:
protected Object doResolveDependency(DependencyDescriptor descriptor, Class<?> type, String beanName, Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException { Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor); if (value != null) { if (value instanceof String) { String strVal = resolveEmbeddedValue((String) value); BeanDefinition bd = (beanName != null && containsBean(beanName) ? getMergedBeanDefinition(beanName) : null); value = evaluateBeanDefinitionString(strVal, bd); } TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter()); return (descriptor.getField() != null ? converter.convertIfNecessary(value, type, descriptor.getField()) : converter.convertIfNecessary(value, type, descriptor.getMethodParameter())); } if (type.isArray()) { Class<?> componentType = type.getComponentType(); Map<String, Object> matchingBeans = findAutowireCandidates(beanName, componentType, descriptor); if (matchingBeans.isEmpty()) { if (descriptor.isRequired()) { raiseNoSuchBeanDefinitionException(componentType, "array of " + componentType.getName(), descriptor); } return null; } if (autowiredBeanNames != null) { autowiredBeanNames.addAll(matchingBeans.keySet()); } TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter()); return converter.convertIfNecessary(matchingBeans.values(), type); } else if (Collection.class.isAssignableFrom(type) && type.isInterface()) { Class<?> elementType = descriptor.getCollectionType(); if (elementType == null) { if (descriptor.isRequired()) { throw new FatalBeanException("No element type declared for collection [" + type.getName() + "]"); } return null; } Map<String, Object> matchingBeans = findAutowireCandidates(beanName, elementType, descriptor); if (matchingBeans.isEmpty()) { if (descriptor.isRequired()) { raiseNoSuchBeanDefinitionException(elementType, "collection of " + elementType.getName(), descriptor); } return null; } if (autowiredBeanNames != null) { autowiredBeanNames.addAll(matchingBeans.keySet()); } TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter()); return converter.convertIfNecessary(matchingBeans.values(), type); } else if (Map.class.isAssignableFrom(type) && type.isInterface()) { Class<?> keyType = descriptor.getMapKeyType(); if (keyType == null || !String.class.isAssignableFrom(keyType)) { if (descriptor.isRequired()) { throw new FatalBeanException("Key type [" + keyType + "] of map [" + type.getName() + "] must be assigned to [java.lang.String]"); } return null; } Class<?> valueType = descriptor.getMapValueType(); if (valueType == null) { if (descriptor.isRequired()) { throw new FatalBeanException("No value type declared for map [" + type.getName() + "]"); } return null; } Map<String, Object> matchingBeans = findAutowireCandidates(beanName, valueType, descriptor); if (matchingBeans.isEmpty()) { if (descriptor.isRequired()) { raiseNoSuchBeanDefinitionException(valueType, "map with value type " + valueType.getName(), descriptor); } return null; } if (autowiredBeanNames != null) { autowiredBeanNames.addAll(matchingBeans.keySet()); } return matchingBeans; } else { Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor); if (matchingBeans.isEmpty()) { if (descriptor.isRequired()) { raiseNoSuchBeanDefinitionException(type, "", descriptor); } return null; } if (matchingBeans.size() > 1) { String primaryBeanName = determinePrimaryCandidate(matchingBeans, descriptor); if (primaryBeanName == null) { throw new NoUniqueBeanDefinitionException(type, matchingBeans.keySet()); } if (autowiredBeanNames != null) { autowiredBeanNames.add(primaryBeanName); } return matchingBeans.get(primaryBeanName); } // We have exactly one match. Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next(); if (autowiredBeanNames != null) { autowiredBeanNames.add(entry.getKey()); } return entry.getValue(); } }If it is an Array, Collection or Map, autowired by type according to the type of the element in the collection class (the type of the Map uses value). Why is it so special? It turns out that Spring is for this purpose: allowing you to inject all implementations that match types at once, that is, you can inject them like this:
@Autowired
private List<Car> cars;
If your car has multiple implementations, it will be injected and will not be reported again
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 case, if you use @Resource, you won't have this problem:
public class AutowiredTest extends BaseSpringTestCase { @Resource @Qualifier("languageChangesMap") private Map<String, String> languageChangesMap; @Test public void testAutowired() { assertNotNull(languageChangesMap); System.out.println(languageChangesMap.getClass().getSimpleName()); System.out.println(languageChangesMap); } }Normal operation:
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} Of course, if you do not specify @Qualifier("languageChangesMap") and the field name is not languageChangesMap, then the same error will still be reported.
Caused 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: {@javax.annotation.Resource(shareable=true, mappedName=, description=, name=, type=class java.lang.Object, authenticationType=CONTAINER, lookup=)} 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.context.annotation.CommonAnnotationBeanPostProcessor.autowireResource(CommonAnnotationBeanPostProcessor.java:438) at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.getResource(CommonAnnotationBeanPostProcessor.java:416) at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor$ResourceElement.getResourceToInject(CommonAnnotationBeanPostProcessor.java:550) at org.springframework.beans.factory.annotation.InjectionMetadata$InjectedElement.inject(InjectionMetadata.java:150) at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87) at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessPropertyValues(CommonAnnotationBeanPostProcessor.java:303) ... 26 moreMoreover, @Resource can also implement the List above to receive all implementations:
public class AutowiredTest extends BaseSpringTestCase { @Resource @Qualifier("languageChangesMap") private Map<String, String> languageChangesMap; @Resource private List<Car> cars; @Test public void testAutowired() { assertNotNull(languageChangesMap); System.out.println(languageChangesMap.getClass().getSimpleName()); System.out.println(languageChangesMap); assertNotNull(cars); System.out.println(cars.getClass().getSimpleName()); System.out.println(cars); } }Running correctly:
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, which is not the same author as AutowiredAnnotationBeanPostProcessor[/Smile]. I won’t analyze it here. Interested students can read the code and study it yourself.
The final conclusion is as follows :
1. @Autowired and @Inject
autowired by type can be explicitly specified by @Qualifier (non-collection class. Note: not autowired by bean name!)
If autowired by type fails (not found or multiple implementations are found), degenerates to autowired by field name (non-collection class)
2. @Resource
Default autowired by field name
If the autowired by field name fails, it will degenerate into autowired by type
Autowired by qualification name can be explicitly specified via @Qualifier
If the autowired by qualification name fails, it will degenerate into autowired by field name. However, if the autowired by field name fails, it will no longer degenerate into autowired by type. The test project is saved on GitHub. It is a standard maven project. Interested students can clone it locally to run the test.
Replenish
A colleague pointed out that there is a sentence in Spring's official document that conflicts with my relationship:
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 qualification 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.
In other words, even if @Autowired is added with @Qualifier annotation, it is actually autowired by type. @Qualifier is just a qualifier, just a filter condition. I followed up with the code and found that this is indeed the case. This @Qualifier name designed by Spring is not equivalent to bean name. He is a bit similar to a tag. However, if this tag is unique, then the effect is actually equivalent to bean name. In terms of implementation, Spring first getByType, get list candidates, and then filter according to the qualifier name.
Define another Lamborghini, here is specified using @Qualifier:
package me.arganzheng.study.spring.autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; @Component @Qualifier("luxury") public class Lamborghini implements Car { }Define another Rolls-Royce, here it is deliberately specified with @Named:
package me.arganzheng.study.spring.autowired; import javax.inject.Named; import org.springframework.stereotype.Component; @Component @Named("luxury") public class RollsRoyce implements Car { } Test the luxury car with injected definition:
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 operation 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 autowire modes. When using XML configuration methods, you can specify them through the autowire attribute.
no. (Default) No autowiring. Bean references must be defined via a ref element. Changing the default setting is not recommended for larger deployments, because specifying collaborators explicitly give greater control and clarity. To some extent, it documents the structure of a system. byName. Autowiring by property name. Spring looks for a bean with the same name as the property that needs to be autowired. For example, If a bean definition is set to autowire by name, and it contains a master property (that is, it has a setMaster(..) method), Spring looks for a bean definition named master, and uses it to set the property. byType. Allows a property to be autowired if exactly one bean of the property type exists in the container. If more than one exists, a fatal exception is thrown, which indicates that you may not use byType autowiring for that bean. If there are no matching beans, nothing happens; the property is not set. constructor. Analogous to byType, but apply to constructor arguments. If there is not exactly one bean of the constructor argument type in the container, a fatal error is raised.
If you use @Autowired, @Inject or @Resource annotations, it will be a little more complicated, and there will be a failed degradation process, and a Qualifier is introduced. But the basic principle is the same.