Spring之自动装配
Spring容器可以自动装配协作bean之间的关系。自动装配具有以下优点:
自动装配可以大大减少指定属性或构造函数参数的需要。
随着对象的发展,自动装配可以更新配置。例如,如果需要将依赖项添加到类中,则无需修改配置即可自动满足该依赖项。因此,自动装配在开发过程中特别有用,而不必担心在代码库变得更稳定时切换到显式接线的选择。
使用基于XML的配置元数据时(请参阅Dependency Injection),您可以使用元素的autowire属性为 bean定义指定自动装配模式。自动装配功能具有四种模式。您可以为每个bean指定自动装配,因此可以选择要自动装配的装配。下表描述了四种自动装配模式:
使用byType或constructor自动装配模式,你可以连接阵列和键入的集合。在这种情况下,将提供容器中与预期类型匹配的所有自动装配候选,以满足相关性。Map如果预期的密钥类型为,则可以自动连接强类型实例String。自动装配Map 实例的值包括与期望类型匹配的所有bean实例,并且 Map实例的键包含相应的bean名称。
自动装配是使用spring满足bean依赖的一种方法
spring会在应用上下文中为某个bean寻找其依赖的bean。
Spring中bean有三种装配机制,分别是:
- 在xml中显式配置;
- 在java中显式配置;
- 隐式的bean发现机制和自动装配。
这里我们主要讲第三种:自动化的装配bean。
Spring的自动装配需要从两个角度来实现,或者说是两个操作:
- 组件扫描(component scanning):spring会自动发现应用上下文中所创建的bean;
- 自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI;
组件扫描和自动装配组合发挥巨大威力,使得显示的配置降低到最少。
推荐不使用自动装配xml配置 , 而使用注解 .
byName
autowire byName (按名称自动装配)
由于在手动配置xml过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率降低。
采用自动装配将避免这些错误,并且使配置简单化。
实体类:
public class Bird {
public void shout() {
System.out.println("飞~");
}
}
public class Dog {
public void shout() {
System.out.println("wang~");
}
}
public class User {
private Bird bird;
private Dog dog;
private String str;
}
配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<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="dog" class="cn.domain.Dog"/>
<bean id="bird" class="cn.domain.Bird"/>
<bean id="user" class="cn.domain.User">
<property name="bird" ref="bird"/>
<property name="dog" ref="dog"/>
<property name="str" value="Daylight"/>
</bean>
</beans>
修改bean配置,增加一个属性 autowire=“byName”
<bean id="user" class="cn.domain.User" autowire="byName">
<property name="str" value="Daylight"/>
</bean>
测试,结果正常
我们再修改
<bean id="birdtest" class="cn.domain.Bird"/>
测试出现空指针异常,因为按byName规则找不对应set方法,真正的setBird就没执行,对象就没有初始化,所以调用时就会报空指针错误。
结论:
- 当一个bean节点带有 autowire byName的属性时。
- 将查找其类中所有的set方法名,例如setBird,获得将set去掉并且首字母小写的字符串,即bird。
- 去spring容器中寻找是否有此字符串名称id的对象。
- 如果有,就取出注入;如果没有,就报空指针异常。
byType
autowire byType (按类型自动装配)
使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常NoUniqueBeanDefinitionException
.
实体类同上
修改配置:将user的bean配置修改一下 : autowire=“byType”,并增加一个bird 的bean对象!
<bean id="dog" class="cn.domain.Dog"/>
<bean id="bird" class="cn.domain.Bird"/>
<bean id="bird2" class="cn.domain.Bird"/>
<bean id="user" class="cn.domain.User" autowire="byType">
<property name="str" value="Daylight"/>
</bean>
测试,报错:NoUniqueBeanDefinitionException,删掉bird2,将bird的bean名称改掉,都不会报错,因为是按类型装配,所以并不会报异常,也不影响最后的结果。甚至将id属性去掉,也不影响结果。
使用注解
使用注解必须保证,jdk5以上,spring2.5以上
在spring的配置文件加上context文件头
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
并开启注解
<context:annotation-config/>
@Required
该@Required注释适用于bean属性setter方法,如下面的例子:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Required
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
此注释指示必须在配置时通过bean定义中的显式属性值或通过自动装配来填充受影响的bean属性。如果尚未填充受影响的bean属性,则容器将引发异常。这允许急切和显式的故障,避免NullPointerException 以后再发生实例等。
注意
:从@RequiredSpring Framework 5.1开始,正式弃用了这个注释,以便为所需的设置(或InitializingBean.afterPropertiesSet()Bean属性设置器方法的自定义实现)使用构造函数注入。
@Autowired
- @Autowired是按类型自动转配的,不支持id匹配。
- 需要导入 spring-aop的包!
测试:将User类中的set方法去掉,使用@Autowired注解
public class User {
@Autowired
private Bird bird;
@Autowired
private Dog dog;
private String str;
public Bird getBird() {
return cat;
}
public Dog getDog() {
return dog;
}
public String getStr() {
return str;
}
}
配置文件
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean id="dog" class="cn.domain.Dog"/>
<bean id="bird" class="cn.domain.Bird"/>
<bean id="user" class="cn.domain.User"/>
</beans>
测试成功
@Autowired(required=false) 说明:false,对象可以为null;true,对象必须存对象,不能为null。
//如果允许对象为null,设置required = false,默认为true
@Autowired(required = false)
private Bird bird;
- 可以将@Autowired注释应用于构造函数,如以下示例所示:
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
注意
:从Spring Framework 4.3开始,@Autowired如果目标Bean仅定义一个以其开头的构造函数,则不再需要在此类构造函数上添加注释。但是,如果有多个构造函数可用,并且没有主/默认构造函数,则必须至少注释一个构造函数,@Autowired以指示容器使用哪个构造函数。
- 可以将@Autowired注释应用于传统的setter方法,如以下示例所示:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
- 还可以将注释应用于具有任意名称和多个参数的方法,如以下示例所示:
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
- 可以将其应用于@Autowired字段,甚至可以将其与构造函数混合使用,如以下示例所示:
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
private MovieCatalog movieCatalog;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
注意
:确保目标组件(例如MovieCatalog或CustomerPreferenceDao)由用于带@Autowired注释的注入点的类型一致地声明。否则,注入可能会由于运行时出现“找不到类型匹配”错误而失败。对于通过类路径扫描找到的XML定义的bean或组件类,容器通常预先知道具体的类型。但是,对于@Bean工厂方法,您需要确保声明的返回类型具有足够的表现力。对于实现了多个接口的组件,或者对于可能由其实现类型引用的组件,请考虑在工厂方法中声明最具体的返回类型(至少根据引用您的bean的注入点的要求进行声明)。
- 可以ApplicationContext通过将@Autowired注释添加到需要该类型数组的字段或方法中,指示Spring提供特定类型的所有bean
,如以下示例所示:
public class MovieRecommender {
@Autowired
private MovieCatalog[] movieCatalogs;
// ...
}
如下例所示,这同样适用于类型化集合:
public class MovieRecommender {
private Set<MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
如果希望数组或列表中的项目以特定顺序排序,则目标bean可以实现org.springframework.core.Ordered接口或使用@Order或标准@Priority注释。否则,它们的顺序将遵循容器中相应目标bean定义的注册顺序。
还可以@Order在目标类级别和@Bean方法上声明注释,这可能适用于单个bean定义(如果使用同一bean类的多个定义)。@Order值可能会影响注入点的优先级,但请注意它们不会影响单例启动顺序,这是由依赖关系和@DependsOn声明确定的正交关注点。
注意
,标准javax.annotation.Priority注释在该@Bean级别不可用 ,因为无法在方法上声明它。可以通过将@Order值与@Primary每个类型的单个bean结合使用来对其语义进行建模。
Map只要预期的键类型为,即使是键入的实例也可以自动装配String。映射值包含所有预期类型的bean,并且键包含相应的bean名称,如以下示例所示:
public class MovieRecommender {
private Map<String, MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
6、可以通过Java 8来表达特定依赖项的非必需性质java.util.Optional,如以下示例所示:
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
...
}
}
@Qualifier
- @Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配
- @Qualifier不能单独使用。
测试实验步骤:
1、配置文件修改内容,保证类型存在对象。且名字不为类的默认名字!
<bean id="dog1" class="cn.domain.Dog"/>
<bean id="bird1" class="cn.domain.Bird"/>
<bean id="dog2" class="cn.domain.Dog"/>
<bean id="bird2" class="cn.domain.Bird"/>
没有加Qualifier测试,直接报错
在属性上添加Qualifier注解
@Autowired
@Qualifier(value = "bird2")
private Bird bird;
@Autowired
@Qualifier(value = "dog2")
private Dog dog;
测试,成功输出!
@Resource
- @Resource如有指定的name属性,先按该属性进行byName方式查找装配;
- 其次再进行默认的byName方式进行装配;
- 如果以上都不成功,则按byType的方式自动装配
- 都不成功,则报异常。
public class User {
//如果允许对象为null,设置required = false,默认为true
@Resource(name = "bird2")
private Bird bird;
@Resource
private Dog dog;
private String str;
}
<bean id="dog1" class="cn.domain.Dog"/>
<bean id="bird1" class="cn.domain.Bird"/>
<bean id="dog2" class="cn.domain.Dog"/>
<bean id="bird2" class="cn.domain.Bird"/>
<bean id="user" class="cn.domain.User"/>
测试:OK
修改配置文件 , 删掉bird2
<bean id="dog1" class="cn.domain.Dog"/>
<bean id="bird1" class="cn.domain.Bird"/>
<bean id="dog2" class="cn.domain.Dog"/>
实体类上只保留注解
@Resource
private Bird bird;
@Resource
private Dog dog;
结果:OK
结论:先进行byName查找,失败;再进行byType查找,成功。
总结
:
@Autowired与@Resource异同:
1、@Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在setter方法上。
2、@Autowired默认按类型装配(属于spring规范),默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用
3、@Resource(属于J2EE复返),默认按照名称进行装配,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
它们的作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired先byType,@Resource先byName。