Spring-IOC的XML配置

引言

在Springboot编程实践中,我们偏向使用注解的方式进行Bean的注册和依赖注入等,但XML格式的容器信息管理方式仍是Spring提供的最为强大、支持最为全面的方式,本文对Spring-IOC的XML配置进行详细的讲解。

BeanFactory和ApplicationContext的XML配置均采用统一的格式,在Spring2.0之前,这种格式由Spring提供的DTD规定,即在配置文件的头部,需要以下形式的DOCTYPE声明:




   ...

从Spring 2.0版本之后,Spring在继续保持向前兼容的前提下,既可以继续使用DTD方式的 DOCTYPE
进行配置文件格式的限定,又引入了基于XML Schema的文档声明:


    ...

是配置文件的顶层元素,其可以包含0或1个和多个以及或者

可以配置所有的全局行为,主要包括:

  • default-lazy-init

取值true或false,默认值false,用来标志是否对所有的进行延迟初始化。

  • default-autowire

可以取值为no、byName、byType、constructor以及autodetect。默认值为 no ,如果使用自动绑定的话,用来标志全体bean使用哪一种默认绑定方式。

  • default-dependency-check

可以取值none、objects、simple以及all,默认值为none,即不做依赖检查。

  • default-init-method

如果所管辖的按照某种规则,都有同样名称的初始化方法的话,可以在这里统一指定这个初始化方法名,而不用在每一个上都重复单独指定。

  • default-destroy-method

与default-init-method相对应,如果所管辖的bean有按照某种规则使用了相同名称的对象销毁方法,可以通过这个属性统一指定。

配置文件的描述信息。

通常情况下,可以根据模块功能或者层次关系,将配置信息分门别类地放到多个配置文件中。在想加载主要配置文件,并将主要配置文件所依赖的配置文件同时加载时,可以在这个主要的配置文件中通过元素对其所依赖的配置文件进行引用。比如,如果A.xml中的定义可能依赖B.xml中的某些定义,那么就可以在A.xml中使用将B.xml引入到A.xml,以类似于 的形式。

可以通过为某些起一些“外号”(别名),通常情况下是为了减少输入。比如,假设有个 ,它的名称为dataSourceForMasterDatabase ,你可以为其添加一个 ,像这样 。以后通过dataSourceForMasterDatabase或者 masterDataSource来引用这个都可以。

  • id属性

对象在容器里的标识,若未配置,则的id取类名的小驼峰。

除了使用id,也可以使用name来进行标识,它与id的区别是, name可以使用id不能使用的一些字符,比如/。而且
还可以通过逗号、空格或者冒号分割指定多个name。name的作用跟使用为id指定多个别名基本相同:


等同于:


  • class属性

每个注册到容器的对象都需要通过元素的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文件:



    
        
            惠普
        
        
            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等,则使用进行注入,若为Java对象,则使用的方式进行注入。

同时,上述的顺序要与Java类中属性的顺序要严格一致,否则会出现问题,如将mainEngine的配置修改为:


		
				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标签,不管的顺序是否与实体类各属性的顺序是否一致,只要保证name一致即可安全注入,如将mainEngine的配置修改为:


		
				3600
		
		
				T600
		
		
				戴尔
		

虽然的顺序与实体类的属性顺序完全相反,但是通过name一对一绑定,运行结果仍旧为:

Computer{name='组装机1', mainEngine=MainEngine{name='戴尔', type='T600', cost=3600}, display=Display{name='惠普', type='V300', cost=1000}}

setter方法注入

setter方法使用完成依赖注入,如:


		
		
		

需要指出的是,除了value和ref标签,Spring还提供了bean、idref、value、null、list、set、map、props。

具体使用场景,本文不做过多介绍,大家可自行Google。

自动注入autowire

除了可以通过配置明确指定bean之间的依赖关系,Spirng还提供了根据bean定义的某些特点将相互依赖的某些bean直接自动绑定的功能。通过 的autowire属性,可以指定当前bean定义采用某种类型的自动绑定模式。这样,你就无需手工明确指定该bean定义相关的依赖关系,从而也可以免去一些手工输入的工作量。

Spring提供了5种自动绑定模式,即 no、byName、byType、constructor和autodetect。

  • no

默认配置,即不采取自动注入,仅依靠手工配置注入。

  • byName

按照类中声明的实例变量的名称,与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的,所以不能自动注入,该项为null。

可以修改配置,添加无法自动注入的属性:


		

此时,再次运行:

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的注入配置修改为:


		

运行:

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

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配置文件中增加如下内容:


		

修改启动类:

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。

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

欢迎您扫一扫上面的二维码,关注我的微信公众号!

你可能感兴趣的:(SpringBoot)