引言

在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配置的东西,我们后面有机会再讲。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐