Spring自动装配
为了减少XML的配置数量。Spring提供了几种技巧来解决这一问题:
自动装配(autowiring): 有助于减少<property>
元素和<constroctor-arg>
元素,让Spring自动识别如何装配Bean的依赖关系
自动检测(autodiscovery): 让Spring自动识别那些类需要被配置成Spring Bean
,从而减少对<bean>
元素的使用
<!--more-->
自动装配Bean属性
4种类型的自动装配
byName: 把与Bean的属性具有相同名字(或ID)的其他Bean自动装配到Bean的对应属性中。如果没有跟属性的名字相匹配的Bean,该属性不进行装配。
byType: 把与Bean的属性具有相同类型的其他Bean自动装配到Bean的对应属性中。若没有相匹配的Bean,则不装配。
constructor: 把与Bean的构造器入参具有相同类型的其他Bean自动装配到Bean的构造器的对应入参中。
autodetect: 首先尝试用
constructor
进行自动装配,若失败就再次尝试用byType
进行自动装配。
byName自动装配
通过byName
元素Spring可以实现自动查找其他Bean中是否有相同的属性名称,有则进行装配。体会下面的案例:
EmpService.java
package demo2; public class EmpService { //注入Dao private EmpDao empDao; public void setEmpDao(EmpDao empDao) { System.out.println("EmpService --> EmpDao"); this.empDao = empDao; } }
EmpDao.java
package demo2; public class EmpDao { }
spring.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="empService" class="demo2.EmpService" autowire="byName"/>
<bean id="empDao" class="demo2.EmpDao"/>
</beans>
Test测试类
public void run(){ //加载Spring上下文 ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml"); ac.getBean("empService"); }
打印结果:
情况一:如果通过之前的方式通过ref
来注入一个引用Bean呢?我们改变上述spring.xml
中的代码:
<bean id="empDao" class="demo2.EmpDao"/>
<bean id="empService" class="demo2.empService">
<property name="empDao" ref="empDao"/>
</bean>
结果和上述相同。
情况二:如果我们改变EmpService.java
中empDao
的数据类型
package demo2; public class EmpService { //注入Dao private String empDao; public void setEmpDao(String empDao) { System.out.println("EmpService --> EmpDao"); this.empDao = empDao; } }
打印结果:
观察发现autowire="byName"
自动装配的含义就是:
若BeanA
中存在与BeanB
的idid="empDao"
相同的属性(且存在setter方法)private EmpDao empDao
;Spring就能自动进行装配。
那么,开始我一直疑惑这个byName
注入不是查找的bean名称和另一个bean的属性名称相同就能注入吗,其实并不是。首先我们要明白注入是注入另一个类或接口什么的,Spring让这两个bean之间产生了联系,即我们定义一个普通类型的参数private String empDao
,即使名称和另一个Bean相同empDao
,但是它只是一个属性,怎么能让一个普通的参数注入到另一个Bean中呢?(注意这里我是在自动注入的前提下说的,当然是可以实现注入的)。
缺点: byName
的缺点就是首先要存在这个和另一个Bean的id相同的属性,其次,如果多个Bean中都存在这个属性,那Spring都会将这些Bean装配进去。如下案例:
新创建一个UserDao.java
package demo2; public class EmpDao { }
EmpService.java
package demo2; public class EmpService { //注入Dao private EmpDao empDao; public void setEmpDao(EmpDao empDao) { System.out.println("EmpService --> EmpDao"); this.empDao = empDao; } private UserDao userDao; public void setUserDao(UserDao userDao) { System.out.println("EmpService --> UserDao"); this.userDao = userDao; } }
在spring.xml
中注入该Bean:
<bean id="userDao" class="demo2.UserDao"/>
观察结果:
如上就体现了这中注入方法的缺陷,就是如果多个Bean的id
名称都和另一个bean的属性有相同的,那么Spring会全部注入。
byType自动装配
byType
自动装配的方法和byName
装配方式类似,区别在于byType
找的是相同的数据类型,而byName
找的是相同的名称
。
EmpService.java
package demo2; public class EmpService { //注入Dao private EmpDao emp; public void setEmpDao(EmpDao emp) { System.out.println("EmpService --> EmpDao"); this.emp = emp; } }
spring.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="empService" class="demo2.EmpService" autowire="byType"/>
<bean id="empDao" class="demo2.EmpDao"/>
</beans>
Test测试类
public void run(){ //加载Spring上下文 ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml"); ac.getBean("empService"); }
打印结果:
观察:上面EmpDao
bean的id是empDao
,但EmpService
中存在的属性是private EmpDao emp;
,注意不是empDao
,那么当我们配置了autowire="byType"
后,因为数据类型相同,Spring仍可将EmpDao
注入到EmpService
中。
缺点
如果Spring找到了多个Bean的,他们的类型与需要自动装配的属性的类型都匹配,那么Spring会抛出异常,而不是选择注入哪个。如下情况:
其他不变,我们在spring.xml
增加一行配置:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="empService" class="demo2.EmpService" autowire="byType"/>
<bean id="empDao" class="demo2.EmpDao"/>
<bean id="dao" class="demo2.EmpDao"/>
</beans>
打印结果:
可以看到,当我们在spring.xml
中注入两个类型相同的Bean(当然名称不能相同),此时就会报错。
为解决这一错误,Spring提供了pimary
和autowire-candidate
两个属性。
为自动装配标识一个首选Bean,使用
primary
属性,如果设置primary="true"
那么该Bean会被优先选择。但Spring却给每一个<bean>
默认都配置了primary="true"
属性,也就是此时每个Bean都是首选的,而你想指定哪个Bean是首选就必须设置其他Bean都是primary="false"
候选。所以说:primary
属性仅对标识的首选Bean有意义。如果想解决上面多个Bean都符合情况造成的报错,必须引入下面属性:即为想被忽略注入的
<bean>
配置autowire-candidate="false"
属性,这样Spring在进行装配时就会忽略autowire-candidate="false"
标识的Bean。
spring.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="empService" class="demo2.EmpService" autowire="byType"/>
<bean id="empDao" class="demo2.EmpDao"/>
<bean id="dao" class="demo2.EmpDao" autowire-candidate="false"/>
</beans>
constructor自动装配
如果要通过构造器来装配Bean,那我们可以移除<constructor-arg>
元素,采用Spring自动注入的方式:(注意这里我们改变了以上的代码逻辑,增加了一个Emp
接口,让EmpDao
和UserDao
都继承这个接口)
Emp.java
package demo2; public interface Emp { }
EmpDao.java
package demo2; public class EmpDao implements Emp { }
UserDao.java
package demo2; public class UserDao implements Emp { }
EmpService.java
package demo2; public class EmpService { private Emp emp; public void setEmp(Emp emp) { System.out.println("执行..."); this.emp = emp; } public EmpService(Emp emp) { System.out.println("这是构造参数..."); this.emp = emp; } }
同时这种方式也存在和byType
一样的缺点:
spring.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="empService" class="demo2.EmpService" autowire="constructor"/>
<bean id="empDao" class="demo2.EmpDao"/>
<bean id="userDao" class="demo2.UserDao"/>
</beans>
结果仍报错:
最佳自动装配—>由Spring决定怎么装配
<bean id="empService" class="demo2.EmpService" autowire="autodetect"/>
默认自动装配
<beans ....
default-autowire="xxx"
>
使用注解自动装配
从Spring2.5开始可以使用注解实现自动装配,注解装配和使用XML中的autowire
本身没有太大区别,但是注解装配可以实现更小颗粒度的装配。但是Spring默认是禁用注解装配的,所以我们首先要在spring.xml
中启用注解装配。最简单的方式就是启用Spring的context
命名空间配置中的<context:annotation-config>
元素,如下:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:annotation-config/>
</beans>
Spring3支持几种不同的自动装配注解:
Spring自带的@Autowired注解
JSR-330的@Inject注解
JSR-250的@Resource注解
使用@Autowired
如下案例:
spring.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:annotation-config/>
<bean id="empService" class="demo2.EmpService"/>
<bean id="empDao" class="demo2.EmpDao"/>
</beans>
EmpService.java
package demo2; import org.springframework.beans.factory.annotation.Autowired; public class EmpService { private Emp emp; public void setEmp(Emp emp) { System.out.println("执行..."); this.emp = emp; } public void play(){ emp.play(); } }
Emp.java
package demo2; public interface Emp { void play(); }
EmpDao.java
package demo2; public class EmpDao implements Emp { public void play() { System.out.println("EmpDao: play..."); } }
Test测试类
public void run(){ //加载Spring上下文 ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml"); EmpService es = (EmpService) ac.getBean("empService"); es.play(); }
除了上面标记在setter方法上,还可以直接标记到私有属性上:
package demo2; import org.springframework.beans.factory.annotation.Autowired; public class EmpService { private Emp emp; public void setEmp(Emp emp) { System.out.println("执行..."); this.emp = emp; } public void play(){ emp.play(); } }
可以看到,使用这种方式仍可以完成注入,并且我们发现将@Autowired
直接标记到私有属性上,并不会执行setter方法,但是仍完成了注入,这其实就是@Autowired
的一大优势,它简化了setter方法的书写,采用Java的反射机制完成的注入。
缺点:采用@Autowired
注解注入固然方便,但是也存在缺点,即被标注的属性或参数必须是可注入的,如下列情况,我们将emp
的数据类型改变(注意这里要注释掉play
方法,因为数据类型改变了也就不能再调用play方法):
package demo2; import org.springframework.beans.factory.annotation.Autowired; public class EmpService { // private Emp emp; private String emp; /*public void play(){ emp.play(); }*/ }
此时就会报错NoSuchBeanDefinitionException
,因为我们认为属性不一定要被装配,返回null值也好,从而避免抛出异常,那么我们就可以在@Autowired
中添加required
属性,表示是否一定要进行装配:
package demo2; import org.springframework.beans.factory.annotation.Autowired; public class EmpService { required = false) (// private Emp emp; private String emp; /*public void play(){ emp.play(); }*/ }
这样就不会再出现保报错的情况。
注意: required
属性可以用于@Autowired
注解所使用的任意地方,但当使用构造器进行装配时,只有一个构造器可以将@Autowired
的required
属性设置为true
,其他都必须设置为required=false
。此外,使用@Autowired
标记多个构造器时,Spring会从所有瞒住装配条件的构造器中选择入参最多的构造器。
限定歧义性的依赖
对于出现多个Bean满足注入条件的解决,上面也介绍了一系列的方法,即使是上面的@Autowired
细颗粒度的注入方式仍然无法解决多个Bean满足注入条件的情况,所以Spring提供了一个更好的方式解决这一问题:
使用@Qualifier("beanName")
限定注入哪个Bean
spring.xml
<bean id="empService" class="demo2.EmpService"/>
<bean id="empDao" class="demo2.EmpDao"/>
<bean id="userDao" class="demo2.UserDao"/>
此时,因为EmpDao
和UserDao
都实现了Emp
接口,所以empDao
和userDao
两个Bean都瞒住注入条件,此时就会产生NoSuchBeanDefinitionException
的错误。
EmpService.java
package demo2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; public class EmpService { required = false) ( "empDao") ( private Emp emp; public void play(){ emp.play(); } }
即这里我们限定了将empDao
注入,而不是注入userDao
,这样就会避免出现错误。
@Inject基于标准的自动装配
为了统一各种依赖注入框架编程模型,JCP(Java Community Process)发布了一套Java的依赖注入规范,@Inject注解则是其核心部件,该注解和@Autowired几乎相同,但对于一些特殊情况,@Inject也有自己的处理办法:
首先@Inject没有required属性,所以被标记的属性必须是可注入的
歧义性的Bean定义:@Qualifier —> @Named
在注解注入中使用表达式
@Value
自动检测Bean
当在Spring配置中增加了<context:annotation-config>
时,我们希望Spring特殊对待我们所定义的Bean里的某一组注解,并使用这些注解指导Bean的装配,由此产生了 <context:component-scan
元素。它能大大简化对<bean>
的配置,通过在<context:component-scan>
元素中指定要扫描指定包下的Bean对象,如:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan base-package="demo2"/>
</beans>
那么<context:component-scan>
又是如何知道哪些类需要注册为Spring Bean呢?
为自动检测标注Bean
<context:component-scan>
会查找使用构造型(stereotype)注解所标注的类
@Component —>通用的构造性注解,标识该类为Spring组件。
@Controller —>标识将该类定义为Spring MVC controller
@Repository —>标识将该类定义为数据仓库
@Service —>标识将该类定义为服务
使用@Component标注的任意自定义注解
举例:
spring.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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <context:component-scan base-package="demo2"/> </beans>
如上我们只需要开启注解支持.
EmpService.java
package demo2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; public class EmpService { required = false) ( private Emp emp; public EmpDao empDao(){ return new EmpDao(userDao()); } public void play(){ emp.play(); } }
如上,我们首先使用@Configuration
标记的Java类,就等价于XML配置中的<beans>
元素。使用@Bean
就等价于<bean>
元素,(@Bean
告知Spring这个方法将返回一个对象,该对象应该被注册为Spring应用上下文中的一个Bean。方法名将作为该Bean的ID。在该方法中所实现的所有逻辑本质上都是为了创建Bean)这样我们就实现了将一个JavaBean对象交付给Spring管理,再添加@Autowired
就完成了自动注入的功能。这是注入一个简单对象,那么我们怎么注入一个引用呢?
package demo2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; public class EmpService { "empDao") ( required = false) ( private Emp emp; public UserDao userDao(){ return new UserDao(); } public EmpDao empDao(){ return new EmpDao(userDao()); } }
向另一个Bean中注入一个引用,只需要在此Bean的返回值中调用另外一个被@Bean
标记的方法名即可完成注入,但是仍要考虑歧义性问题(使用@Qualifier
进行限定)
总结
综上:我们已经介绍了Spring自动装配和注解开发的相关知识,下面我们来回顾一下。
自动装配
byName: 把与Bean的属性(在JavaBean中定义的属性)和具有相同名字(ID)的其他Bean自动装配到Bean的对应属性中。
byType: 把与Bean的属性具有相同类型的其他Bean自动装配到Bean的对应属性中。
constructor: 把与Bean的构造器参数入参具有相同类型的其他Bean自动装配到Bean的对应入参中。
autodetect: 首先舱室使用
constructor
进行自动装配,如果装配失败再尝试使用byType
进行装配。
注解开发
自动装配提供的注解:
@Autowired — required=false — @Qualifier("beanName")
@Inject — @Named
Spring基于Java的注解开发:
@Configuration(
<beans>
) @Bean(
<bean>
)
注意问题:
以上的自动装配的方式都无法避免多个Bean同时满足注入条件的情况,但是Spring针对不同的注入方式提供了不同的解决办法:
byName
会对满足条件的Bean都进行封装,不加考虑。byType
对遇到多个满足注入条件的Bean时会抛出异常,同时给出primary
(仅对首选Bean有意义)、autowire-candidate="false"
(忽略某个Bean的自动装配)。(注:我们要明白Spring默认将所有Bean都设置为primary="true"
这样就没有什么首选Bean可言。所以此时我们最好采用autowire-candidate
忽略某个Bean的自动装配。constructor
自动装配和byType
有一样的局限性,会报错。综合以上,提出了最佳的自动装配方式:
autowire="autodetect"
。Spring首先会尝试使用constructor自动装配,如果没有与构造器相匹配的Bean,Spring将尝试使用byType
自动装配。注解开发有常用的三个自动装配方式:1.
@Autowired
(Spring自带的注解); 2.Inject
(基于标准的自动装配);3.@Resource
@Autowired
(默认按照类型装配)避免
@Autowired
标注的属性或参数是不可装配的,提供required=false
属性。限定歧义性的依赖(多个Bean的情况),提供
@Qualifier("beanName")
缩小选择范围,指定某一个Bean
@Inject
注解自动装配和@Autowired
注解相似,但是对上述解决方案给出的注解不同,如以下:@Inject
限定所标记的属性必须是可装配的,不然就会报错@Qualifier
—>@Named
@Resource
(默认按照名称装配)是jdk1.6支持的注解
<br/>
交流
如果大家有兴趣,欢迎大家加入我的Java交流群:671017003 ,一起交流学习Java技术。博主目前一直在自学JAVA中,技术有限,如果可以,会尽力给大家提供一些帮助,或是一些学习方法,当然群里的大佬都会积极给新手答疑的。所以,别犹豫,快来加入我们吧!
<br/>
If you have some questions after you see this article, you can contact me or you can find some info by clicking these links.