Spring 支持五种自动装配模式:
byName模式:在使用byName模式进行自动装配时,Spring会尝试用每个属性去上下文中适配同名的bean,例如有一个TestInject bean,该bean中有一个私有属性 OmsGoodsVo 并且 我们在ApplicationContext中也定义了名为 OmsGoodsVo这个bean,那么在TestInject初始化时OmsGoodsVo这个bean将会被自动分配给TestInject中的OmsGoodsVo属性。
byType模式:在使用byType模式进行自动装配时,Spring会尝试在Spring上下文中取寻找相同类型的bean去分配给目标bean的对应属性。
构造函数模式:该模式和byType模式有异曲同工之妙,该模式通过bean的构造器进行注入而不是通过setter ,在该模式下Spring总是会尝试进行构造器最大入参的适配,例如TestInjectbean有两个构造器分别是TestInject1(String,Integer) 和TestInject2(String) 如果在ApplicationContext上下文中同时存在一个String bean和 Integer bean,那么Spring总是会去使用TestInject1去进行注入。
默认模式:Spring 将自动在构造函数模式和byType模式之间进行选择,选择的依据是如果bean存在一个默认的无参数构造函数则使用byType模式进行构造,否则用构造函数模式。
无:不使用任何模式,这是Spring的默认设置
示例:
有如下一个简单的配置文件:appliacation_test.xml, 我们将使用如下的bean来尝试不同的装配模式
<beans>
<bean id="omsGoodsVo" class="com.biz.tower.vo.OmsGoodsVo"/>
<bean id="stockAllVo" class="com.biz.tower.vo.StockAllVo"/>
<bean id="injectByType" class="com.biz.tower.vo.TestInject" lazy-init="true"
autowire="byType"/>
<bean id="injectByName" class="com.biz.tower.vo.TestInject" lazy-init="true"
autowire="byName"/>
<beanid="injectByConstructor" class="com.biz.tower.vo.TestInject" lazy-init="true" autowire="constructor"/>
</beans>
在上面的配置中可以看见有两个空类 OmsGoodsVo 和 StockAllVo 并且同时命名为omsGoodsVo和stockAllVo ,同时有三个TestInject类来分别进行byType,byName和constructor的注入测试,设置lazy-init为true的目的是为了控制日志的打印位置并无其他作用
TestInject类:
/**
* @ClassName TestInject
* @Description 测试注入类
* @Author liuzhibo
* @Date 2019/2/28 11:00 PM
**/
public class TestInject{
private OmsGoodsVo omsGoodsVoOne;
private OmsGoodsVo omsGoodsVoTwo;
private StockAllVo stockAllVo;
public TestInject() {}
public TestInject(OmsGoodsVo omsGoodsVo){
System.out.println("单参数构造注入");
}
public TestInject(OmsGoodsVo omsGoodsVo,StockAllVo stockAllVo) {
System.out.println("双参数构造注入");
}
public void setOmsGoodsVoOne(OmsGoodsVo omsGoodsVoOne) {
System.out.println("属性装配-》set omsGoodsVoOne");
}
public void setOmsGoodsVoTwo(OmsGoodsVo omsGoodsVoTwo) {
System.out.println("属性装配-》set omsGoodsVoTwo");
}
public void setStockAllVo(StockAllVo stockAllVo) {
System.out.println("属性装配-》set stockAllVo");
}
public static void main(String[] args) {
GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
ctx.load("classpath:/application_test.xml");
ctx.refresh();
TestInjecttestInject = null;
System.out.println("----------------ByName 测试----------------");
testInject = (TestInject) ctx.getBean("injectByName");
System.out.println("----------------ByType 测试----------------");
testInject = (TestInject) ctx.getBean("injectByType");
System.out.println("----------------ByConstructor 测试----------------");
testInject = (TestInject) ctx.getBean("injectByConstructor");
}
}
在上面这段代码中,可以看到TestInject类拥有三个构造函数和三个私有属性以及对应的setter方法和一个main方法,我们将通过getBean的方式来分别触发byName,byType,byConstructor注入模式。
运行结果:
----------------ByName测试----------------
属性装配-》set stockAllVo
----------------ByType测试----------------
属性装配-》set omsGoodsVoOne
属性装配-》set omsGoodsVoTwo
属性装配-》set stockAllVo
----------------ByConstructor测试----------------
双参数构造注入
可以看到byName模式下只自动注入了stockAllVo,这是符合我们期望的一个结果,因为在TestInject中只有stockAllVo能通过名称在ApplocationContext上下文中找到适配的bean,当使用byType模式时,spring自动装配了TestInject类中的所有三个属性,构造函数注入同样是符合期望的。
Spring自动装配的日常应用
相信大多数人都遇到过 org.springframework.beans.factory.NoSuchBeanDefinitionException或者org.springframework.beans.factory.UnsatisfiedDependencyException 这两个异常,该异常大多数是在通过@Autowired注入bean时Spring在上下文中发现了相同类型的bean从而不知道该如何选择装配导致的,所以,有没有思考过以下问题:
1.为什么@Service通常不会直接打在interface上?
2.@Autowired为什么会有一个@Qualifier 与其适配?@Resource呢?
参考书籍:
《Spring 5 高级编程》第五版