- 参考文档:传送门1 传送门2
- 说明:第一个文档是W3School中的Spring学习文档,比较粗略。第二个文档是Spring官方的文档,非常详细,对自己英语水平不自信的话使用浏览器的翻译插件查看。
- 写在前面:本文是个人学习总结,笔记内容都来自于以上两个文档,笔记大部分采用了问答的方式来梳理文档中复杂的知识体系,属个人习惯,不喜勿喷。
- 这是学习笔记的第二篇,本文通篇都在介绍Spring环境下的依赖注入方法,不管是基于构造方法,setter方法,自动装配还是注解配置的DI,其目的都是为beans之间建立正确的联系。
文章目录
依赖注入
- 什么叫做依赖注入( DI)?
——Spring框架的核心功能之一就是通过依赖注入的方式来管理Bean之间的依赖关系。bean之间的依赖通过构造函数参数,工厂方法的参数或在构造对象实例后在对象实例上设置的属性来定义。依赖注入(DI)是一个过程,通过这个过程,Spring的IoC容器在实例化bean的时候为它注入这些依赖项,达到控制反转(IoC)的目的。
- DI有什么优势?
——使用DI原则的代码更清晰,当对象提供其依赖项时,解耦更有效。该对象不查找其依赖项,也不知道依赖项的位置或类。因此,单元测试也会变得更加方便。
- 依赖注入有几种方式?分别是什么?
——两种。一种是基于构造方法的依赖注入,另一种是基于setter方法的依赖注入。
序号 | 依赖注入类型 | 描述 |
---|---|---|
1 | Constructor-based dependency injection | 当容器调用带有多个参数的构造函数类时,实现基于构造函数的 DI,每个代表在其他类中的一个依赖关系。 |
2 | Setter-based dependency injection | 基于 setter 方法的 DI 是通过在调用无参数的构造函数或无参数的静态工厂方法实例化 bean 之后容器调用 beans 的 setter 方法来实现的。 |
- 两种注入方式应该如何选择?
——it is a good rule of thumb to use constructors for mandatory dependencies and setter methods or configuration methods for optional dependencies.
——将构造函数的依赖注入用于强制依赖项和将setter方法注入用于可选依赖项的配置是一个良好的经验法则。
基于构造方法的依赖注入
这是一个普通的POJO,不依赖于容器特定的接口,基类或注释,仅提供 一个有参构造方法。
- 最简单的情况
SimpleBeanA 类的依赖项是SimpleBeanB类:
package com.xx.DI_demo;
public class SimpleBeanA {
private SimpleBeanB simpleBeanB;
public SimpleBeanA(SimpleBeanB simpleBeanB) {
this.simpleBeanB = simpleBeanB;
}
}
在Beans.xml中为其注入依赖:
<bean id="simpleBeanA" class="com.xx.DI_demo.SimpleBeanA">
<!-- 依赖注入,simpleBeanB--》simpleBeanA -->
<constructor-arg ref="simpleBeanB"></constructor-arg>
</bean>
<bean id="simpleBeanB" class="com.xx.DI_demo.SimpleBeanB"></bean>
- 特殊情况:类的构造参数中有java基础类型导致参数类型无法识别
比如这个类的构造参数是String类型和int类型:
package com.xx.DI_demo;
public class SimpleBeanC {
private String name;
private int age;
public SimpleBeanC(String name, int age) {
this.name = name;
this.age = age;
}
}
那么你就需要在Beans.xml中指定构造参数的类型(type)和值(value):
<bean id="simpleBeanC" class="com.xx.DI_demo.SimpleBeanC">
<constructor-arg type="java.lang.String" value="张三"/>
<constructor-arg type="int" value="18"/>
</bean>
- 正确的参数匹配
观察下面这个类,它的构造函数中有两个类型一样的参数:
package com.xx.DI_demo;
public class SimpleBeanD {
private int width;
private int length;
public SimpleBeanD(int width, int length) {
this.width = width;
this.length = length;
}
}
问题是:怎么在Beans.xml中配置是的参数能正确匹配上?
为了解决这个问题,constructor-arg 标签给我们提供了许多属性来定位一个参数:
- type - 参数类型
- index - 参数索引,从0开始
- name - 参数名
还有两个属性用来赋值:
- ref - 引用一个bean
- value - 直接赋值
举例:
<bean id="simpleBeanD" class="com.xx.DI_demo.SimpleBeanD">
<constructor-arg index="0" value="20"></constructor-arg>
<constructor-arg index="1" value="10"></constructor-arg>
</bean>
c-namespace
Spring 3.1中引入的c-namespace允许使用内联属性来配置构造函数参数,而不是嵌套constructor-arg元素。
使用c方法可以解决构造参数众多时的代码庞杂问题。
- 示例:
注意要给beans加上c命名空间的支持:
xmlns:c="http://www.springframework.org/schema/c"
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
<!-- traditional declaration with optional argument names -->
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg name="thingTwo" ref="beanTwo"/>
<constructor-arg name="thingThree" ref="beanThree"/>
<constructor-arg name="email" value="something@somewhere.com"/>
</bean>
<!-- 使用c方法来给构造函数参数赋值,效果和上面一致 -->
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>
</beans>
标准的格式是 c:变量名=值
,如果要表示引用,则使用格式c:变量名-ref="bean id"
。
基于setter方法的依赖注入
当容器调用一个无参的构造函数或一个无参的静态 factory 方法来初始化你的 bean 后,通过容器在你的 bean 上调用setter函数,基于setter函数的 DI 就完成了。
这种方法默认使用的是bean的无参构造方法。
综上,我们的POJO必须有无参构造方法,所有成员的get和set方法:
package com.xx.DI_demo;
public class SimpleBeanE {
private SimpleBeanF simpleBeanF;
public SimpleBeanE() {
}
public SimpleBeanF getSimpleBeanF() {
return simpleBeanF;
}
public void setSimpleBeanF(SimpleBeanF simpleBeanF) {
this.simpleBeanF = simpleBeanF;
}
}
Beans.xml
<bean id="simpleBeanE" class="com.xx.DI_demo.SimpleBeanE">
<property name="simpleBeanF" ref="simpleBeanF"></property>
</bean>
<bean id="simpleBeanF" class="com.xx.DI_demo.SimpleBeanF"/>
- 基于构造函数注入和基于设值函数注入中的 Beans.xml 文件的区别?
——唯一的区别就是在基于构造函数注入中,我们使用的是〈bean〉标签中的〈constructor-arg〉元素,而在基于设值函数的注入中,我们使用的是〈bean〉标签中的〈property〉元素。
内部bean
和java中的内部类相似,用的不多,直接上代码:
<bean id="simpleBeanE" class="com.xx.DI_demo.SimpleBeanE">
<property name="simpleBeanF">
<bean id="simpleBeanF" class="com.xx.DI_demo.SimpleBeanF"/>
</property>
</bean>
不需要使用ref,而且其他Bean也可以引用simpleBeanF。
p-namespace
虽然使用property标签可以快速的完成DI,但是当bean中有非常多的属性需要DI时,使用property就显得非常麻烦。这个时候可以考虑p空间的注入方式:
- 步骤一:在beans标签上添加命名空间:
xmlns:p="http://www.springframework.org/schema/p"
- 步骤二:注入依赖
<bean id="simpleBeanF" class="com.xx.DI_demo.SimpleBeanF"/>
<bean id="simpleBeanG" class="com.xx.DI_demo.SimpleBeanG"
p:age="19" p:simpleBeanF-ref="simpleBeanF"/>
标准的格式是 p:变量名=值
,如果要表示引用,则使用格式p:变量名-ref="bean id"
。
循环依赖的解决方法
- 什么叫循环依赖?
——A依赖B,B又依赖A。那么容器初始化时就会抛出
BeanCurrentlyInCreationException
的异常。注意,循环依赖在是使用构造函数DI的前提下发生的。
- 如何解决?
——使用setter方法来DI。
注入集合类
如果你想在xml中传递Java Collection 类型 List、Set、Map 和 Properties给构造方法或者给属性赋值的话,Spring提供了四种类型的集合的配置元素:
<list>
<set>
<map>
<props>
使用示例:
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- results in a setAdminEmails(java.util.Properties) call -->
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.org</prop>
<prop key="support">support@example.org</prop>
<prop key="development">development@example.org</prop>
</props>
</property>
<!-- results in a setSomeList(java.util.List) call -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!-- results in a setSomeMap(java.util.Map) call -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key ="a ref" value-ref="myDataSource"/>
</map>
</property>
<!-- results in a setSomeSet(java.util.Set) call -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>
注入null
- 正确的注入null
<bean id="simpleBeanE" class="com.xx.DI_demo.SimpleBeanE">
<property name="simpleBeanF">
<null/>
</property>
</bean>
- 错误的注入null
<bean id="simpleBeanE" class="com.xx.DI_demo.SimpleBeanE">
<property name="simpleBeanF" value=""></property>
</bean>
相当于给属性赋了空字符串的值而不是null。
自动装配
我们已经介绍了使用<bean>
元素来声明 bean 和通过使用 XML 配置文件中的<constructor-arg>
和<property>
元素来注入依赖 。
Spring 容器可以在不使用<constructor-arg>
和<property>
元素的情况下自动装配相互协作的 bean 之间的关系,这有助于减少编写一个大的基于 Spring 的应用程序的 XML 配置的数量。
- 如何理解自动装配?
——自动装配的目的也是为bean注入依赖,只要我们定义bean是遵守一定的规范,容器就可以自动的为它注入所需要的依赖项。
- java中有几种装配机制?分别是什么?
——Spring中bean有三种装配机制,分别是:
- 在xml中显示配置;
- 在java中显示配置;
- 隐式的bean发现机制和自动装配。
- 如何启动自动装配?
——使用
<bean>
元素的 autowire 属性为一个 bean 定义指定自动装配模式。
- 自动装配有几种模式?分别是什么?
——五种。
模式 | 描述 |
---|---|
no | 这是默认的设置,它意味着没有自动装配。 |
byName | 由属性名自动装配。Spring 容器看到在 XML 配置文件中 bean 的自动装配的属性设置为 byName。然后尝试匹配,并且将它的属性与在配置文件中被定义为相同名称的 beans 的属性进行连接。 |
byType | 由属性数据类型自动装配。Spring 容器看到在 XML 配置文件中 bean 的自动装配的属性设置为 byType。然后如果它的类型匹配配置文件中的一个确切的 bean 名称,它将尝试匹配和连接属性的类型。如果存在不止一个这样的 bean,则一个致命的异常将会被抛出。 |
constructor | 类似于 byType,但该类型适用于构造函数参数类型。如果在容器中没有一个构造函数参数类型的 bean,则一个致命错误将会发生。 |
autodetect | Spring首先尝试通过 constructor 使用自动装配来连接,如果它不执行,Spring 尝试通过 byType 来自动装配。 |
使用byType和constructor模式可以连接数组和集合类。
- 自动装配有什么局限性?
——使用自动装配有一定的局限性和缺陷:
- 显式依赖项property和constructor-arg设置始终覆盖自动装配。
- 不能自动装配简单属性,比如int,String,等等。
- 自动装配不如显式装配精确,所以如果可能的话尽可能使用显式装配。
- 可能无法为可能从Spring容器生成文档的工具提供连接信息
byName自动装配
这种模式由属性名称指定自动装配。步骤大概分为三步:
- 在 XML 配置文件中 beans 的 auto-wire 属性设置为 byName。
- 容器将在 bean 的属性与配置文件中定义为相同名称的 bean 进行匹配和连接。
- 如果找到匹配项,它将注入这些 beans,否则,它将抛出异常。
我们按照这个思路来测试:
- SimpleBeanA 的依赖项是SimpleBeanB ,提供setter方法和无参构造方法。写一个测试方法打印一下属性simpleBeanB看看是否能自动装配上。
package com.xx.DI_demo;
public class SimpleBeanA {
private SimpleBeanB simpleBeanB;
public SimpleBeanA() {
}
public SimpleBeanB getSimpleBeanB() {
return simpleBeanB;
}
public void setSimpleBeanB(SimpleBeanB simpleBeanB) {
this.simpleBeanB = simpleBeanB;
}
public void doTest() {
System.out.println("this is A -- " + this.simpleBeanB);
}
}
- Beans.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--autowire属性用来配置自动装配模式,我们设置它为byName-->
<!-- 我们并没有人为的通过构造方法或者setter方法为它注入依赖 -->
<bean id="simpleBeanA" class="com.xx.DI_demo.SimpleBeanA" autowire="byName"/>
<bean id="simpleBeanB" class="com.xx.DI_demo.SimpleBeanB"/>
</beans>
- 测试类:
package com.xx.main;
import com.xx.DI_demo.SimpleBeanA;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test_DI_Autowire {
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
SimpleBeanA beanA = context.getBean(SimpleBeanA.class);
beanA.doTest();
// 关闭容器
context.registerShutdownHook();
}
}
- 程序执行结果:
this is A -- com.xx.DI_demo.SimpleBeanB@25bbe1b6
- 结论:
我们使用自动装配byName模式,自动的为beanA注入了依赖。
byType自动装配
这种模式由属性类型指定自动装配。
- 在 XML 配置文件中 beans 的 autowire 属性设置为 byType。
- 如果beand的依赖项的 type 恰好与配置文件中 beans 名称中的一个相匹配,它将尝试匹配和连接它的属性。
- 如果找到匹配项,它将注入这些 beans,否则,它将抛出异常。
这里不再一一演示,我们看一下Beans.xml是如何配置的就可以:
<bean id="simpleBeanA" class="com.xx.DI_demo.SimpleBeanA" autowire="byType"/>
<bean id="simpleBeanB" class="com.xx.DI_demo.SimpleBeanB"/>
同样的,SimpleBeanA的依赖项是SimpleBeanB,那么容器也会自动装配。
constructor自动装配
这种模式与 byType 非常相似,但它应用于构造器参数。
- 在 XML 配置文件中 beans 的 autowire 属性设置为 constructor。
- 容器尝试把bean的构造函数的参数与配置文件中 beans 名称中的一个进行匹配和连线。
- 如果找到匹配项,它会注入这些 bean,否则,它会抛出异常。
使用这种自动装配要求我们的bean提供有参构造方法。
- Beans.xml:
<bean id="simpleBeanA" class="com.xx.DI_demo.SimpleBeanA" autowire="constructor"/>
<bean id="simpleBeanB" class="com.xx.DI_demo.SimpleBeanB"/>
基于注解的容器配置
- Annotation or XML ?
——Spring2.5开始支持注解配置容器,这是一种颇具争议性的机制,长久以来,Annotation Or XML? 的问题一致没有一个标准的答案。二者各有各的优劣,基于注解的配置代码量少,效率高,但是基于XML的配置可见性高,更利于管理。Spring对这两者机制完美的支持,你甚至可以混合使用它们。至于到底使用哪种方法来支持你的项目,应该由开发者自己的偏好决定。不过,按照现阶段的发展趋势,使用注解配置的开发者越来越多,也越来越被人们认可。
- 注解和XML的配置混合使用要注意什么?
——一般不建议二种配置方式混合使用,但是,如果你非要这样做,那么就要注意,注解配置会先于XML配置执行,也就是说,基于注解的配置有可能会被XML的配置覆盖掉。
- 如何开启注解配置?
——注解配置默认不开启,想使用的haul需要在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
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-->
<context:component-scan base-package="包名"/>
</beans>
配置后,你就可以开始注解你的代码,表明 Spring 应该自动连接值到属性,方法和构造函数。
<context:annotation-config/>
仅查找在定义它的同一应用程序上下文中的bean上的注释。
- 几个重要的注解
序号 | 注解 | 描述 |
---|---|---|
1 | @Required | @Required 注解应用于 bean 属性的 setter 方法。 |
2 | @Autowired | @Autowired 注解可以应用到 bean 属性的 setter 方法,非 setter 方法,构造函数和属性。 |
3 | @Qualifier | 通过指定确切的将被连线的 bean,@Autowired 和 @Qualifier 注解组合使用可以用来去歧。 |
4 | JSR-250 Annotations | Spring 支持 JSR-250 的基础的注解,其中包括了 @Resource,@PostConstruct 和 @PreDestroy 注解。 |
@Required
@Required 注解应用于 bean 属性的 setter 方法。它表明受影响的 bean 属性在配置时必须放在 XML 配置文件中,否则容器就会抛出一个BeanInitializationException
异常。
示例:
package com.xx.DI_demo;
import org.springframework.beans.factory.annotation.Required;
public class SimpleBeanA {
private SimpleBeanB simpleBeanB;
public SimpleBeanA() {
}
public SimpleBeanB getSimpleBeanB() {
return simpleBeanB;
}
@Required
public void setSimpleBeanB(SimpleBeanB simpleBeanB) {
this.simpleBeanB = simpleBeanB;
}
}
@Required注释从Spring Framework 5.1开始正式被弃用,被后置处理器所取代。
@Autowired
@Autowired 注解可以应用到 bean 属性的 setter 方法,非 setter 方法,构造函数和属性。
- Setter 方法中的 @Autowired
当 Spring遇到一个在 setter 方法中使用的 @Autowired 注释,它会执行 byType 自动装配。
- 属性中的 @Autowired
在属性中使用 @Autowired 注释表示这个属性是必须配置的,而且省去setter方法,容器执行 byType 自动装配。
- 构造函数中的 @Autowired
构造函数中的参数会被自动装配(byType)
- 普通方法中的 @Autowired
方法中的形式参数会被自动装配(byType)
问题汇总:
- 可以混合注解吗?
——可以。但是重复了的属性注解没有必要这么做。一个实例:
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
private MovieCatalog movieCatalog;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
- 在数组和集合类类型的属性上使用@Autowired会发生什么?
——容器中符合类型的所有bean都会被自动装配。实例:
SimpleBeanH
package com.xx.DI_demo;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Arrays;
public class SimpleBeanH {
// 注释属性不需要再提供setter方法
@Autowired
private SimpleBeanI[] simpleBeanI;
public SimpleBeanH() {
}
public void doTest() {
System.out.println("this is h -- " + this);
}
@Override
public String toString() {
return "SimpleBeanH{" +
"simpleBeanI=" + Arrays.toString(simpleBeanI) +
'}';
}
}
Beans.xml:配置了两个SimpleBeanI类型的bean
<?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:p="http://www.springframework.org/schema/p" 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/>
<context:component-scan base-package="com.xx.DI_demo"/>
</beans>
测试类
package com.xx.main;
import com.xx.DI_demo.SimpleBeanH;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test_DI_Autowire {
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
SimpleBeanH bean = context.getBean(SimpleBeanH.class);
bean.doTest();
context.registerShutdownHook();
}
}
程序运行结果
this is h -- SimpleBeanH{simpleBeanI=[com.xx.DI_demo.SimpleBeanI@9a7504c, com.xx.DI_demo.SimpleBeanI@2c039ac6]}
结论:打印出了两个SimpleBeanI类型的对象,符合预期猜测。这种情况同样适用于集合。
- @Autowired和@Required的联系?
——@Autowired 注释意味着依赖是必须的,它类似于 @Required 注释。然而,你可以使用
@Autowired 的 (required=false)
选项关闭默认行为。
@Autowired(required=false)
public void setAge(Integer age) {
this.age = age;
}
- 如果容器中有多个类型匹配的bean,如何装配?
——@Autowired默认的装配模式是byType,如果匹配到了多个bean,就会根据byName模式再次匹配,如果匹配不到就会报错。
示例:
Beans.xml中我们配置了多个SimpleBeanI类型的bean,其中一个名叫simpleBeanI1,一个名叫simpleBeanI2:
<?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:p="http://www.springframework.org/schema/p" 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="simpleBeanH" class="com.xx.DI_demo.SimpleBeanH" ></bean>
<bean id="simpleBeanI1" class="com.xx.DI_demo.SimpleBeanI" ></bean>
<bean id="simpleBeanI2" class="com.xx.DI_demo.SimpleBeanI" ></bean>
</beans>
SimpleBeanH 依赖于SimpleBeanI ,这里的属性名是simpleBeanI(容器中没有)
package com.xx.DI_demo;
import org.springframework.beans.factory.annotation.Autowired;
public class SimpleBeanH {
// 注释属性不需要再提供setter方法
@Autowired
private SimpleBeanI simpleBeanI;
public SimpleBeanH() {
}
public void doTest() {
System.out.println("this is h -- " + this);
}
@Override
public String toString() {
return "SimpleBeanH{" +
"simpleBeanI=" + simpleBeanI +
'}';
}
}
测试类:
package com.xx.main;
import com.xx.DI_demo.SimpleBeanA;
import com.xx.DI_demo.SimpleBeanH;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test_DI_Autowire {
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
SimpleBeanH bean = context.getBean(SimpleBeanH.class);
bean.doTest();
context.registerShutdownHook();
}
}
运行结果:报错
修改Beans.xml,把其中的一个SimpleBeanI的bean的名字改为和SimpleBeanH中的属性名一致,使得byName装配正常执行:
this is h -- SimpleBeanH{simpleBeanI=com.xx.DI_demo.SimpleBeanI@60285225}
我们接下来介绍的注解@Qualifier就是专门来解决这个问题的。
@Qualifier
可能会有这样一种情况,当你创建多个具有相同类型的 bean 时,并且想要用一个属性只为它们其中的一个进行装配,在这种情况下,你可以使用 @Qualifier 注释和 @Autowired 注释通过指定哪一个真正的 bean 将会被装配来消除混乱。
还是上面的例子,我们修改SimpleBeanH使用@Qualifier(“bean name”)的方法就可解决:
package com.xx.DI_demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
public class SimpleBeanH {
// 注释属性不需要再提供setter方法
@Autowired
@Qualifier("simpleBeanI2") // 注意这里
private SimpleBeanI simpleBeanI;
public SimpleBeanH() {
}
public void doTest() {
System.out.println("this is h -- " + this);
}
@Override
public String toString() {
return "SimpleBeanH{" +
"simpleBeanI=" + simpleBeanI +
'}';
}
}
你也可以在Beans.xml中指定primary属性,容器会优先匹配它,也可以解决这个问题,但推荐使用上面的方法:
<?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:p="http://www.springframework.org/schema/p" 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="simpleBeanH" class="com.xx.DI_demo.SimpleBeanH" ></bean>
<!--多类型优先匹配-->
<bean id="simpleBeanI" class="com.xx.DI_demo.SimpleBeanI" primary="true"></bean>
<bean id="simpleBeanI2" class="com.xx.DI_demo.SimpleBeanI" ></bean>
</beans>
JSR-250 注释
Spring还使用基于 JSR-250 注释,它包括 @PostConstruct, @PreDestroy 和 @Resource 注释。
@Resource注释的用法和@Autowired类似,你可以在字段中或者 setter 方法中使用 @Resource 注释,不同的是,它遵循的是byName的自动装配模式。
@PostConstruct 和 @PreDestroy 注释分别来替代我们初始化回调方法 init-method 和销毁回调方法 destroy-method。
示例:
package com.xx.helloworld
import javax.annotation.*;
public class HelloWorld {
private String message;
public void setMessage(String message){
this.message = message;
}
public String getMessage(){
System.out.println("Your Message : " + message);
return message;
}
@PostConstruct
public void init(){
System.out.println("Bean is going through init.");
}
@PreDestroy
public void destroy(){
System.out.println("Bean will destroy now.");
}
}
Spring基于java的配置
如何使用 XML 配置文件来配置 Spring bean我们基本上已经掌握了,你可以使用基于java的配置完全的代替。
上篇博文已经提到了几个注释,比如@Configuration、@Bean、@Import 、 @Scope等等。
我们自己写一个基于java的配置来用一下它们,更详细的使用以后写:
- 配置类
package com.xx.config;
import com.xx.DI_demo.SimpleBeanH;
import com.xx.DI_demo.SimpleBeanI;
import org.springframework.context.annotation.*;
@Configuration // 相当于<beans>
@ComponentScan(basePackages = "com.xx") // 启动组件扫描
@Import(ServiceConfig.class) // 引入别的配置,就可以引用其中的bean
public class MyAppConfig {
// 使用@Bean注解的方法相当于一个bean,bean的id|name就是方法名
// 可以指定生命周期回调方法
@Bean(initMethod = "myInit", destroyMethod = "myDestroy")
public SimpleBeanH simpleBeanH() {
// 使用有参构造方法,就相当于DI,通过这种方法表达beans之间的依赖性
return new SimpleBeanH(simpleBeanI());
}
// SimpleBeanH类依赖于simpleBeanI
@Bean
@Scope("prototype") //@Scope用来指定作用域
public SimpleBeanI simpleBeanI() {
return new SimpleBeanI();
}
}
- SimpleBeanH
package com.xx.DI_demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
public class SimpleBeanH {
// 注释属性不需要再提供setter方法
private SimpleBeanI simpleBeanI;
public SimpleBeanH(SimpleBeanI simpleBeanI) {
this.simpleBeanI = simpleBeanI;
}
public void doTest() {
System.out.println("this is h -- " + this);
}
@Override
public String toString() {
return "SimpleBeanH{" +
"simpleBeanI=" + simpleBeanI +
'}';
}
private void myInit() {
System.out.println("init...");
}
private void myDestroy() {
System.out.println("myDestroy...");
}
}
- 测试类:
package com.xx.main;
import com.xx.DI_demo.SimpleBeanH;
import com.xx.config.MyAppConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Test_Annotation {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 加载配置,可以加载多个配置,与之类似
context.register(MyAppConfig.class);
context.refresh();
SimpleBeanH bean = context.getBean(SimpleBeanH.class);
bean.doTest();
}
}
- 运行结果
init...
this is h -- SimpleBeanH{simpleBeanI=com.xx.DI_demo.SimpleBeanI@351d0846}
myDestroy...
- 如果把bean的作用域设置为prototype(原型)那么关闭容器后不会调用销毁方法,因为此时容器并不会自动销毁这个bean的对象,而把它交给了开发者管理。练习时要注意。