引言
在Springboot编程实践中,我们偏向使用注解的方式进行Bean的注册和依赖注入等,但XML格式的容器信息管理方式仍是Spring提供的最为强大、支持最为全面的方式,本文对Spring-IOC的XML配置进行详细的讲解。
<beans>和<bean>
BeanFactory和ApplicationContext的XML配置均采用统一的格式,在Spring2.0之前,这种格式由Spring提供的DTD规定,即在配置文件的头部,需要以下形式的DOCTYPE声明:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
...
</beans>
从Spring 2.0版本之后,Spring在继续保持向前兼容的前提下,既可以继续使用DTD方式的 DOCTYPE
进行配置文件格式的限定,又引入了基于XML Schema的文档声明:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:lang="http://www.springframework.org/schema/lang"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-2.0.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee-2.0.xsd
http://www.springframework.org/schema/lang
http://www.springframework.org/schema/lang/spring-lang-2.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
...
</beans>
<beans>
<beans>是配置文件的顶层元素,其可以包含0或1个<description>和多个<bean>以及<import>或者<alias>
<beans>可以配置所有<bean>的全局行为,主要包括:
- default-lazy-init
取值true或false,默认值false,用来标志是否对所有的<bean>进行延迟初始化。
- default-autowire
可以取值为no、byName、byType、constructor以及autodetect。默认值为 no ,如果使用自动绑定的话,用来标志全体bean使用哪一种默认绑定方式。
- default-dependency-check
可以取值none、objects、simple以及all,默认值为none,即不做依赖检查。
- default-init-method
如果所管辖的<bean>按照某种规则,都有同样名称的初始化方法的话,可以在这里统一指定这个初始化方法名,而不用在每一个<bean>上都重复单独指定。
- default-destroy-method
与default-init-method相对应,如果所管辖的bean有按照某种规则使用了相同名称的对象销毁方法,可以通过这个属性统一指定。
<description>、<import>和<alias>
- <description>
配置文件的描述信息。
- <import>
通常情况下,可以根据模块功能或者层次关系,将配置信息分门别类地放到多个配置文件中。在想加载主要配置文件,并将主要配置文件所依赖的配置文件同时加载时,可以在这个主要的配置文件中通过<import>元素对其所依赖的配置文件进行引用。比如,如果A.xml中的<bean>定义可能依赖B.xml中的某些<bean>定义,那么就可以在A.xml中使用<import>将B.xml引入到A.xml,以类似于<import resource=“B.xml”/> 的形式。
- <alias>
可以通过<alias>为某些<bean>起一些“外号”(别名),通常情况下是为了减少输入。比如,假设有个<bean> ,它的名称为dataSourceForMasterDatabase ,你可以为其添加一个<alias> ,像这样<alias name=“dataSourceForMasterDatabase” alias=“masterDataSource”/> 。以后通过dataSourceForMasterDatabase或者 masterDataSource来引用这个<bean>都可以。
<bean>
- id属性
对象在容器里的标识,若未配置,则<bean>的id取类名的小驼峰。
除了使用id,也可以使用name来进行标识,它与id的区别是, name可以使用id不能使用的一些字符,比如/。而且
还可以通过逗号、空格或者冒号分割指定多个name。name的作用跟使用<alias>为id指定多个别名基本相同:
<bean id="person" name="/china/person,/england/person"/ class="com.ruanshubin.springboot.entity.Person">
等同于:
<alias name="person" alias="/china/person"/>
<alias name="person" alias="/england/person"/>
- class属性
每个注册到容器的对象都需要通过<bean>元素的class属性指定其类型。
依赖注入
为了演示依赖注入,我们新建3个实体类,分别为主机、显示器和电脑。
public class MainEngine {
// 名称
private String name;
// 型号
private String type;
// 花费
private Integer cost;
...
构造器及get/set方法
toString方法
}
public class Display {
// 名称
private String name;
// 型号
private String type;
// 花费
private Integer cost;
...
构造器及get/set方法
toString方法
}
public class Computer {
// 名称
private String name;
// 主机
private MainEngine mainEngine;
// 显示器
private Display display;
...
构造器及get/set方法
toString方法
}
构造方法注入
在resources目录下新建spring-beans.xml文件:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:lang="http://www.springframework.org/schema/lang"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-2.0.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee-2.0.xsd
http://www.springframework.org/schema/lang
http://www.springframework.org/schema/lang/spring-lang-2.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
<bean id="display" class="com.ruanshubin.springboot.ioc.entity.Display">
<constructor-arg>
<value>惠普</value>
</constructor-arg>
<constructor-arg>
<value>V300</value>
</constructor-arg>
<constructor-arg>
<value>1000</value>
</constructor-arg>
</bean>
<bean id="mainEngine" class="com.ruanshubin.springboot.ioc.entity.MainEngine">
<constructor-arg>
<value>戴尔</value>
</constructor-arg>
<constructor-arg>
<value>T600</value>
</constructor-arg>
<constructor-arg>
<value>3600</value>
</constructor-arg>
</bean>
<bean id="computer" class="com.ruanshubin.springboot.ioc.entity.Computer">
<constructor-arg>
<value>组装机1</value>
</constructor-arg>
<constructor-arg>
<ref bean="mainEngine"></ref>
</constructor-arg>
<constructor-arg>
<ref bean="display"></ref>
</constructor-arg>
</bean>
</beans>
编写主函数:
public class IocXmlTest {
public static void main(String[] args) {
XmlBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("spring-beans.xml"));
Computer computer = (Computer) beanFactory.getBean("computer");
System.out.println(computer);
}
}
运行结果为:
Computer{name='组装机1', mainEngine=MainEngine{name='戴尔', type='T600', cost=3600}, display=Display{name='惠普', type='V300', cost=1000}}
可以发现,如果注入的属性为基本数据类型(及其包装类)、String等,则使用<value>进行注入,若为Java对象,则使用<ref bean=“…”>的方式进行注入。
同时,上述<constructor-arg>的顺序要与Java类中属性的顺序要严格一致,否则会出现问题,如将mainEngine的配置修改为:
<bean id="mainEngine" class="com.ruanshubin.springboot.ioc.entity.MainEngine">
<constructor-arg>
<value>T600</value>
</constructor-arg>
<constructor-arg>
<value>戴尔</value>
</constructor-arg>
<constructor-arg>
<value>3600</value>
</constructor-arg>
</bean>
重新运行主类,结果为:
Computer{name='组装机1', mainEngine=MainEngine{name='T600', type='戴尔', cost=3600}, display=Display{name='惠普', type='V300', cost=1000}}
可以发现,主机的名称和型号互换,造成异常。
此时,可以添加index标签,其表征了属性的顺序编号,从0开始。
<bean id="mainEngine" class="com.ruanshubin.springboot.ioc.entity.MainEngine">
<constructor-arg index="1">
<value>T600</value>
</constructor-arg>
<constructor-arg index="0">
<value>戴尔</value>
</constructor-arg>
<constructor-arg>
<value>3600</value>
</constructor-arg>
</bean>
还有type标签,用于各属性类型不同时配置:
<bean id="mainEngine" class="com.ruanshubin.springboot.ioc.entity.MainEngine">
<!--不添加type标签会报错-->
<constructor-arg type="Integer">
<value>3600</value>
</constructor-arg>
<constructor-arg>
<value>戴尔</value>
</constructor-arg>
<constructor-arg>
<value>T600</value>
</constructor-arg>
</bean>
最强大的是name标签,不管<constructor-arg>的顺序是否与实体类各属性的顺序是否一致,只要保证name一致即可安全注入,如将mainEngine的配置修改为:
<bean id="mainEngine" class="com.ruanshubin.springboot.ioc.entity.MainEngine">
<constructor-arg name="cost">
<value>3600</value>
</constructor-arg>
<constructor-arg name="type">
<value>T600</value>
</constructor-arg>
<constructor-arg name="name">
<value>戴尔</value>
</constructor-arg>
</bean>
虽然<constructor-arg>的顺序与实体类的属性顺序完全相反,但是通过name一对一绑定,运行结果仍旧为:
Computer{name='组装机1', mainEngine=MainEngine{name='戴尔', type='T600', cost=3600}, display=Display{name='惠普', type='V300', cost=1000}}
setter方法注入
setter方法使用<property>完成依赖注入,如:
<bean id="computer" class="com.ruanshubin.springboot.ioc.entity.Computer">
<property name="name" value="组装机1"/>
<property name="mainEngine" ref="mainEngine"/>
<property name="display" ref="display"/>
</bean>
需要指出的是,除了value和ref标签,Spring还提供了bean、idref、value、null、list、set、map、props。
具体使用场景,本文不做过多介绍,大家可自行Google。
自动注入autowire
除了可以通过配置明确指定bean之间的依赖关系,Spirng还提供了根据bean定义的某些特点将相互依赖的某些bean直接自动绑定的功能。通过<bean> 的autowire属性,可以指定当前bean定义采用某种类型的自动绑定模式。这样,你就无需手工明确指定该bean定义相关的依赖关系,从而也可以免去一些手工输入的工作量。
Spring提供了5种自动绑定模式,即 no、byName、byType、constructor和autodetect。
- no
默认配置,即不采取自动注入,仅依靠手工配置注入。
- byName
按照类中声明的实例变量的名称,与XML配置文件中声明的bean定义的beanName的值进行匹配,相匹配的bean定义将被自动绑定到当前实例变量上。
如我们将上面的computer的注入配置修改为:
<bean id="computer" class="com.ruanshubin.springboot.ioc.entity.Computer" autowire="byName">
</bean>
运行结果为:
Computer{name='null', mainEngine=MainEngine{name='戴尔', type='T600', cost=3600}, display=Display{name='惠普', type='V300', cost=1000}}
程序会自动寻找id为mainEngine、display的bean来完成注入,因为没有id为name的<bean>,所以不能自动注入,该项为null。
可以修改配置,添加无法自动注入的属性:
<bean id="computer" class="com.ruanshubin.springboot.ioc.entity.Computer" autowire="byName">
<property name="name" value="组装机1"/>
</bean>
此时,再次运行:
Computer{name='组装机1', mainEngine=MainEngine{name='戴尔', type='T600', cost=3600}, display=Display{name='惠普', type='V300', cost=1000}}
- byType
与byName类似,byType是按照类中声明的实例变量的Type,与XML配置文件中声明的bean的Type进行匹配,相匹配的bean定义将被自动绑定到当前实例变量上。
如我们将上面的computer的注入配置修改为:
<bean id="computer" class="com.ruanshubin.springboot.ioc.entity.Computer" autowire="byType">
<property name="name" value="组装机1"/>
</bean>
运行:
Computer{name='组装机1', mainEngine=MainEngine{name='戴尔', type='T600', cost=3600}, display=Display{name='惠普', type='V300', cost=1000}}
此处有个问题,当某个实例变量的Type在Spring容器中存在两个<bean>,会选择哪个进行注入呢?
假设在上述spring-beans.xml文件中添加如下配置:
<bean id="mainEngine1" class="com.ruanshubin.springboot.ioc.entity.MainEngine">
<constructor-arg name="cost">
<value>3000</value>
</constructor-arg>
<constructor-arg name="type">
<value>X900</value>
</constructor-arg>
<constructor-arg name="name">
<value>神州</value>
</constructor-arg>
</bean>
此时,Spring容器里存在2个主机实例,我们仍旧通过byType进行自动注入。
运行主函数:
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'computer' defined in class path resource [spring-beans.xml]: Unsatisfied dependency expressed through bean property 'mainEngine'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.ruanshubin.springboot.ioc.entity.MainEngine' available: expected single matching bean but found 2: mainEngine,mainEngine1
...
很明显,Spring不会帮你做这个决策,当同一个Type存在多个实例时,程序直接会将错误抛出来,由你来做决策。
- constructor
constructor类型则是针对构造方法参数的类型而进行的自动绑定,它同样是byType类型的绑定模式。不过,constructor是匹配构造方法的参数类型,而不是实例属性的类型。与byType模式类似,如果找到不止一个符合条件的bean定义,那么,容器会返回错误。
- autodetect
是byType和constructor模式的结合体,如果对象拥有默认无参数的构造方法,容器会优先考虑byType的自动绑定模式。否则,会使用constructor模式。当然,如果通过构造方法注入绑定后还有其他属性没有绑定,容器也会使用byType对剩余的对象属性进行自动绑定。
依赖检查及继承
依赖检查
检查依赖是否按照预期绑定完成,其由dependency-check标签进行约束,存在以下4种模式:
- none
不做依赖检查
- simple
容器会对简单属性类型以及相关的collection进行依赖检查,对象引用类型的依赖除外。
- object
只对对象引用类型依赖进行检查。
- all
simple和object的结合体。
继承
新建服务器类,继承自计算机类:
public class Server extends Computer{
// 名称
private String name;
// 主机
private MainEngine mainEngine;
// 显示器
private Display display;
// GPU型号
private String gpuType;
public Server() {
}
public Server(String name, MainEngine mainEngine, Display display, String gpuType) {
super(name, mainEngine, display);
this.gpuType = gpuType;
}
...
get/set方法
toString方法
}
在spring-beans.xml配置文件中增加如下内容:
<bean id="server" parent="computer" class="com.ruanshubin.springboot.ioc.entity.Server">
<property name="gpuType" value="TC800"/>
</bean>
修改启动类:
public class IocXmlTest {
public static void main(String[] args) {
XmlBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("spring-beans.xml"));
Server server = beanFactory.getBean("server", Server.class);
System.out.println(server);
}
}
运行结果为:
Server{name='组装机1', mainEngine=MainEngine{name='戴尔', type='T600', cost=3600}, display=Display{name='惠普', type='V300', cost=1000}, gpuType='TC800'}
可以看到,我们通过parent标签完成了Bean继承的管理。
Bean的scope
Spring2.0前,Bean容器仅有2种作用域类型,即singleton和prototype,2.0后,又引入了3种web相关的scope类型,即request、session、global session。
singleton
- 对象实例
容器中只存在一个共享实例。
- 对象存活时间
第一次请求被实例化到容器销毁或者退出。
prototype
- 对象实例
容器中存在多个实例。
- 对象存活时间
每次请求即创建1个新的实例,对象实例返回给请求方之后,容器就不再拥有当前返回对象的引用,请求方需要自己负责当前返回对象的后继生命周期的管理工作,包括该对象的销毁。
- request
Spring容器,即XmlWebApplicationContext会为每个HTTP请求创建一个全新的Request-Processor对象供当前请求使用,当请求结束后,该对象实例的生命周期即告结束。当同时有10个HTTP请求进来的时候,容器会分别针对这10个请求返回10个全新的RequestProcessor 对象实例,且它们之间互不干扰。
- session
Spring容器会为每个独立的session创建属于它们自己的全新的UserPreferences对象实例。与request相比,除了拥有session scope的bean的实例具有比request scope的bean可能更长的存活时间,其他方面真是没什么差别。
- global session
global session只有应用在基于portlet的Web应用程序中才有意义,它映射到portlet的global范围的session。如果在普通的基于servlet的Web应用中使用了这个类型的scope,容器会将其作为普通的session类型的scope对待。
- 自定义scope
在Spring 2.0之后的版本中,容器提供了对scope的扩展点,这样,你可以根据自己的需要或者应用的场景,来添加自定义的scope类型。需要说明的是,默认的singleton和prototype是硬编码到代码中的,而request、session和global session,包括自定义scope类型,则属于可扩展的scope行列,它们都实现了org.springframework.beans.factory.config.Scope接口。
具体如何进行自定义scope的设计开发,以后我们专门写篇文章介绍。
下面看一个有意思的东西:
去除掉MainEngine的toString()方法,并将mainEngine的scope设置为prototype。
<bean id="mainEngine" class="com.ruanshubin.springboot.ioc.entity.MainEngine" scope="prototype">
<constructor-arg name="cost">
<value>3600</value>
</constructor-arg>
<constructor-arg name="type">
<value>T600</value>
</constructor-arg>
<constructor-arg name="name">
<value>戴尔</value>
</constructor-arg>
</bean>
修改主函数:
public class IocXmlTest {
public static void main(String[] args) {
XmlBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("spring-beans.xml"));
Computer computer = (Computer) beanFactory.getBean("computer");
System.out.println(computer.getMainEngine());
System.out.println(computer.getMainEngine());
}
}
运行:
com.ruanshubin.springboot.ioc.entity.MainEngine@4dfa3a9d
com.ruanshubin.springboot.ioc.entity.MainEngine@4dfa3a9d
显然,2次获取的MainEngine实例是同一个。
那么,如何在每次获取MainEngine时,总返回新创建的实例呢,可以使用<lookup-method>标签:
<bean id="computer" class="com.ruanshubin.springboot.ioc.entity.Computer">
<property name="name" value="组装机1"/>
<property name="mainEngine" ref="mainEngine"/>
<property name="display" ref="display"/>
<lookup-method name="getMainEngine" bean="mainEngine"/>
</bean>
再次运行主函数:
com.ruanshubin.springboot.ioc.entity.MainEngine@480bdb19
com.ruanshubin.springboot.ioc.entity.MainEngine@2a556333
达到目的。
同时,可以对Computer的getMainEngine进行改造,使其每次从BeanFactory中取MainEngine得实例,操作方法是使Computer实现BeanFactoryAware接口。
public class Computer implements BeanFactoryAware {
private BeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
// 名称
private String name;
// 主机
private MainEngine mainEngine;
// 显示器
private Display display;
public Computer() {
}
public Computer(String name, MainEngine mainEngine, Display display) {
this.name = name;
this.mainEngine = mainEngine;
this.display = display;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public MainEngine getMainEngine() {
return beanFactory.getBean("mainEngine", MainEngine.class);
}
public void setMainEngine(MainEngine mainEngine) {
this.mainEngine = mainEngine;
}
public Display getDisplay() {
return display;
}
public void setDisplay(Display display) {
this.display = display;
}
}
此时,去掉以下配置,运行上述主程序:
<lookup-method name="getMainEngine" bean="mainEngine"/>
运行结果为:
com.ruanshubin.springboot.ioc.entity.MainEngine@402a079c
com.ruanshubin.springboot.ioc.entity.MainEngine@59ec2012
仍然可达到目的。
当然,如果不想实现BeanFactoryAware接口,也可以采用ObjectFactoryCreatingFactoryBean方式。
ObjectFactoryCreatingFactoryBean是Spring提供的一个FactoryBean实现,它返回一个ObjectFactory实例。从ObjectFactoryCreatingFactoryBean返回的这个ObjectFactory实例可以为我们返回容器管理的相关对象。
首先,在spring-beans.xml里配置ObjectFactoryCreatingFactoryBean,并注入主机类。
<bean id="objectFactory" class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean">
<property name="targetBeanName">
<idref bean="mainEngine"/>
</property>
</bean>
<bean id="computer" class="com.ruanshubin.springboot.ioc.entity.Computer">
<property name="name" value="组装机1"/>
<property name="display" ref="display"/>
<property name="objectFactory">
<ref bean="objectFactory"/>
</property>
</bean>
同时修改Computer类:
public class Computer{
private ObjectFactory objectFactory;
public void setObjectFactory(ObjectFactory objectFactory) {
this.objectFactory = objectFactory;
}
// 名称
private String name;
// 主机
private MainEngine mainEngine;
// 显示器
private Display display;
public Computer() {
}
public Computer(String name, MainEngine mainEngine, Display display) {
this.name = name;
this.mainEngine = mainEngine;
this.display = display;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public MainEngine getMainEngine() {
return (MainEngine) objectFactory.getObject();
}
public void setMainEngine(MainEngine mainEngine) {
this.mainEngine = mainEngine;
}
public Display getDisplay() {
return display;
}
public void setDisplay(Display display) {
this.display = display;
}
}
运行结果为:
com.ruanshubin.springboot.ioc.entity.MainEngine@5cb9f472
com.ruanshubin.springboot.ioc.entity.MainEngine@56ef9176
写着写着就写多了,更多Spring-IOC容器XML配置的东西,我们后面有机会再讲。