详解Java Spring各种依赖注入注解的区别
注解注入顾名思义就是通过注解来实现注入,spring和注入相关的常见注解有autowired、resource、qualifier、service、controller、repository、component。
autowired是自动注入,自动从spring的上下文找到合适的bean来注入
resource用来指定名称注入
qualifier和autowired配合使用,指定bean的名称
service,controller,repository分别标记类是service层类,controller层类,数据存储层的类,spring扫描注解配置时,会标记这些类要生成bean。
component是一种泛指,标记类是组件,spring扫描注解配置时,会标记这些类要生成bean。
spring对于bean的依赖注入,支持多种注解方式:
@resource javax.annotation jsr250 (common annotations for java) @inject javax.inject jsr330 (dependency injection for java) @autowired org.springframework.bean.factory spring
直观上看起来,@autowired是spring提供的注解,其他几个都是jdk本身内建的注解,spring对这些注解也进行了支持。但是使用起来这三者到底有什么区别呢?笔者经过方法的测试,发现一些有意思的特性。
区别总结如下:
一、@autowired有个required属性,可以配置为false,这种情况下如果没有找到对应的bean是不会抛异常的。@inject和@resource没有提供对应的配置,所以必须找到否则会抛异常。
二、 @autowired和@inject基本是一样的,因为两者都是使用autowiredannotationbeanpostprocessor来处理 依赖注入。但是@resource是个例外,它使用的是commonannotationbeanpostprocessor来处理依赖注入。当然,两者 都是beanpostprocessor。
@autowired和@inject - 默认 autowired by type - 可以 通过@qualifier 显式指定 autowired by qualifier name。 - 如果 autowired by type 失败(找不到或者找到多个实现),则退化为autowired by field name @resource - 默认 autowired by field name - 如果 autowired by field name失败,会退化为 autowired by type - 可以 通过@qualifier 显式指定 autowired by qualifier name - 如果 autowired by qualifier name失败,会退化为 autowired by field name。但是这时候如果 autowired by field name失败,就不会再退化为autowired by type了。
tips qualified name vs bean name
在spring设计中,qualified name并不等同于bean name,后者必须是唯一的,但是前者类似于tag或者group的作用,对特定的bean进行分类。可以达到getbytag(group)的效果。对 于xml配置的bean,可以通过id属性指定bean name(如果没有指定,默认使用类名首字母小写),通过标签指定qualifier name:
<bean id="lamborghini" class="me.arganzheng.study.spring.autowired.lamborghini"> <qualifier value="luxury"/> <!-- inject any dependencies required by this bean --> </bean>
如果是通过注解方式,那么可以通过@qualifier注解指定qualifier name,通过@named或者@component(@service,@repository等)的value值指定bean name:
@component("lamborghini") @qualifier("luxury") public class lamborghini implements car { }
或者
@component @named("lamborghini") @qualifier("luxury") public class lamborghini implements car { }
同样,如果没有指定bean name,那么spring会默认是用类名首字母小写(lamborghini=>lamborghini)。
三、 通过anotation注入依赖的方式在xml注入方式之前进行。如果对同一个bean的依赖同时使用了两种注入方式,那么xml的优先。但是不同担心通过anotation注入的依赖没法注入xml中配置的bean,依赖注入是在bean的注册之后进行的。
四、目前的autowired by type方式(笔者用的是3.2.3.release版本),spring的autowiredannotationbeanpostprocessor 实现都是有”bug”的,也就是说@autowired和@inject都是有坑的(称之为坑,不称之为bug是因为貌似是故意的。。)。这是来源于线上 的一个bug,也是这边文章的写作原因。现场如下:
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/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>
其中static-field应用的常量定义在如下类中:
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"; } }
然后如果我们在代码中如下声明依赖:
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,诡异的事情发生了!
运行结果如下:
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}
也就是说map
严重: 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
debug了一下,发现确实是spring的一个bug。在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 assignable 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; } ... }
关键在这一句:map
严重: 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 more
debug了一下,发现跟没有指定qualifie name是一样的执行路径。不是指定了bean name了吗?为什么还是autowired by type呢?仔细查看了一下才发现。defaultlistablebeanfactory的doresolvedependency方法对首先对类型做 了区别:
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 assignable 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(); } }
如果是array,collection或者map,则根据集合类中元素的类型来进行autowired by type(map使用value的类型)。为什么这么特殊处理呢?原来,spring是为了达到这样的目的:让你可以一次注入所有符合类型的实现,也就是 说可以这样子注入:
@autowired
private list<car> cars;
如果你的car有多个实现,那么都会注入进来,不会再报
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].
然而,上面的情况如果你用@resource则不会有这个问题:
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); } }
正常运行:
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}
当然,你如果不指定@qualifier(“languagechangesmap”),同时field name不是languagechangesmap,那么还是一样报错的。
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 more
而且,@resource也可以实现上面的list接收所有实现:
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); } }
运行的妥妥的:
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]
这是因为@resource注解使用的是commonannotationbeanpostprocessor处理器,跟 autowiredannotationbeanpostprocessor不是同一个作者[/偷笑]。这里就不分析了,感兴趣的同学可以自己看代码研究 一下。
最终结论如下:
1、@autowired和@inject
autowired by type 可以 通过@qualifier 显式指定 autowired by qualifier name(非集合类。注意:不是autowired by bean name!)
如果 autowired by type 失败(找不到或者找到多个实现),则退化为autowired by field name(非集合类)
2、@resource
默认 autowired by field name
如果 autowired by field name失败,会退化为 autowired by type
可以 通过@qualifier 显式指定 autowired by qualifier name
如果 autowired by qualifier name失败,会退化为 autowired by field name。但是这时候如果 autowired by field name失败,就不会再退化为autowired by type了
测试工程保存在github上,是标准的maven工程,感兴趣的同学可以clone到本地运行测试一下。
补充
有同事指出spring官方文档上有这么一句话跟我的结有点冲突:
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.
也就是说@autowired即使加了@qualifier注解,其实也是autowired by type。@qualifier只是一个限定词,过滤条件而已。重新跟进了一下代码,发现确实是这样子的。spring设计的这个 @qualifier name 并不等同于 bean name。他有点类似于一个tag。不过如果这个tag是唯一的化,那么其实效果上等同于bean name。实现上,spring是先getbytype,得到list candicates,然后再根据qualifier name进行过滤。
再定义一个兰博基尼,这里使用@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 { }
再定义一个劳斯莱斯,这里故意用@named指定:
package me.arganzheng.study.spring.autowired; import javax.inject.named; import org.springframework.stereotype.component; @component @named("luxury") public class rollsroyce implements 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); } }
运行结果如下:
arraylist [me.arganzheng.study.spring.autowired.lamborghini@66b875e1, me.arganzheng.study.spring.autowired.rollsroyce@58433b76]
补充:autowiring modes
spring支持四种autowire模式,当使用xml配置方式时,你可以通过autowire属性指定。
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 gives 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 applies to constructor arguments. if there is not exactly one bean of the constructor argument type in the container, a fatal error is raised.
如果使用@autowired、@inject或者@resource注解的时候,则稍微复杂一些,会有一个失败退化过程,并且引入了qualifier。不过基本原理是一样。
上一篇: 常见的java面试题
下一篇: JavaMail入门教程之接收邮件(4)