Spring中的注入方式 和使用的注解 详解
注解:http://www.cnblogs.com/liangxiaofeng/p/6390868.html
注入方式:http://www.cnblogs.com/java-class/p/4727775.html
、、、、、、、、、、、、、、、、
Spring 依赖注入方式详解
平常的Java开发中,程序员在某个类中需要依赖其它类的方法。
通常是new一个依赖类再调用类实例的方法,这种开发存在的问题是new的类实例不好统一管理。
Spring提出了依赖注入的思想,即依赖类不由程序员实例化,而是通过Spring容器帮我们new指定实例并且将实例注入到需要该对象的类中。
依赖注入的另一种说法是"控制反转"。通俗的理解是:平常我们new一个实例,这个实例的控制权是我们程序员。
而控制反转是指new实例工作不由我们程序员来做而是交给Spring容器来做。
Spring有多种依赖注入的形式,本篇文章仅介绍Spring通过xml进行IOC配置的方式。
1.Set注入
这是最简单的注入方式,假设有一个SpringAction,类中需要实例化一个SpringDao对象,那么就可以定义一个private的SpringDao成员变量,然后创建SpringDao的set方法(这是ioc的注入入口):
package com.bless.springdemo.action;
public class SpringAction {
//注入对象springDao
private SpringDao springDao;
//一定要写被注入对象的set方法
public void setSpringDao(SpringDao springDao) {
this.springDao = springDao;
} public void ok(){
springDao.ok();
}
}
随后编写spring的xml文件,<bean>中的name属性是class属性的一个别名,class属性指类的全名,因为在SpringAction中有一个公共属性Springdao,所以要在<bean>标签中创建一个<property>标签指定SpringDao。<property>标签中的name就是SpringAction类中的SpringDao属性名,ref指下面<bean name="springDao"...>,这样其实是spring将SpringDaoImpl对象实例化并且调用SpringAction的setSpringDao方法将SpringDao注入:
<!--配置bean,配置后该类由spring管理-->
<bean name="springAction" class="com.bless.springdemo.action.SpringAction">
<!--(1)依赖注入,配置当前类中相应的属性-->
<property name="springDao" ref="springDao"></property>
</bean>
<bean name="springDao" class="com.bless.springdemo.dao.impl.SpringDaoImpl"></bean>
2.构造器注入
这种方式的注入是指带有参数的构造函数注入,看下面的例子,我创建了两个成员变量SpringDao和User,但是并未设置对象的set方法,所以就不能支持第一种注入方式,这里的注入方式是在SpringAction的构造函数中注入,也就是说在创建SpringAction对象时要将SpringDao和User两个参数值传进来:
public class SpringAction {
//注入对象springDao
private SpringDao springDao;
private User user; public SpringAction(SpringDao springDao,User user){
this.springDao = springDao;
this.user = user;
System.out.println("构造方法调用springDao和user");
} public void save(){
user.setName("卡卡");
springDao.save(user);
}
}
在XML文件中同样不用<property>的形式,而是使用<constructor-arg>标签,ref属性同样指向其它<bean>标签的name属性:
<!--配置bean,配置后该类由spring管理-->
<bean name="springAction" class="com.bless.springdemo.action.SpringAction">
<!--(2)创建构造器注入,如果主类有带参的构造方法则需添加此配置-->
<constructor-arg ref="springDao"></constructor-arg>
<constructor-arg ref="user"></constructor-arg>
</bean>
<bean name="springDao" class="com.bless.springdemo.dao.impl.SpringDaoImpl"></bean>
<bean name="user" class="com.bless.springdemo.vo.User"></bean>
解决构造方法参数的不确定性,你可能会遇到构造方法传入的两参数都是同类型的,为了分清哪个该赋对应值,则需要进行一些小处理:
下面是设置index,就是参数位置:
<bean name="springAction" class="com.bless.springdemo.action.SpringAction">
<constructor-arg index="0" ref="springDao"></constructor-arg>
<constructor-arg index="1" ref="user"></constructor-arg>
</bean>
另一种是设置参数类型:
<constructor-arg type="java.lang.String" ref=""/>
3.静态工厂的方法注入
静态工厂顾名思义,就是通过调用静态工厂的方法来获取自己需要的对象,为了让spring管理所有对象,我们不能直接通过"工程类.静态方法()"来获取对象,而是依然通过spring注入的形式获取:
package com.bless.springdemo.factory; import com.bless.springdemo.dao.FactoryDao;
import com.bless.springdemo.dao.impl.FactoryDaoImpl;
import com.bless.springdemo.dao.impl.StaticFacotryDaoImpl; public class DaoFactory {
//静态工厂
public static final FactoryDao getStaticFactoryDaoImpl(){
return new StaticFacotryDaoImpl();
}
}
同样看关键类,这里我需要注入一个FactoryDao对象,这里看起来跟第一种注入一模一样,但是看随后的xml会发现有很大差别:
public class SpringAction {
//注入对象
private FactoryDao staticFactoryDao; public void staticFactoryOk(){
staticFactoryDao.saveFactory();
}
//注入对象的set方法
public void setStaticFactoryDao(FactoryDao staticFactoryDao) {
this.staticFactoryDao = staticFactoryDao;
}
}
Spring的IOC配置文件,注意看<bean name="staticFactoryDao">指向的class并不是FactoryDao的实现类,而是指向静态工厂DaoFactory,并且配置 factory-method="getStaticFactoryDaoImpl"指定调用哪个工厂方法:
<!--配置bean,配置后该类由spring管理-->
<bean name="springAction" class="com.bless.springdemo.action.SpringAction" >
<!--(3)使用静态工厂的方法注入对象,对应下面的配置文件(3)-->
<property name="staticFactoryDao" ref="staticFactoryDao"></property>
</property>
</bean>
<!--(3)此处获取对象的方式是从工厂类中获取静态方法-->
<bean name="staticFactoryDao" class="com.bless.springdemo.factory.DaoFactory" factory-method="getStaticFactoryDaoImpl"></bean>
4.实例工厂的方法注入
实例工厂的意思是获取对象实例的方法不是静态的,所以你需要首先new工厂类,再调用普通的实例方法:
public class DaoFactory {
//实例工厂
public FactoryDao getFactoryDaoImpl(){
return new FactoryDaoImpl();
}
}
那么下面这个类没什么说的,跟前面也很相似,但是我们需要通过实例工厂类创建FactoryDao对象:
public class SpringAction {
//注入对象
private FactoryDao factoryDao; public void factoryOk(){
factoryDao.saveFactory();
} public void setFactoryDao(FactoryDao factoryDao) {
this.factoryDao = factoryDao;
}
}
最后看spring配置文件:
<!--配置bean,配置后该类由spring管理-->
<bean name="springAction" class="com.bless.springdemo.action.SpringAction">
<!--(4)使用实例工厂的方法注入对象,对应下面的配置文件(4)-->
<property name="factoryDao" ref="factoryDao"></property>
</bean> <!--(4)此处获取对象的方式是从工厂类中获取实例方法-->
<bean name="daoFactory" class="com.bless.springdemo.factory.DaoFactory"></bean>
<bean name="factoryDao" factory-bean="daoFactory" factory-method="getFactoryDaoImpl"></bean>
5.总结
Spring IOC注入方式用得最多的是(1)(2)种,多写多练就会非常熟练。
另外注意:通过Spring创建的对象默认是单例,如果需要创建多实例对象可以在<bean>标签后面添加一个属性:
<bean name="..." class="..." scope="prototype">
、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、
注解注入顾名思义就是通过注解来实现注入,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的依赖注入,支持多种注解方式:
1
2
3
4
5
6
7
8
9
|
@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。
1
2
3
4
5
6
7
8
9
|
@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:
1
2
3
4
|
<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:
1
2
3
4
|
@Component ( "lamborghini" ) @Qualifier ( "luxury" ) public class Lamborghini implements Car { } |
或者
1
2
3
4
5
|
@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中有如下定义:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
<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应用的常量定义在如下类中:
1
2
3
4
5
6
7
8
9
10
11
12
|
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" ; } } |
然后如果我们在代码中如下声明依赖:
1
2
3
4
5
6
7
8
9
10
|
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,诡异的事情发生了!
运行结果如下:
1
2
|
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
1
2
3
4
5
6
7
8
9
|
[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的这个方法出问题了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
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
1
2
3
4
5
6
7
8
|
[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方法对首先对类型做 了区别:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
|
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有多个实现,那么都会注入进来,不会再报
1
2
3
|
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则不会有这个问题:
1
2
3
4
5
6
7
8
9
10
11
|
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); } } |
正常运行:
1
2
|
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,那么还是一样报错的。
1
2
3
4
5
6
7
8
9
10
11
|
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接收所有实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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); } } |
运行的妥妥的:
1
2
3
|
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指定:
1
2
3
4
5
6
7
|
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指定:
1
2
3
4
5
6
7
|
package me.arganzheng.study.spring.autowired; import javax.inject.Named; import org.springframework.stereotype.Component; @Component @Named ( "luxury" ) public class RollsRoyce implements Car { } |
测试一下注入定义的豪华车:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
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); } } |
运行结果如下:
1
2
|
ArrayList [me.arganzheng.study.spring.autowired.Lamborghini @66b875e1 , me.arganzheng.study.spring.autowired.RollsRoyce @58433b76 ] |
补充:Autowiring modes
Spring支持四种autowire模式,当使用XML配置方式时,你可以通过autowire属性指定。
1
2
3
4
|
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。不过基本原理是一样。
Spring中的注入方式 和使用的注解 详解的更多相关文章
- Java Spring 中你不知道的注入方式
前言 在Spring配置文件中使用XML文件进行配置,实际上是让Spring执行了相应的代码,例如: 使用<bean>元素,实际上是让Spring执行无参或有参构造器 使用<prop ...
- Spring中的注入方式
在Spring配置文件中使用XML文件进行配置,实际上是让Spring执行了相应的代码,例如: 使用<bean>元素,实际上是让Spring执行无参或有参构造器 使用<propert ...
- 170330、Spring中你不知道的注入方式
前言 在Spring配置文件中使用XML文件进行配置,实际上是让Spring执行了相应的代码,例如: 使用<bean>元素,实际上是让Spring执行无参或有参构造器 使用<prop ...
- Spring Data Jpa (五)@Entity实例里面常用注解详解
详细介绍javax.persistence下面的Entity中常用的注解. 虽然Spring Data JPA已经帮我们对数据的操作封装得很好了,约定大于配置思想,帮我们默认了很多东西.JPA(Jav ...
- Spring中依赖注入的四种方式
在Spring容器中为一个bean配置依赖注入有三种方式: · 使用属性的setter方法注入 这是最常用的方式: · 使用构造器注入: · 使用Filed注入(用于注解方式). 使用属性的sett ...
- 使用IDEA详解Spring中依赖注入的类型(上)
使用IDEA详解Spring中依赖注入的类型(上) 在Spring中实现IoC容器的方法是依赖注入,依赖注入的作用是在使用Spring框架创建对象时动态地将其所依赖的对象(例如属性值)注入Bean组件 ...
- spring的set注入方式流程图解
spring的set注入方式流程图解 自己学习spring的一些笔记,详细画出了spring的set方式实现依赖注入的流程. 注意:<property name="UserDao&qu ...
- spring中构造函数注入
spring中构造函数注入,简单来说,就是通过beans.xml中,设置对应的值.而且通过bean类中的构造函数进行注入这些值. 文件结构 watermark/2/text/aHR0cDovL2Jsb ...
- JAVA高级架构师基础功:Spring中AOP的两种代理方式:动态代理和CGLIB详解
在spring框架中使用了两种代理方式: 1.JDK自带的动态代理. 2.Spring框架自己提供的CGLIB的方式. 这两种也是Spring框架核心AOP的基础. 在详细讲解上述提到的动态代理和CG ...
随机推荐
- 手机移动端网站开发流程HTML5
手机移动端网站开发流程HTML5 最近一直在研究移动手机网站的开发,发现做手机网站没有想象中的那么难.为什么会这么说呢?我们试想下:我们连传统的PC网站都会做,难道连一个小小的手机网站难道都搞不定吗? ...
- 搭建LoadRunner中的场景(一) 创建场景
一.创建场景 1. 使用场景创建设置对话框 场景分类: 1. 人工场景:相比面向目标场景,人工场景在实际工作中的应用更为广泛. 2. 面向目标场景:预先定义了一个测试目标,LoadRunner将根据这 ...
- C#多线程学习 之 线程池[ThreadPool]
在多线程的程序中,经常会出现两种情况: 一种情况: 应用程序中,线程把大部分的时间花费在等待状态,等待某个事件发生,然后才能给予响应 这一般使用ThreadPo ...
- 2018.3.3 How too much fructose may cause liver damage
Fructose is the sweetest of the natural sugars. As its name suggests, it is found mainly in fruits. ...
- codeforces 659E E. New Reform(图论)
题目链接: E. New Reform time limit per test 1 second memory limit per test 256 megabytes input standard ...
- dd备份文件系统
1.实现dd的备份: 使用gzip压缩: dd if=/dev/hdb | gzip > /local/path/image.gz 说明:/dev/hdb 是硬盘整盘.对不同的硬盘,可能是 /d ...
- 解决 maps to localhost, but this does not map back to the address
修改 /etc/ssh/ssh_config vim /etc/ssh/ssh_config GSSAPIAuthentication no
- java自定义类型 作为HashMap中的Key值 (Pair<V,K>为例)
由于是自定义类型,所以HashMap中的equals()方法和hashCode()方法都需要自定义覆盖. 不然内容相同的对象对应的hashCode会不同,无法发挥算法的正常功能,覆盖equals方法, ...
- poj1065 Wooden Sticks[LIS or 贪心]
地址戳这.N根木棍待处理,每根有个长x宽y,处理第一根花费1代价,之后当处理到的后一根比前一根长或者宽要大时都要重新花费1代价,否则不花费.求最小花费代价.多组数据,N<=5000 本来是奔着贪 ...
- LuoguP1419 寻找段落(二分 单调队列
题目描述 给定一个长度为n的序列a_i,定义a[i]为第i个元素的价值.现在需要找出序列中最有价值的“段落”.段落的定义是长度在[S,T]之间的连续序列.最有价值段落是指平均值最大的段落, 段落的平均 ...