在Spring中,对象无需自己查找或者创建与其相关联的其他对象, 相反,容器负责把需要相互协作的对象引用赋予各个对象.
创建应用对象之间协作关系的行为通常称为”装配”. 这也是依赖注入(DI)的本质.
Spring提供了三种主要的装配机制:
1. 在XML中进行显示配置
2. 在JAVA中进行显示配置.
3. 隐式的Bean发现机制和自动装配.
Spring提供了几种技巧,可以减少XML的配置数量:
1. 自动装配(autowiring):可以减少和元素,让Spring自动识别如何装配Bean的依赖关系;
2. 自动检测(autodiscovery):Spring能够自动识别哪些类需要被装配成Spring Bean,从而减少对的使用。
自动装配
自动装配用于配置Bean的属性, 构造器,依赖关系等.
4种自动装配
- byName:把与Bean属性具有相同名字(或id)的其他Bean自动装配到Bean的对应属性中;
- byType:把与Bean属性具有相同类型的其他Bean自动装配到Bean的对应属性中;
- constructor:把与Bean的构造函数的入参具有相同类型的其他Bean自动装配到Bean的构造函数对应的入参中;
- autodetect:先尝试使用constructor,失败后再使用byType。
Demo 类
public class Instrumentalist implements Performer {
private String song;
private Instrument instrument;
public Instrumentalist() {
}
public void perform() throws PerformanceException {
System.out.print("Playing " + song + " : ");
instrument.play();
}
public void setSong(String song) { // 注入歌曲
this.song = song;
}
public String getSong() {
return song;
}
public String screamSong() {
return song;
}
public void setInstrument(Instrument instrument) { // 注入乐器
this.instrument = instrument;
}
}
1. XML自动装配
1.1 byName
为属性自动装配id与该属性的名字相同的Bean。使用方法:
<bean id="kenny" class="com.springinaction.springidol.Instrumentalist" autowire="byName">
<property name="song" value="Happy" />
</bean>
通过配置Bean Kenny的autowire=”byName”属性,Spring就可以利用此信息自动装配Kenny的instrument属性了。
缺点:需要假设Bean的名字(如instrument)与其他Bean的属性的名字一样,若其他多个Bean的属性都是instrument,那么让他们将使用同一个instrument。
1.2 byType
类似byName ,但匹配属性是检查属性的类型,
限制:当Spring根据类型匹配到多个Bean时,会抛出异常,为了解决这个问题,
Spring提供了两种方案:可以自动装配标识一个首选Bean,或者可以取消某个Bean的自动装配的候选资格。
标识首选Bean:primary=”true”
可以使用primary属性将Bean设置为首选Bean,那么它将会得到优选被选择权:
<bean id="saxphone" class="com.springinaction.springidol.Saxophone" />
<bean id="guitar" class="com.springinaction.springidol.Guitar" primary="true"/>
<bean id="kenny" class="com.springinaction.springidol.Instrumentalist"
autowire="byType">
<property name="song" value="Happy" />
</bean>
有两个Bean类型满足kenny的instrument属性,但是guitar设置了primary=”true”,因此会注入guitar。
排除其他Bean: autowire-candidate=”false”
<bean id="saxphone" class="com.springinaction.springidol.Saxophone" autowire-candidate="false"/>
<bean id="guitar" class="com.springinaction.springidol.Guitar"/>
<bean id="kenny" class="com.springinaction.springidol.Instrumentalist"
autowire="byType">
<property name="song" value="Happy" />
</bean>
通过排除其他Bean的候选资格来达到和上面设置primary同样的效果。
1.3 constructor
当有多个Bean匹配某个构造函数的入参时,Spring同样会抛出异常。
<bean id="duke" class="com.springinaction.springidol.PoeticJuggler"
autowire="constructor">
</bean>
当使用constructor自动装配时,就不能混合使用constructor自动装配和标签了。
1.4 autodetect
如果想自动装配,但又不能决定使用哪一种类型的自动装配,可以把autowire属性设置为autodetect,由Spring来决定,如:
<bean id="duke" class="com.springinaction.springidol.PoeticJuggler"
autowire="autodetect">
</bean>
Spring将首先尝试使用constructor自动装配,如果没有发现与构造器相匹配的Bean时,Spring将尝试使用ByType自动装配。
1.5 默认自动装配
可以在根元素上添加default-autowire属性来设置该配置文件中的的自动装配方式。
【default-autowire=”byName”】
<beans xmlns="http://www.springframework.org/schema/beans"
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/mvc"
default-lazy-init="true" default-autowire="byName">
</beans>
1.6 混合使用显示装配和自动装配
显示装配会覆盖掉自动装配:
<bean id="saxphone" class="com.springinaction.springidol.Saxophone" autowire-candidate="false"/>
<bean id="guitar" class="com.springinaction.springidol.Guitar"/>
<bean id="kenny" class="com.springinaction.springidol.Instrumentalist"
autowire="byType">
<property name="song" value="Happy" />
<property name="instrument" ref="saxphone"></property>
</bean>
虽然取消了saxphone的候选资格,但最终仍是saxphone注入到了kenny的属性中。
2.注解自动装配
启用注解装配:
<context:annotation-config />
Spring 支持几种不同的用于自动装配的注解
Spring自带的 @Autowired注解
JSR-330的 @Inject注解
JSR-250的 @Resource注解
2.1 @Autowired
// 1、可以标注setter
@Autowired
public void setInstrument(Instrument instrument) {
this.instrument = instrument;
}
// 2、标注其他方法
@Autowired
public void heresYourInstrument(Instrument instrument) {
this.instrument = instrument;
}
// 3、标注构造器
@Autowired
public Instrumentalist(Instrument instrument) {
this.instrument = instrument;
}
// 4、直接标注属性
@Autowired
private Instrument instrument;
@Autowired 存在两个问题: 没有找到合适的Bean,或者找到多个合适的bean,解决这个问题要用到可选的自动装配
@Autowired(required=false)
private Instrument instrument;
注意:
1. 当使用构造器装配时,只有一个构造器可以将@Autowired的required属性设置为true,其他使用@Autowired注解的required属性必须设置为false。
2. 当使用@Autowired标注多个构造器时,Spring会从所有满足装配条件的构造器中选择入参最多的那个。
限制歧义性的依赖
当有多个Bean满足装配条件时,可以配合使用@Qualifier注解。
@Autowired
@Qualifier("guitar")
private Instrument instrument;
注:用户也可以创建自定义的限定器,限定器的最终目的是【缩小了自动装配候选Bean的范围】
2.2 @Inject
Spring的@Autowired注解会引入对Spring的特定依赖,幸运的是Spring还提供了标准的JAVA注解来代替@Autowired,
和@Autowired一样,@Inject可以装配 属性、方法和构造器;
但是@Inject没有required属性,因此@Inject注解所依赖的bean是必须存在的,如果不存在就会抛出异常。
除了@Inject注解,JSR-330还提供了另一种技巧,与其直接注入一个引用, 不如要求@Inject注入一个Provider。Provider接口可以实现Bean引用的延迟注入以及注入多个Bean实例的功能。
private Set<Knife> knives;
@Inject
public KnifeJuggler(Provider<Knife> knifeProvider) {
knives = new HashSet<Knife>();
for (int i = 0; i < 5; i++) {
knives.add(knifeProvider.get());
}
}
KnifeJuggler类需要注入多个Knife实例,假设Knife Bean的作用域是prototype的,那么KnifeJuggler将获得一个Provider,这时只有provider被注入;在调用provider的get()方法之前,实际的Knife对象没有被注入。
限定注入的属性:@Named
@Inject
@Named("guitar")
private Instrument instrument;
用户也可以创建自定义的JSR-330 Qualifier
在注解中使用表达式:@Value
可以使用@Value装配简单值:String类型和基本类型,如:
@Value("Happy")
private String song;
@Value可以配合SpEL使用:
@Value("#{systemProperties.myFavoriteSong}")
private String song;
自动监测
自动监测用于使Spring自动发现应用中的Bean,解放手动配置Bean(xml或使用注解) 的繁琐流程.
<context:annotation-config> 可以减少<property>和<constructor-arg> 的使用,但仍需配置bean。
使用<context:component-scan> 除了可以完成上述工作,还可以自动检测Bean和定义Bean,
它会扫描指定的包及其所有子包,并查找出能够自动注册为Spring Bean的类。
正确配置:
<context:component-scan base-package="com.springinaction.springidol"/>
1.注解自动监测Bean
默认情况下, scan 会查找使用如下注解的类,
@Component:通用的构造型注解,标识该类为Spring组件;
@Controller:标识该类为Spring MVC controller;
@Repository:标识为数据仓库;
@Service:标识为服务;
@Component:标注为自定义注解。
如:
//Guitar对应的Bean的ID为guitar,
//注意首字母变为小写
@Component
public class Guitar implements Instrument {
public void play() {
System.out.println("Strum strum strum");
}
}
//Strum 对应的Bean的ID为eddie
@Component("eddie")
public class Strum implements Instrument {
public void play() {
System.out.println("Strum strum strum");
}
}
2.注解过滤组件扫描
自动检测注解的不足:必须手动用@Component标注所有Bean,对于第三方的代码,无法标注,所以Spring添加了一些过滤器组件【context:exclude-filter】,其Type和expression属性一起协作来定义组件扫描策略:
过滤器类型 | 描述 |
---|---|
annotation | 扫描使用指定注解所标注的类,通过expression属性指定要扫描的注解 |
assignable | 扫描派生于expression属性所指定类型的那些类 |
aspectj | 扫描与expression属性所指定的AspectJ表达式多匹配的那些类 |
custom | 使用自定义的org.springframework.core.type.TypeFilter实现类,该类由expression属性指定 |
regex | 扫描类名称与expression属性所指定的正则表达式所匹配的类 |
以下配置实现了:自动注册所有实现了Instrument接口的类,并且排除使用自定义@SkipIt注解的类。
<context:component-scan base-package="com.springinaction.springidol">
<!--所有实现Instrument的类都将被自动注册为bean-->
<context:include-filter type="assignable"
expression="com.springinaction.springidol.Instrument" />
<!--排除使用自定义@SkipIt注解的类-->
<context:exclude-filter type="annotation"
expression="com.springinaction.springidol.SkipIt" />
</context:component-scan>
注:应用过滤器时,可以有无限的过滤可能,但我们会发现默认的基于注解的过滤策略是经常使用到的。
3.基于JAVA配置bean
component-can会自动加载base-package下使用@Configuration注解所标注的类,此类即为配置类,
等价于XML配置中的’< beans>元素, 该注解告诉Spring:这个类将包含一个或者多个Spring Bean的定义,这行Bean的定义是使用@Bean注解所标注的方法。
//定义一个配置类
@Configuration
class Employee {
public Car myCar;
public Employee(Car _car) {
this.myCar = _car;
}
//@Bean 告诉Spring这个方法将返回一个对象,该对象会被注册为Spring应用上下文中的一个Bean
@Bean
public Car newCar() {
return new Car(1, "white");
}
//使用Bean装配另一个Bean引用。
@Bean
public Employee newEmployeeWithCar() {
return new Employee(newCar());
}
}