在Springboot编程实践中,我们偏向使用注解的方式进行Bean的注册和依赖注入等,但XML格式的容器信息管理方式仍是Spring提供的最为强大、支持最为全面的方式,本文对Spring-IOC的XML配置进行详细的讲解。
BeanFactory和ApplicationContext的XML配置均采用统一的格式,在Spring2.0之前,这种格式由Spring提供的DTD规定,即在配置文件的头部,需要以下形式的DOCTYPE声明:
...
从Spring 2.0版本之后,Spring在继续保持向前兼容的前提下,既可以继续使用DTD方式的 DOCTYPE
进行配置文件格式的限定,又引入了基于XML Schema的文档声明:
...
取值true或false,默认值false,用来标志是否对所有的
可以取值为no、byName、byType、constructor以及autodetect。默认值为 no ,如果使用自动绑定的话,用来标志全体bean使用哪一种默认绑定方式。
可以取值none、objects、simple以及all,默认值为none,即不做依赖检查。
如果所管辖的
与default-init-method相对应,如果所管辖的bean有按照某种规则使用了相同名称的对象销毁方法,可以通过这个属性统一指定。
配置文件的描述信息。
通常情况下,可以根据模块功能或者层次关系,将配置信息分门别类地放到多个配置文件中。在想加载主要配置文件,并将主要配置文件所依赖的配置文件同时加载时,可以在这个主要的配置文件中通过
可以通过
对象在容器里的标识,若未配置,则
除了使用id,也可以使用name来进行标识,它与id的区别是, name可以使用id不能使用的一些字符,比如/。而且
还可以通过逗号、空格或者冒号分割指定多个name。name的作用跟使用
等同于:
每个注册到容器的对象都需要通过
为了演示依赖注入,我们新建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文件:
惠普
V300
1000
戴尔
T600
3600
组装机1
编写主函数:
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等,则使用
同时,上述
T600
戴尔
3600
重新运行主类,结果为:
Computer{name='组装机1', mainEngine=MainEngine{name='T600', type='戴尔', cost=3600}, display=Display{name='惠普', type='V300', cost=1000}}
可以发现,主机的名称和型号互换,造成异常。
此时,可以添加index标签,其表征了属性的顺序编号,从0开始。
T600
戴尔
3600
还有type标签,用于各属性类型不同时配置:
3600
戴尔
T600
最强大的是name标签,不管
3600
T600
戴尔
虽然
Computer{name='组装机1', mainEngine=MainEngine{name='戴尔', type='T600', cost=3600}, display=Display{name='惠普', type='V300', cost=1000}}
setter方法使用
需要指出的是,除了value和ref标签,Spring还提供了bean、idref、value、null、list、set、map、props。
具体使用场景,本文不做过多介绍,大家可自行Google。
除了可以通过配置明确指定bean之间的依赖关系,Spirng还提供了根据bean定义的某些特点将相互依赖的某些bean直接自动绑定的功能。通过
Spring提供了5种自动绑定模式,即 no、byName、byType、constructor和autodetect。
默认配置,即不采取自动注入,仅依靠手工配置注入。
按照类中声明的实例变量的名称,与XML配置文件中声明的bean定义的beanName的值进行匹配,相匹配的bean定义将被自动绑定到当前实例变量上。
如我们将上面的computer的注入配置修改为:
运行结果为:
Computer{name='null', mainEngine=MainEngine{name='戴尔', type='T600', cost=3600}, display=Display{name='惠普', type='V300', cost=1000}}
程序会自动寻找id为mainEngine、display的bean来完成注入,因为没有id为name的
可以修改配置,添加无法自动注入的属性:
此时,再次运行:
Computer{name='组装机1', mainEngine=MainEngine{name='戴尔', type='T600', cost=3600}, display=Display{name='惠普', type='V300', cost=1000}}
与byName类似,byType是按照类中声明的实例变量的Type,与XML配置文件中声明的bean的Type进行匹配,相匹配的bean定义将被自动绑定到当前实例变量上。
如我们将上面的computer的注入配置修改为:
运行:
Computer{name='组装机1', mainEngine=MainEngine{name='戴尔', type='T600', cost=3600}, display=Display{name='惠普', type='V300', cost=1000}}
此处有个问题,当某个实例变量的Type在Spring容器中存在两个
假设在上述spring-beans.xml文件中添加如下配置:
3000
X900
神州
此时,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类型则是针对构造方法参数的类型而进行的自动绑定,它同样是byType类型的绑定模式。不过,constructor是匹配构造方法的参数类型,而不是实例属性的类型。与byType模式类似,如果找到不止一个符合条件的bean定义,那么,容器会返回错误。
是byType和constructor模式的结合体,如果对象拥有默认无参数的构造方法,容器会优先考虑byType的自动绑定模式。否则,会使用constructor模式。当然,如果通过构造方法注入绑定后还有其他属性没有绑定,容器也会使用byType对剩余的对象属性进行自动绑定。
检查依赖是否按照预期绑定完成,其由dependency-check标签进行约束,存在以下4种模式:
不做依赖检查
容器会对简单属性类型以及相关的collection进行依赖检查,对象引用类型的依赖除外。
只对对象引用类型依赖进行检查。
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配置文件中增加如下内容:
修改启动类:
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继承的管理。
Spring2.0前,Bean容器仅有2种作用域类型,即singleton和prototype,2.0后,又引入了3种web相关的scope类型,即request、session、global session。
容器中只存在一个共享实例。
第一次请求被实例化到容器销毁或者退出。
容器中存在多个实例。
每次请求即创建1个新的实例,对象实例返回给请求方之后,容器就不再拥有当前返回对象的引用,请求方需要自己负责当前返回对象的后继生命周期的管理工作,包括该对象的销毁。
Spring容器,即XmlWebApplicationContext会为每个HTTP请求创建一个全新的Request-Processor对象供当前请求使用,当请求结束后,该对象实例的生命周期即告结束。当同时有10个HTTP请求进来的时候,容器会分别针对这10个请求返回10个全新的RequestProcessor 对象实例,且它们之间互不干扰。
Spring容器会为每个独立的session创建属于它们自己的全新的UserPreferences对象实例。与request相比,除了拥有session scope的bean的实例具有比request scope的bean可能更长的存活时间,其他方面真是没什么差别。
global session只有应用在基于portlet的Web应用程序中才有意义,它映射到portlet的global范围的session。如果在普通的基于servlet的Web应用中使用了这个类型的scope,容器会将其作为普通的session类型的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。
3600
T600
戴尔
修改主函数:
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时,总返回新创建的实例呢,可以使用
再次运行主函数:
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;
}
}
此时,去掉以下配置,运行上述主程序:
运行结果为:
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,并注入主机类。
同时修改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配置的东西,我们后面有机会再讲。
欢迎您扫一扫上面的二维码,关注我的微信公众号!