1.Spring容器高层视图
Spring支持多种形式的Bean配置形式。Spring1.0仅支持基于XML的配置,Spring2.0新增基于注解配置的支持,Spring3.0新增基于Java类配置的支持,Spring4.0新增基于Groovy动态语言配置的支持。
Bean配置信息是Bean的元数据信息,由四个方面组成
- Bean的实现类
- Bean的属性信息,如数据源的连接数、用户名、密码等
- Bean的依赖关系,Spring根据依赖关系配置完成Bean之间的装配。
- Bean的行为配置,如生命周期范围及生命周期各过程的回调函数等。
2.基于XML的配置
对于基于XML的配置,Spring1.0的配置文件采用DTD格式,Spring2.0以后采用Schema格式,后者让不同类型的配置拥有了自己的命名空间,使得配置文件更具有扩展性。Spring基于Schema配置方案为许多领域的问题提供了简化的配置方法,配置工作得到了大幅简化。
命名空间的定义分为两个步骤:
- 第一步指定命名空间的名称:需要指定命名空间的缩略名和全名
xmlns:aop="http://www.springframework.org/schema/aop"
aop为命名空间的别名,而http://www.springframework.org/schema/aop为空间的全限定名。如果命名空间的别名为空,那么表示该命名空间为文档默认命名空间。
- 第二步指定命名空间的Schema文档格式文件的位置,用空格或回车换行进行分割。
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
语法如下:<命名空间1>空格<命名空间1Schema文件>空格<命名空间2>空格<命名空间2Schema文件>空格<命名空间3>空格<命名空间3Schema文件>
指定命名空间的Schema文件地址有两个用途:一:XML解析器可以获取Schema文件并对文档进行格式合法性验证;二:在开发环境下,IDE可以引用Schema文件对文档编辑提供诱导功能(自动补全)
2.1Bean基本配置
装配一个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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
<bean id="car" name="#car1" class="com.smart.simple.Car"></bean>
<bean id="boss" class="com.smart.simple.Boss"></bean>
</beans>
id为bean的名称,通过容器的getBean("boss")即可获取对应的Bean,在容器中起到定位查找的作用,是外部程序和SpringIoC容器进行交互的桥梁。class属性指定了Bean对应的实现类。
3.依赖注入
Spring支持两种依赖注入方式,分别是属性注入和构造函数注入。除此之外,Spring还支持工厂方式注入方式。
3.1属性注入
属性注入指通过setXxx(xxx)方法注入Bean的属性值或依赖对象。由于属性注入方式具有可选择性和灵活性高的优点,因此属性注入是实际应用中最常采用的注入方式。
- 属性注入实例
package com.smart.ditype;
public class Car {
public String brand;
private String corp;
private double price;
private int maxSpeed;
public Car() {}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public int getMaxSpeed() {
return maxSpeed;
}
public void setMaxSpeed(int maxSpeed) {
this.maxSpeed = maxSpeed;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public String toString(){
return "brand:"+brand+"/maxSpeed:"+maxSpeed+"/price:"+price;
}
}
xml配置
<bean id="car" class="com.smart.ditype.Car">
<property name="brand" value="红旗&CA72"/>
<property name="maxSpeed" value="200"/>
<property name="price" value="20000.00"/>
</bean>
上述代码配置了一个Bean,并为该Bean的3个属性提供了属性值。Bean的每一个属性对应一个
Spring只会检查Bean中是否有对应的Setter()方法,至于Bean中是否有对应的属性成员变更则不作要求。譬如配置文件中
2. JavaBean关于属性命名的特殊规范
JavaBean允许以大写字母开头的属性变量名,不过必须满足“变量的前两个字母要么全部大写,要么全部小写”。
3.2构造函数注入
它保证一些必要的属性在Bean实例化时就得到设置,确保Bean在实例化后就可以使用。
- 按类型匹配入参
使用属性注入方式,只能人为地在配置时提供保证而无法在语法级提供保证,通过构造函数就可以很好地满足这一要求。使用构造函数注入的前提是Bean必须提供带参的构造函数。
public class Car {
public String brand;
private double price;
public Car(String brand, double price) {
this.brand = brand;
this.price = price;
}
}
构造函数注入的配置方式和属性注入的配置方式有所不同,构造函数的xml配置如下:
<!--构造函数注入:type -->
<bean id="car1" class="com.smart.ditype.Car">
<constructor-arg type="java.lang.String">
<value>红旗CA72</value>
</constructor-arg>
<constructor-arg type="double">
<value>20000</value>
</constructor-arg>
</bean>
Spring的配置文件采用和元素标签顺序无关的策略,这种策略在一定程度上可以保证配置信息的确定性。但如果构造函数有两个类型相同的入参,那么仅通过type就无法确定对应关系了,这时需要通过入参索引的方式进行确定。
2. 按索引匹配入参
在属性注入时,Spring可以使用Java反射机制调用setter方法完成属性注入。但java反射机制并不会记住构造函数的入参名,因此无法通过指定构造函数的入参名进行构造函数注入的配置,只能通过入参类型和索引信息间接确定构造函数配置项和入参的对应关系。
//该构造函数第一、第二入参都是String类型
public Car(String brand, String corp, int maxSpeed) {
this.brand = brand;
this.corp = corp;
this.maxSpeed = maxSpeed;
}
xml配置
<!-- 构造函数注入:index -->
<bean id="car2" class="com.smart.ditype.Car">
<constructor-arg index="0" value="红旗CA72" />
<constructor-arg index="1" value="中国一汽" />
<constructor-arg index="2" value="20000" />
</bean>
- 联合使用类型和索引匹配入参
public Car(String brand, String corp, double price) {
this.brand = brand;
this.corp = corp;
this.price = price;
}
public Car(String brand, String corp, int maxSpeed) {
this.brand = brand;
this.corp = corp;
this.maxSpeed = maxSpeed;
}
xml配置
<!--构造函数注入:type&index -->
<bean id="car3" class="com.smart.ditype.Car">
<constructor-arg index="0" type="java.lang.String">
<value>红旗CA72</value>
</constructor-arg>
<constructor-arg index="1" type="java.lang.String">
<value>中国一汽</value>
</constructor-arg>
<constructor-arg index="2" type="int">
<value>200</value>
</constructor-arg>
</bean>
对于因参数数目相同而类型不同引起的潜在配置歧义问题,Spring容器可以正确启动且不会给出报错信息,它将随机采用一个匹配的构造函数实例化Bean,而被选择的构造函数可能不是用户期望的那个。因此,必须特别谨慎,避免潜在问题。
4. 通过自身类型反射匹配入参
如果Bean构造函数入参的类型是可辨识的(非基础数据类型且入参类型各异),由于Java反射机制可以获取构造函数入参的类型,及时构造函数注入的配置不提供类型和索引信息,Spring依旧可以正确地完成构造函数的注入工作。例如:
public Boss(String name, Car car, Office office) {
this.name = name;
this.car = car;
this.office = office;
}
由于car office name入参类型都是可辨识的,所以无需再构造函数配置时指定
<!--构造函数注入:自动识别入参类型 -->
<bean id="boss1" class="com.smart.ditype.Boss">
<constructor-arg>
<value>John</value>
</constructor-arg>
<constructor-arg>
<ref bean="car" />
</constructor-arg>
<constructor-arg>
<ref bean="office" />
</constructor-arg>
</bean>
如果Bean存在多个构造函数,那么使用显示知道指定index和type属性是一种良好的习惯。
- 循环依赖问题
Spring能够对构造函数配置的Bean进行实例化有一个前提,即Bean构造函数入参引用的对象必须已经准备就绪。由于这个机制的限制,如果两个Bean都采用构造函数注入,而且都通过构造函数入参引用对方,就会发横类似于线程死锁的问题。
public class Car {
public String brand;
private String corp;
private double price;
private int maxSpeed;
private Boss boss;
public Car() {}
public Car(String brand, double price) {
this.brand = brand;
this.price = price;
}
public Car(String brand, Boss boss) {
this.brand = brand;
this.boss = boss;
}
}
public class Boss {
private String name;
private Car car;
private Office office;
public Boss() {
}
public Boss(String name, Car car) {
this.name = name;
this.car = car;
}
}
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
<bean id="car" class="com.smart.cons.Car">
<constructor-arg index="0" type="java.lang.String" value="BMW"/>
<constructor-arg index="1" type="com.smart.cons.Boss" ref="boss"/>
</bean>
<bean id="boss" class="com.smart.cons.Boss">
<constructor-arg index="0" type="java.lang.String" value="huanhuan"/>
<constructor-arg index="1" type="com.smart.cons.Car" ref="car"/>
</bean>
</beans>
运行结果:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'car' defined in class path resource [com/smart/cons/beans.xml]:
Cannot resolve reference to bean 'boss' while setting constructor argument;
nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'boss' defined in class path resource [com/smart/cons/beans.xml]: Cannot resolve reference to bean 'car' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'car': Requested bean is currently in creation: Is there an unresolvable circular reference?
当启动SpringIoC容器时,因为存在循环依赖的问题,Spring容器无法成功启动。解决方式:将构造函数注入方式调整为属性注入方式就可以了。
改变其中一个方式即可,但是要注意加载顺序,正确的顺序:
<bean id="car" class="com.smart.cons.Car">
<property name="brand" value="BMW"/>
<property name="boss" ref="boss"/>
</bean>
<bean id="boss" class="com.smart.cons.Boss">
<constructor-arg index="0" type="java.lang.String" value="huanhuan"/>
<constructor-arg index="1" type="com.smart.cons.Car" ref="car"/>
</bean>
下面的配置依然会报错:
<bean id="car" class="com.smart.cons.Car">
<property name="brand" value="BMW"/>
<property name="boss" ref="boss"/>
</bean>
<bean id="boss" class="com.smart.cons.Boss">
<constructor-arg index="0" type="java.lang.String" value="huanhuan"/>
<constructor-arg index="1" type="com.smart.cons.Car" ref="car"/>
</bean>
SpringIoC容器加载的顺序和配置文件的顺序一致。
3.3工厂方法注入
- 非静态工厂方法
public class CarFactory {
public Car createHongQiCar(){
Car car = new Car();
car.setBrand("红旗CA72");
return car;
}
}
<!-- 工厂方法-->
<bean id="carFactory" class="com.smart.ditype.CarFactory" />
<bean id="car5" factory-bean="carFactory" factory-method="createHongQiCar">
</bean>
- 静态工厂方法
public class CarFactory {
public static Car createCar(){
Car car = new Car();
return car;
}
}
xml配置
<bean id="car6" class="com.smart.ditype.CarFactory"
factory-method="createCar"></bean>
4.注入参数详解
在Spring配置文件中,用户不但可以将String,int等字面值注入Bean,还可以将集合、map等类型的数据注入Bean中,此外还可以注入配置文件中其他定义的Bean
4.1字面值
一般指可以用字符串表示的值,这些值可以通过
String允许用户用户注册自定义的编辑器,以处理其他类型属性注入时的转换工作(见下篇)
- 字面值注入
bean id="car" class="com.smart.attr.Car" lazy-init="default">
<property name="brand">
<value>
<![CDATA[红旗&CA72]]>
</value>
</property>
<property name="maxSpeed">
<value>200</value>
</property>
<property name="price" value="2000.00" />
</bean>
XML标签中共有5个特殊字符,分别是&、<、>、“、’。如果配置文件中的注入值包括这些特殊字符,需要进行特殊处理。
其一:采用![CDATA[ ]] 特殊标签
其二:使用XML转义序列表示这些特殊字符
特殊符号 | 转义序列 |
---|---|
< | < |
> | > |
& | & |
“ | " |
‘ | ' |
一般情况下,XML解析器会忽略元素标签内部字符串的前后空格,但Spring却不会忽略元素标签内部字符串的前后空格。如果属性值中有空格,Spring会将“红旗红旗&CA72”连同前后空格一起赋给brand属性。
4.2引用其他Bean
SpringIoC容器中定义的Bean可以相互引用,IoC容器充当“红娘”的角色。
public class Boss {
private Car car;
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
}
<!-- 引用Bean -->
<bean id="boss1" class="com.smart.attr.Boss">
<property name="car" ref="car" />
</bean>
- bean:通过该属性可以引用同一容器中或父容器中的Bean,是最常见的形式。
- local:通过该属性只能引用同一配置文件中定义的Bean,它可以利用XML解析器自动检验引用的合法性,以便开发人员在编写配置时能够及时发现并纠正配置错误。
- parent:引用父容器中的Bean,如的配置说明car的Bean是父容器中的Bean。
父容器引用实例
bean1.xml
<!-- 在父容器中定义的Car -->
<bean id="car" class="com.smart.attr.Car">
<property name="maxSpeed">
<value>200</value>
</property>
<property name="brand">
<value>
<![CDATA[红旗&CA72]]>
</value>
</property>
<property name="price" value="2000.00" />
</bean>
bean2.xml
<!-- 该Bean和父容器的Car Bean具有相同的ID-->
<bean id="car" class="com.smart.attr.Car">
<property name="brand" value="吉利CT5" />
<property name="maxSpeed" value="100" />
<property name="price" value="1000.00" />
</bean>
<bean id="boss" class="com.smart.attr.Boss">
<property name="car">
<!-- 采用父容器中的car -->
<ref parent="car" />
</property>
</bean>
test
public class ParentContainerBeanTest {
@Test
public void parent(){
ClassPathXmlApplicationContext pFactory = new ClassPathXmlApplicationContext(new String[]{"com/smart/attr/beans1.xml"});
ApplicationContext factory = new ClassPathXmlApplicationContext(new String[]{"com/smart/attr/beans2.xml"},pFactory);
Boss boss = (Boss)factory.getBean("boss");
assertNotNull(boss);
System.out.println(boss.getCar().toString());
}
}
引用的父容器的Bean
4.3内部Bean
如果car Bean只能被boss Bean引用,而不被容器中任何其他的Bean引用,可以将car以内部bean的方式注入Boss中。
<!-- 内部Bean -->
<bean id="boss2" class="com.smart.attr.Boss">
<property name="car">
<bean class="com.smart.attr.Car">
<property name="maxSpeed" value="200" />
<property name="price" value="2000.00" />
</bean>
</property>
</bean>
内部Bean和Java的匿名内部类相似,既没有名字也不能被其他Bean引用。内部Bean提供id,name,scope属性,也会被忽略,scope默认为prototype类型。
4.4null值
<property name="brand"><null/></property>
<!-- = car.setBrand(null) -->
4.5级联属性
假设希望在定义Boss时,直接为Car的属性提供注入值,可以采取以下配置
<bean id="boss3" class="com.smart.attr.Boss">
<property name="car.brand" value="红旗" />
</bean>
按照上面的配置,Spring将调用Boss.getCar().setBrand("红旗&CA72")方法进行属性的注入操作。这是必须为Boss类改造,为car属性声明一个初始化对象。
public class Boss {
private Car car = new Car();
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
}
Spring没有对级联属性的级联层数进行限制,只需配置的bean拥有对应于级联属性的类结构,就可以配置任意层数的级联属性。
4.6集合类型属性
java.util 包中的集合类型是最常用的数据结构类型,主要包括 List、Set、Map、Propertie.Spring为这些集合类型属性提供了专属的配置标签。
- List
为Boss添加一个List类型的favorite属性,如下
private List favorites = new ArrayList();
public List getFavorites() {
return favorites;
}
public void setFavorites(List favorites) {
this.favorites = favorites;
}
对应的xml片段
<bean id="boss1" class="com.smart.attr.Boss">
<property name="car" ref="car" />
<property name="favorites">
<list>
<value>看报</value>
<value>赛车</value>
<value>高尔夫</value>
</list>
</property>
</bean>
List属性既可以通过注入字符串,也可以通过注入容器中其他的bean.
如果一个属性类型可以通过字符串字面值进行配置,那么该类型对应的数组类型的属性(如String[] int[])也可以采用List方式进行配置。
- set
<bean id="boss1" class="com.smart.attr.Boss">
<property name="car" ref="car" />
<property name="favorites">
<set>
<value>看报</value>
<value>赛车</value>
<value>高尔夫</value>
</set>
</property>
</bean>
- map
private Map jobs = new HashMap();
public Map getJobs() {
return jobs;
}
public void setJobs(Map jobs) {
this.jobs = jobs;
}
<bean id="boss1" class="com.smart.attr.Boss">
<property name="jobs">
<map>
<entry >
<key>
<value>AM</value>
</key>
<value>会见客户</value>
</entry>
<entry>
<key>
<value>PM</value>
</key>
<value>公司内部会议</value>
</entry>
</map>
</property>
</bean>
- properties
Properties类型可以看做Map类型特例。Properties属性的键和值都只能是字符串。
private Properties mails = new Properties();
public Properties getMails() {
return mails;
}
public void setMails(Properties mails) {
this.mails = mails;
}
<property name="mails">
<props>
<prop key="jobMail">john-office@smart.com</prop>
<prop key="lifeMail">john-life@smart.com</prop>
</props>
</property>
因为Properties键值对只能是字符串,因此配置比map的配置要简单一些,之一值的配置没有
子元素标签。
- 强类型集合
java5.0提供了强类型集合的新功能,允许为集合元素指定类型。
private Map<String, Integer> jobTime = new HashMap<String, Integer>();
public Map<String, Integer> getJobTime() {
return jobTime;
}
public void setJobTime(Map<String, Integer> jobTime) {
this.jobTime = jobTime;
}
在xml中的配置和非强类型集合相同
<property name="jobTime">
<map>
<entry>
<key>
<value>会见客户</value>
</key>
<value>124</value>
</entry>
</map>
</property>
Spring容器在注入强类型集合时会判断元素的类型,将设置值转换为对应的数据类型。
- 集合合并
Spring支持集合合并的功能,允许子继承父 的同名属性集合元素,并将子中配置的集合属性值和父中配置的同名属性值合并起来作为最终bean的属性值。
<bean id="parentBoss" abstract="true"
class="com.smart.attr.Boss">
<property name="favorites">
<set>
<value>看报</value>
<value>赛车</value>
<value>高尔夫</value>
</set>
</property>
</bean>
<bean id="childBoss" parent="parentBoss">
<property name="favorites">
<!-- 和父bean中的同名集合属性合并-->
<set merge="true">
<value>爬山</value>
<value>游泳</value>
</set>
</property>
</bean>
- 通过util命名空间配置集合类型的bean
4.7简化配置方式
- 字面值属性
- 引用属性属性
的简化形式对应于,而和没有对应的简化形式
3.使用p命名空间
为了简化XML文件的配置,越来越多的XML文件采用属性而非子元素配置信息。Spring从2.5引入了一个新的p命名空间,可以通过元素属性的方式配置bean的属性。
由于p命名空间中的属性名是可变的,所以p命名空间没有对应的Schema定义文件。
在IDEA开发工具中,对Spring配置文件默认提供诱导功能,对于p命名空间的属性配置,主要输入p:就能动态分析出bean类的属性列表。对应其他开发工具,如Eclipse,需要安装SpringIDE for Eclipse插件,并按ALT+/才能诱导。
4.8自动装配
SpringIoC容器知道所有Bean的配置信息,此外,通过java反射机制还可以获知实现类的结构信息,如构造函数方法的结构,属性等信息。掌握所有Bean的这些信息后,SpringIoC容器就可以按照某种规则对容器中的Bean进行自动装配,而无需通过显示的方式进行依赖装配。Spring为厌恶配置的开发人员提供了一种轻松的方法,可以按照某些规则进行Bean的自动装配。
自动装配类型 | 说明 | |
---|---|---|
byName | 根据名称进行自动匹配。假设Boss有一个名为car的属性,如果容器中刚好有一个名为car的Bean ,Spring就会自动将其装配给Boss的car属性 | |
byType | 根据类型进行自动匹配。假设Boss有一个名为Car类型的属性,如果容器中刚好有一个Car类型的Bean ,Spring就会自动将其装配给Boss的这个属性 | |
constructor | 与byType类似,只不过它是针对构造函数注入而言的。如果boss有一个构造函数,构造函数包含一个Car类型的入参,如果容器中有一个Car类型的Bean,则Spring将自动把这个Bean作为Boss构造函数的入参;如果容器中没有找到和构造函数入参匹配类型的Bean,则Spring将抛出异常 | |
autodetect | 根据Bean的自省机制决定采用byTe还是constructor进行自动装配。如果Bean提供了默认的构造函数。则采用byType;否则采用constructor |
在实际开发中,XML配置方式很少启用自动装配功能,而基于注解的装配方式默认采用byType自动装配策略。
5.方法注入
无状态Bean的作用域一般可以配置为singleton(单例模式),如果我们往singleton的Boss中注入prototype的Car,并希望每次调用boss bean 的getCar()方法时都能够返回一个新的car Bean.使用传统的注入方式无法实现这样的要求。因为singleton的Bean注入关联Bean的动作仅有一次,虽然car Bean的作用范围是prototype类型,但Boss通过getCar()方法返回的对象还是最开始注入的那个car Bean.
可选方法让Boss实现BeanFactoryAware接口,且能够访问容器的引用,这样Boss的getCar()方法就可以采取以下方式来达到目的:
public class Boss implements BeanFactoryAware {
private String name;
private Car car;
private Office office;
private BeanFactory beanFactory;
//通过getBean()返回prototype的Bean,每次都将返回新实例
public Car getCar() {
return (Car)beanFactory.getBean("car");
}
public void setCar(Car car) {
this.car = car;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
}
这种依赖Spring框架接口的设计将应用于Spring框架绑定在一起,并不优雅。是否有既不与Spring框架绑定,又可以享受依赖注入好处的实现方案。Spring通过方法注入的方案完美解决这个问题。
5.1lookup方法注入
SpringIoC容器拥有复写Bean方法的能力,归功于CGLib类包。CGLib可以在运行期动态操作Class字节码,为Bean动态创建子类或实现类。
现在生命一个MagicBoss接口,并声明一个getCar()的接口方法。
public interface MagicBoss {
Car getCar();
}
不编写如何实现类,仅通过配置为该接口提供动态实现,让getCar()接口方法每次都返回新的car Bean。
<!-- prototype 类型的Bean -->
<bean id="car" class="com.smart.injectfun.Car"
p:brand="红旗CA72" p:price="2000" scope="prototype"/>
<!-- 实施方法注入 -->
<bean id="magicBoss" class="com.smart.injectfun.MagicBoss" >
<lookup-method name="getCar" bean="car"/>
</bean>
通过lookup-method 元素标签为MagicBoss的getCar()提供动态实现,返回prototype类型的car Bean,这样Spring将在运行期为MagicBoss接口提供动态实现,效果等同于
public class MagicBossImpl implements MagicBoss, ApplicationContextAware {
private ApplicationContext ctx;
@Override
public Car getCar() {
return (Car)ctx.getBean("car");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.ctx = applicationContext;
}
}
lookup方法注入是有一定使用范围的,一般希望通过singleton Bean获取一个protorype Bean时使用。如果将car Bean的作用域设置为singleton,虽然配置仍然可以运行,但这时lookup所提供的方法注入就没什么意义了。
方法注入时Spring需要用到CGLib类包,否则无法使用此方法注入功能。
5.2方法替换
可以使用一个Bean的方法去替换另一个Bean的方法。
Boss1
public class Boss1 implements MagicBoss{
public Car getCar() {
Car car = new Car();
car.setBrand("宝马Z4");
return car;
}
}
Boss2
public class Boss2 implements MethodReplacer {
public Object reimplement(Object arg0, Method arg1, Object[] arg2)
throws Throwable {
Car car = new Car();
car.setBrand("美人豹");
return car;
}
}
用户替换他人的Bean必须实现MethodReplacer接口,Spring将利用该接口的方法去替换目标Bean的方法。
<bean id="boss2" class="com.smart.injectfun.Boss2"/>
<bean id="boss1" class="com.smart.injectfun.Boss1">
<replaced-method name="getCar" replacer="boss2"></replaced-method>
</bean>
这种高级功能就像《宋玉答楚王问》中所说的阳春白雪一样,在实际中应用很少使用,而属性注入、构造函数注入等“下里巴人”式的普通功能反而在实际项目中使用最多。
6.Bean之间的关系
不但可以通过引用另一个Bean,建立起Bean和Bean之间的依赖关系,
6.1继承(key:abstract)
如果多个
未使用父<bean>的配置
<bean id="car1" class="com.smart.tagdepend.Car" p:brand="红旗CA72" p:price="2000.00" p:color="黑色"/>
<bean id="car2" class="com.smart.tagdepend.Car" p:brand="红旗CA72" p:price="2000.00" p:color="红色"/>
使用父<bean>消除重复代码
<!-- 父子<bean> -->
<bean id="abstractCar" class="com.smart.tagdepend.Car"
p:brand="红旗CA72" p:price="2000.00" p:color="黑色"
abstract="true"/>
<bean id="car3" parent="abstractCar">
<property name="color" value="红色"/>
</bean>
<bean id="car4" parent="abstractCar" >
<property name="color" value="白色"/>
</bean>
父的主要功能是简化子的配置,所以一般声明为abstract = "true",表示这个
不实例化为一个对应的Bean,如果用户没有指定abstract = "true",则SpringIoC容器会实例化一个名为abstractCar 的Bean。
6.2依赖(key:depends-on)
public class SystemSettings {
public static int SESSION_TIMEOUT = 30;
public static int REFRESH_CYCLE = 60;
}
提供一个用于修改系统参数的类
public class SysInit {
public SysInit(){
System.out.println("SysInit");
//模拟从数据库中加载系统设置信息
SystemSettings.REFRESH_CYCLE = 100;
SystemSettings.SESSION_TIMEOUT = 10;
}
}
假设有一个缓存刷新管理器,需要根据系统参数SystemSettings.REFRESH_CYCLE 创建刷新定时任务。
public class CacheManager {
public CacheManager(){
Timer timer = new Timer();
TimerTask cacheTask = new CacheTask();
timer.schedule(cacheTask,0,SystemSettings.REFRESH_CYCLE*1000);
}
}
如果3个Bean都在Spring配置文件中定义,如何保证SysInit在CacheManger之前进行初始化
Spring允许通过depends-on 属性显示指定Bean前置依赖的Bean,前置依赖的Bean会在本Bean实例化之前创建好。
<!-- <bean>的依赖 -->
<bean id="cacheManager" class="com.smart.tagdepend.CacheManager" depends-on="sysInit" />
<bean id="sysInit" class="com.smart.tagdepend.SysInit" />
如果前置依赖于多个Bean,可以通过逗号,空格或分号的方式创建Bean的名称。
同样,可以解决配置文件中因为加载顺序导致的Bean依赖不存在的问题
6.3引用(key:idref)
一般情况下,在一个Bean中引用另一个Bean的id是希望在运行期通过getBean(beanName)方法获取对应的Bean。由于Spring并不会在容器启动时对属性配置值进行特殊检查,因此即使编写错误,也要等到具体调用时才会发现。
7.整合多个配置文件
对于多个XML配置文件,在启动Spring容器时,可以通过一个Spring数组指定这些配置文件。Spring还允许通过<import>将多个配置文件引入到一个文件中,进行配置文件的集成。这样,在启动Spring容器时,仅需指定这个合并好的配置文件即可。
8.Bean的作用域
在配置文件中定义Bean时,不但可以配置Bean的属性值及相互之间的依赖关系,还可以定义Bean的作用域。作用域将对Bean的生命周期和创建方式产生影响。
Spring4.0支持的所有作用域类型。
类型 | 说明 | |
---|---|---|
singleton | 在SpringIoC容器中仅存在一个Bean实例,Bean以单实例的方式存在 | |
prototype | 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行new XxxBean()操作 | |
requeset | 每次http请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境 | |
session | 同一个HTTP Session 共享一个Bean,不同的HTTPSession适用不同 的Bean,该作用域仅适用于WebApplicationContext环境 | |
globalSession | 同一个全局Session共享一个Bean,一般用于 Portlet应用环境。 该作用域仅适用于WebApplicationContext环境 |
除了以上5种预定义的Bean作用域外,Spring还允许用户自定义Bean的作用域。可以通过org.springframework.beans.factory.config.Scope接口定义新的作用域,再通过org.springframework.beans.factory.config.CustomScopeConfigurer这个BeanFactoryProcessor注册自定义的Bean作用域。
8.1singleton作用域
一般情况下,无状态或者状态不可变的类适合使用单例模式,不过Spring对此实现了超越。在传统开发中,由于DAO类持有Connection这个非线程安全的变量,因此往往未采用单例模式。而在Spring环境下,对于所有的DAO类都可以采用单例模式,因为Spring利用AOP和LocalThread功能,对非线程安全的变量(状态)进行了特殊处理,是这些非线程安全的类变成了线程安全的类。
因为Spring的这一超越,在实际应用中,大部分Bean都能以单例的方式运行,这也是Spring将Bean的默认作用域定位singleton的原因。
在默认情况下,Spring的ApplicationContext容器在启动时,自动实例化所有singleton的Bean并缓存于容器中。虽然启动会花费时间,但也带来了两个好处:
- 对Bean的提前实例化操作会及早发现一些潜在的配置问题
- Bean以缓存的方式保存,当运行时用到该Bean时就无须再实例化了,提高了运行效率。
如果用户不希望提前实例化,可以通过lazy-init 属性进行控制。
<bean id="boss1" class="com.smart.scope.Boss" p:car-ref="car" lazy-init="true"/>
如果该Bean被其他需要提前实例化的Bean所引用,那么Spring将忽略延迟实例化的设置。
8.2prototype作用域
<bean id="car" class="com.smart.scope.Car" scope="prototype"/>
<bean id="boss1" class="com.smart.scope.Boss" p:car-ref="car" lazy-init="true"/>
<bean id="boss2" class="com.smart.scope.Boss" p:car-ref="car" scope="prototype"/>
<bean id="boss3" class="com.smart.scope.Boss" p:car-ref="car"/>
通过以上配置,boss1 boss2 boss3所引用的都是一个新的car实例。
在默认情况下。Spring容器在启动时不实例化prototype的Bean。此外,Spring容器将prototype的Bean交给调用者后,就不在管理它的生命周期。
8.3与Web应用环境相关的Bean作用域(添加监听器配置)
在使用另外3中作用域之前,必须在Web容器中进行一些额外的配置。
- 在web容器中进行额外配置
http请求监听器
org.springframework.web.context.request.RequestContextListener
http请求过滤器
org.springframework.web.filter.RequestContextFilter
在整合Spring容器时使用ContextLoaderListener,它实现了ServletContextListener监听器接口,ServletContextListener只负责监听Web容器启动和关闭的事件。而RequestContextListener实现了ServletRequestListener监听器接口,该监听器监听HTTP请求事件,Web服务器接收的每次请求都会通知该监听器。通过配置RequestContextListener,Spring容器和Web容器的结合更加紧密,Spring容器对Web 容器的“风吹草动”都能够察觉,因此就可以实施Web相应Bean作用域的控制了。
8.4作用域依赖问题(不同作用域Bean的依赖问题)
假设将Web相关作用域的Bean注入singleton或prototype的Bean中,如果没有进行一些额外配置,将得到一个失望的结果。
<bean name="car" class="com.smart.scope.Car" scope="request">
<aop:scoped-proxy/>
</bean>
<bean id="boss4" class="com.smart.scope.Boss">
<property name="car" ref="car"/>
</bean>
当boss Bean在Web环境下调用car Bean时,SpringAOP将启用动态代理智能地判断boss Bean 位于哪个HTTP请求线程中,并从对应的请求线程中获取对应的car Bean.
在配置文件中添加aop:scoped-proxy/后,注入boss Bean中的car Bean已经不是原来的car Bean,而是car Bean的动态代理对象。这个动态代理时Car类的子类(或实现类,假设Car是接口),Spring在动态代理子类中加入一段逻辑。以判断当前的boss需要取得那个HTTP请求相关的car bean.
盲猜实现过程:根据当前代理类获需要注入属性的Bean,根据当前Bean,获取当前线程的标识,找到对应的HttpRequest,再从HTTPRequest域中获取对应的car。一般情况下,一个HTTP请求对应一个独立的线程。
9.FactoryBean
一般情况下,Spring通过反射机制利用<bean>的class属性指定实现类实例化Bean。在某些情况下,实例化Bean的过程比较复杂,如按照传统方式,需要在<bean>中提供大量的配置信息。配置方式的灵活性是受限的,这是采用编码的方式可能会获得一个简单的方案。Spring为此提供了一个org.springframework.beans.factory.FactoryBean工厂类接口,用户可以通过实现该工厂类接口定制实例化Bean的逻辑。
FactoryBean接口对于Spring框架占有重要的地位,Spring自身就提供了70多个FactoryBean的实现类。他们隐藏了实例化一些复杂Bean的细节,给上层应用带来了便利。
FactoryBean有3个接口方法:
- T getObject():返回由FactoryBean创建的Bean实例。如果isSingleton()返回true,则该实例会放到Spring容器的单实例缓存池中。
- boolean isSingleton(): 确定FactoryBean创建的Bean的作用域是singleton还是prototype
- CLass<?> getObjectType(): 返回FactoryBean创建Bean的类型。
当配置文件中的class属性配置的实现类时FactoryBean时,通过getBean()方法返回的不是FactoryBean本是,而是FactoryBean#getObject()方法返回的对象。相当于FactoryBean#getObject()代理了getBean()方法。
下面以一种简洁的方式实现Car的创建:
import org.springframework.beans.factory.FactoryBean;
public class CarFactoryBean implements FactoryBean<Car> {
private String carInfo;
public String getCarInfo() {
return carInfo;
}
public void setCarInfo(String carInfo) {
this.carInfo = carInfo;
}
public Car getObject() throws Exception {
Car car = new Car();
String[] infos = carInfo.split(",");
car.setBrand(infos[0]);
car.setMaxSpeed(Integer.parseInt(infos[1]));
car.setPrice(Double.parseDouble(infos[2]));
return car;
}
public Class<Car> getObjectType() {
return Car.class;
}
public boolean isSingleton() {
return false;
}
}
有了这个CarFactoryBean后,就可以在配置文件中使用以下自定义的配置方式配置Car Bean:
<bean id="car" class="com.smart.fb.Car"
p:brand="红旗CA72" p:maxSpeed="200" p:price="20000.00"/>
<bean id="car1" class="com.smart.fb.CarFactoryBean"
p:carInfo="红旗CA72,200,20000.00"/>
当调用 getBean("car")时,Spring通过反射机制发现CarFactoryBean实现了FactoryBean接口,这时Spring容器就调用接口方法CarFactoryBean#getObject()返回工厂类创建的对象。如果用户希望获取CarFactoryBean的实例,需要在使用getBean(beanName)方法时显示地在beanName前加上“&”前缀,即getBean("&car")
10.基于注解的配置
10.1使用注解定义Bean
不管是XML还是注解,它们都是表达Bean定义的载体,其实质都是为Spring容器提供Bean定义的信息,在表现形式上都是将XML定义的内容通过类注解进行描述。Spring从2.0引入注解,2.5时得到完善,4.0进一步增强。
Spring容器成功启动的三大要件分别是Bean定义信息,Bean的实现类以及Spring本身。采用XML配置,Bean定义信息和Bean实现类本是是分离的;采用基于注解的配置文件,则Bean定义信息通过在Bean实现类上标注注解实现。
import org.springframework.stereotype.Component;
//1.通过Component定义一个DAO的Bean
@Component("userDao")
public class UserDao {
}
在1处使用@Component注解在UserDao类声明处对类进行标注,它可以被Spring容器识别,Spring自动将POJO(Persistence Object Java Object)转化为容器管理的Bean。和以下XML配置是等价的:
<bean id="userDao" class="com.smart.conf.UserDao"/>
除@Component外,Spring还提供了3个功基本和@Component等效的注解,分别用于对DAO,Service,以及Web层的Controller进行注解。
- @Repository:用于对DAO实现类进行标注
- @Service:用于对Service实现类进行标注
- @Controller:用于对Controller实现类进行标注。
之所以在@Component外提供这三个特殊的注解,是为了让标注类本身的用途清晰化,功能上完全可以用@Component替代这三个特殊的注解。
但是,推荐使用特定的注解标注特定的类,可以很好的看出Bean的真实身份。
10.2扫描注解定义的Bean
Spring提供了一个context命名空间,它提供了通过扫描类包以应用注解定义Bean的方式,如代码所示:
<?xml version="1.0" encoding="UTF-8" ?>
<!--1声明context命名空间-->
<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-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd"
>
<!--2扫描类包以应用注解定义的Bean-->
<context:component-scan base-package="com.smart.anno"/>
</beans>
在1处声明context命名空间,在2处可通过context命名空间的component-scan的base-package属性指定一个需要扫描的基类包。Spring会扫描基类包下的所有类,并从类的注解信息中获取Bean的定义信息。
如果希望扫描特定的类而非基包下的所有类,可以使用resource-pattern属性过滤出特定的类,如下
<context:component-scan base-package="com.smart" resource-pattern="anno/*.class"/>
//Spring仅会扫描基类包里anno子包中的类。
如果只使用resource-pattern,会发现很多时候并不能满足要求,如仅需要过滤基类包中实现了XxxService接口的类或标注了某个特定注解的类等。这些需求可以通过context:component-scan的子元素实现:
<context:component-scan base-package="com.smart">
<context:include-filter type="regex" expression="com.smart.anno.*Dao"/>
<context:include-filter type="regex" expression="com.smart.anno.*Service"/>
<context:exclude-filter type="aspectj" expression="com.smart..*Controller+"/>
</context:component-scan>
context:include-filter表示要包含的目标类,而context:exclude-filter表示要排除的目标类。这两个过滤元素均支持多种类型的过滤表达式。
类别 | 示例 | 说明 |
---|---|---|
annotation | com.smart.XxxAnnotation | 所有标注了XxxAnnotation的类。该类型采用目标类是否标注了某个注解进行过滤 |
assignable | com.smart.XxxService | 所有继承或扩展XxxService的类。该类型采用目标类是否继承或扩展了某个特定类进行过滤 |
aspectj | com.smart..*Service+ | 所有类名以Service结束的类及继承或扩展它们的类。该类型采用AspectJ表达式进行过滤 |
regex | com.smart.anno..* | 所有com.smart.anno类包下的类。该类型采用正则表达式根据目标类的类型进行过滤 |
custom | com.smart.SxxTypeFilter | 采用XxxTypeFilter代码方式实现过滤规则。该类必须实现org.springframework.core.type.filter.TypeFilter接口 |
在所有过滤类型中,除custom类型外,aspectj的过滤表达式能力是最强的,它可以轻易实现其它类型所能表达的过滤规则。
context:component-scan/拥有一个容易被忽视的use-default-filters属性,其默认值为true,表示默认会对标注@Component @Controller @Service @Repository 的Bean进行扫描。 context:component-scan/先根据context:exclude-filter列出需要排除的黑名单,再通过context:include-filter列出需要包含的白名单。由于use-default-filters属性默认值的作用,下面的配置片段不但会扫描@Controller的Bean,还会扫描 @Component @Service @Repository的Bean。
<context:component-scan base-package="com.smart">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
如果想仅扫描@Controller的Bean,则必须将use-default-filters设置为false。
<context:component-scan base-package="com.smart" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
10.3自动装配Bean
- 使用@Autowire进行自动注入
Spring使用@Autowire注解实现Bean的依赖注入。
@Service
public class LogonService {
@Autowired
private LogDao logDao;
@Autowired
private UserDao userDao;
}
@Autowired默认按类型(byType)匹配的方式在容器中查找匹配的Bean,当且仅有一个匹配的Bean时,Spring将其注入@Autowire标注的变量中。
2. 使用@Autowired的required属性
如果容器中没有一个和标注变量类型匹配的Bean,那么Spring容器启动时将报NoSuchBeanDefinitionException异常。如果希望Spring即使找不到匹配的Bean完成注入也不要抛出异常,那么可以使用@Autowired(required=false)
@Service
public class LogonService {
@Autowired(required=false)
private LogDao logDao;
}
默认情况下,@Autowired的required属性值为true。
- 使用@Qualifier指定注入Beande的名称
如果容器中有一个以上匹配的bean时,可以通过@Qualifier注解限定Bean名称
@Service
public class LogonService {
@Autowired
private LogDao logDao;
@Autowired
@Qualifier("userDao")
private UserDao userDao;
}
- @Autowired对类方法进行标注
@Autowired可以对类成员变量及方法的入参进行标注,下面在类的方法上使用@Autowired注解,如代码:
@Service
public class LogonService implements BeanNameAware {
//自动将名为userDao的Bean传给方法入参
@Autowired
@Qualifier("userDao")
public void setUserDao(UserDao userDao) {
System.out.println("auto inject");
this.userDao = userDao;
}
//自动将LogDao传给方法入参
@Autowired
public void setLogDao(LogDao logDao) {
this.logDao = logDao;
}
}
如果一个方法拥有多个入参,则在默认情况下,Spring将自动选择匹配入参类型的Bean进行注入。Spring允许对方法入参标注@Qualifier以指定注入Bean的名称,如下:
@Autowired
public void init(@Qualifier("userDao")UserDao userDao,LogDao logDao){
System.out.println("multi param inject");
this.userDao = userDao;
this.logDao =logDao;
}
一般情况下,在Spring容器中大部分Bean都是单实例的,所以一般无须通过@Repository、@Service等注解的value属性为Bean指定名称,也无须使用@Qualifier注解按名称进行注入。
虽然Spring支持在属性和方法上标注自动注入注解@Autowired,但在实际开发中建议采用在方法上标注@Autowired注解,因为这样更加“面向对象”,也方便单元测试的编写。如果将注解标注在私有属性上,则在单元测试时很难用编程的方法设置属性值。
- 对集合类进行标注
如果对类中集合类的变量或方法入参进行@Autowired标注,那么Spring会将容器中类型匹配的所有Bean都自动注入进来。如代码:
@Component
public class MyComponent {
//1Spring会将容器中所有类型为Plugin的Bean注入到这个变量中
@Autowired(required=false)
private List<Plugin> plugins;
//2将类型为Plugin的变量注入map中
@Autowired
private Map<String,Plugin> pluginMaps;
public List<Plugin> getPlugins() {
return plugins;
}
}
在2处将实现Plugin接口的Bean注入map集合,是Spring4.0的新特性,其中key是bean的名字,value是所有实现了Plugin的Bean。
在默认情况下,多个Bean的加载顺序是不确定的,在Spring4.0中可以通过@Order注解或实现Ordered接口来决定Bean加载的顺序,值越小,优先被加载。
6. 对延迟依赖注入的支持
Spring4.0支持延迟依赖注入,即在Spring容器启动的时候,对于在Bean上标注@Lazy及@Autowired注解的属性,不会立即注入属性值,而是延迟到调用此属性的时候才会注入属性值。如代码
@Lazy//1此处需要标注延迟注解
@Repository
public class LogDao implements InitializingBean {
public void afterPropertiesSet() throws Exception {
System.out.println("LogDao....");
}
public void saveLog(){}
}
@Service
public class LogonService implements BeanNameAware {
@Lazy//2.此处需要标注延迟注解
@Autowired(required=false)
private LogDao logDao;
}
对Bean实施延迟依赖注入,要注意@Lazy注解必须同时标注在属性及目标Bean上,如上例中1和2处,二者缺一,则延迟注入无效。
Spring对集合类自动注入容器中所有匹配类型Bean的功能非常强大。某开发平台采用“模型驱动+插件”的体系架构,其中的插件体系就完全利用Spring集合注入的功能完成插件的识别和注入工作,大大简化了平台的研发难度。
- 对标准注解的支持
Spring还支持JSR-250中定义的@Resource和JSR-330中定义的@Inject注解,这两个注解和@Autowired注解的功能类似,都是对类变更及方法入参提供自动注入功能。
@Resource注解要求提供一个Bean名称的属性,如果属性为空,则自动采用标注处的变量名或方法名作为Bean的名称,如代码:
@Component
public class Boss {
private Car car;
@Resource("car")
private void setCar(Car car){
System.out.println("execute in setCar");
this.car = car;
}
这时,如果@Resource未指定“car”属性,则也可以根据属性方法得到需要注入的Bean名称。可见@Autowired默认按类型匹配注入Bean,@Resource按名称匹配注入Bean。而@Inject和@Autowired同样也是按类型匹配注入Bean的,只不过它没有required属性。可见,不管是@Resource还是@Inject注解,功能都没有@Autowired丰富,因此,除非必要,大可不必在乎这两个注解。
10.4Bean的作用范围及生命过程方法
Spring为注解配置提供了一个@Scope注解,可以通过它显示指定Bean的作用范围,默认作用范围为singleton
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
@Component
public class Car {
}
在使用
@Component
public class Boss {
private Car car;
public Boss(){
System.out.println("construct...");
}
@Autowired
private void setCar(Car car){
System.out.println("execute in setCar");
this.car = car;
}
@PostConstruct
private void init1(){
System.out.println("execute in init1");
}
@PostConstruct
private void init2(){
System.out.println("execute in init1");
}
@PreDestroy
private void destory1(){
System.out.println("execute in destory1");
}
@PreDestroy
private void destory2(){
System.out.println("execute in destory2");
}
}
执行结果
construct...
execute in setCar
execute in init2
execute in init1
execute in destory2
execute in destory1
Spring先调用Boss的构造函数实例化Bean,再执行@Autowired进行自动注入,然后分别执行标注了@PostConstruct的方法,在容器关闭时,则分别执行标注了@PreDestroy的方法。
11.基于Java类的配置
11.1 使用Java类提供Bean定义信息
JavaConfig是Spring的一个子项目,旨在通过Java类的方式提供Bean的定义信息,该项目在Spring2.0就已发布了1.0版本。Spring4.0基于Java类配置的核心就取材于JavaConfig,JavaConfig经过若干年的努力修成正果,称为Spring4.0的核心功能。
普通的POJO只要标注@Configuration注解,就可以成为Spring容器提供Bean定义的信息,每个标注了@Bean的类方法就相当于提供了一个Bean的定义信息,如代码:
//1、将一个POJO标注为定义Bean的配置类
@Configuration
public class AppConf {
//2、以下两个方法定义了来个Bean,并提供了Bean的实例化逻辑
@Bean
public UserDao userDao(){
return new UserDao();
}
@Bean
public LogDao logDao(){
return new LogDao();
}
//定义了logonService的Bean
@Bean
public LogonService logonService(){
LogonService logonService = new LogonService();
//4、将2和3处的定义的Bean注入logonService Bean中
logonService.setLogDao(logDao());
logonService.setUserDao(userDao());
return logonService;
}
}
public class LogDao {
}
public class UserDao {
}
在1处,在AppConf类的定义处标注了@Configuration注解,说明这个类可用于为Spring提供Bean定义的信息。该类的方法可以标注@Bean注解。Bean的类型由方法返回值的类型决定。名称默认和方法名相同,也可以通过入参显示指定Bean的名称,如@Bean(name="userDao")。@Bean所标注的方法体提供了Bean的实例化逻辑。
在2处。userDao()和logDao()方法定义了一个UserDao和一个LogDao的Bean,他们的Bean名称分别为userDao和logDao。在3处,又定义了一个logonService Bean,并且在4处注入2处所定义的两个Bean。
因此,以上配置和以下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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
<bean id="userDao" class="com.smart.anno.UserDao"/>
<bean id="logDao" class="com.smart.anno.LogDao"/>
<bean id="logonService" class="com.smart.conf.LogonService"
p:logDao-ref="userDao" p:userDao-ref="logDao"/>
</beans>
基于Java类的配置方式和基于XML或基于注解的陪孩子方式相比,前者通过代码编程的方式可以更加灵活地实现Bean的实例化及Bean之间的装配;后两者都是通过配置声明的方式,在灵活性上稍逊一些,但在配置上更简单一些。
如果Bean在多个@Configuration配置类中定义,如何引用不同配置类中定义的Bean呢?
@Configuration
public class DaoConfig {
@Bean(name="")
public UserDao userDao(){
return new UserDao();
}
@Scope("prototype")
@Bean
public LogDao logDao(){
return new LogDao();
}
}
由于@Configuration注解类本身已经标注了@Component注解,所以任何标注了@Configuration的类,本身也相当于标注了@Component,即它们可以像普通Bean一样被注入其他Bean中。DaoConfig标注了@Configuration注解后就成为一个Bean,它可以被自动注入到ServiceConfig中,如代码:
@Configuration
public class ServiceConfig {
//1像普通Bean一样注入DaoConfig
@Autowired
private DaoConfig daoConfig;
@Bean
public LogonService logonService(){
LogonService logonService = new LogonService();
System.out.println(daoConfig.logDao() == daoConfig.logDao());
//2、像普通Bean一样,调用Bean相关方法
logonService.setLogDao(daoConfig.logDao());
logonService.setUserDao(daoConfig.userDao());
return logonService;
}
}
调用daoConfig的logDao()和userDao()方法,就相当于将DaoConfig配置类中的Bean注入进来。Spring会对配置类所有标注@Bean的方法进行“改造”(AOP增强),将对Bean生命周期的逻辑织入进来。所以,在2处调用daoConfig.logDao()及daoConfig.userDao()方法时,不是简单地执行DaoConfig类中定义的方法逻辑,而是从Spring容器中返回相应Bean的单例。换句话说,多次调用daoConfig.logDao()返回的都是Spring容器中相同 的Bean。在@Bean处,还可以标注@Scope注解以控制Bean的作用范围。如果在@Bean处标注了@Scope("prototype"),则每次调用daoConfig.logDao()都会返回一个新的logDao Bean,如代码
@Configuration
public class DaoConfig {
@Bean(name="")
public UserDao userDao(){
return new UserDao();
}
@Scope("prototype")
@Bean
public LogDao logDao(){
return new LogDao();
}
}
由于Spring容器会自动对@Configuration的类进行“改造”,以植入Spring容器对Bean的管理逻辑,所以使用基于Java类的配置必须保证将Spring aop类包和CGLib类包加载到类路径下。
11.2 使用基于Java类的配置信息启动Spring容器
- 直接通过@Configuration类启动Spring容器
Spring提供了一个org.springframework.context.annotation.AnnotationConfigApplicationContext类,它能直接通过标注@Configuration的Java类启动Spring容器,如代码
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class JavaConfigTest {
public static void main(String[] args) {
//1.通过构造函数加载配置类
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConf.class);
LogonService logonService = ctx.getBean(LogonService.class);
System.out.println((logonService.getLogDao() !=null));
logonService.printHelllo();
}
在1处,通过AnnotationConfigApplicationContext类的构造函数直接传入标注@Configuration的Java类,直接用该类中提供的Bean定义信息启动Spring容器。
AnnotationConfigApplicationContext还支持通过编码的方式加载多个@Configuration配置类,然后通过刷新容器应用这些配置类,如代码:
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class JavaConfigTest {
public static void main(String[] args) {
//2.通过编码方式注册配置类
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(DaoConfig.class);
ctx.register(ServiceConfig.class);
/ ctx.refresh();
}
还可以通过@Import将多个配置类组装到一个配置类中,这样仅需注册这个组装好的配置类就可以启动容器,如代码:
@Configuration
@Import(DaoConfig.class)
public class ServiceConfig {
@Autowired
private DaoConfig daoConfig;
@Bean
public LogonService logonService(){
LogonService logonService = new LogonService();
System.out.println(daoConfig.logDao() == daoConfig.logDao());
logonService.setLogDao(daoConfig.logDao());
logonService.setUserDao(daoConfig.userDao());
return logonService;
}
}
- 通过XML配置文件应用@Configuration的配置
标注了@Configuration的配置类与标注了@Component的类一样也是一个Bean,它可以被Spring的context:component-scan扫描到。仅需在MXL中通过context:component-scan扫描到相应的配置类即可,如代码:
<?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-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<context:component-scan base-package="com.smart.conf"
resource-pattern="AppConf.class" />
</beans>
- 通过@Configuration配置类引用XML配置信息
假设在XML中定义了两个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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
<bean id="userDao" class="com.smart.conf.UserDao"/>
<bean id="logDao" class="com.smart.conf.LogDao"/>
</beans>
在@Configuration配置中可以通过@ImportResource引入XML配置文件,在LogonAppConfig配置类中即可直接通过@Autowired引入XML配置文件中定义的Bean,如代码:
//1.通过@ImportResource引入XML配置文件
@Configuration
@ImportResource("classpath:com/smart/conf/beans3.xml")
public class LogonAppConfig {
//2.自动注入XML文件中定义的Bean
@Bean
@Autowired
public LogonService logonService(UserDao userDao,LogDao logDao){
LogonService logonService = new LogonService();
logonService.setUserDao(userDao);
logonService.setLogDao(logDao);
return logonService;
}
}
在1处引入定义userDao和logDao Bean的XML配置文件并不是2处可以自动注入成功的前提条件。只要不同形式的Bean定义信息能够加载到Spring容器中,Spring就足够智能地完成Bean之间的装配。
12.基于Groovy DSL的配置
12.1使用Groovy DSL 提供Bean定义信息
Groovy 是一种基于JVM的敏捷开发语言,它结合了Python、Ruby等动态语言的特性。Groovy 代码能够与Java代码很好地结合,也能用于扩展现有代码。由于其运行在JVM上的特性,所以Groovy 可以使用其他java语言编写的库。
Groovy 在Spring框架构建,Bean配置等方面的应用非常广泛。Spring模块化构建采用的工具Gradle也是以Groovy DSL作为基础的。
Spring4.0支持使用Groovy DSL来进行Bean定义配置,类似于XML配置,只不过配置信息是由Groovy 脚本表达的,可以实现任何复杂的Bean配置,需要Groovy 2.3.1以上版本,如代码: