Spring IoC基于XML的DI详细配置全解两万字
Posted 刘Java
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring IoC基于XML的DI详细配置全解两万字相关的知识,希望对你有一定的参考价值。
详细介绍了Spring IoC基于XML的DI详细配置全解。
前面的文章Spring IOC容器的概念以及基于XML的IoC装配中,我们学习了IoC容器的概念以及依赖注入的两种方式,现在我们来看看一些更详细的配置。本文基于Spring5.2.8,案例基于上一篇文章的案例。
文章目录
1 value字面量
对于基本类型、String、包装类类型的属性,我们可以直接使用value属性的字符串值来描述具体的值,这样可读性也更强。在最后注入的时候Spring的转换服务会将这些值从 String 转换为属性或参数的实际类型。
并且Spring支持使用< value >标签表示具体的字面量值:
<bean id="simpleSetterBased" class="com.spring.core.SimpleSetterBased">
<constructor-arg name="property1">
<value>xxx</value>
</constructor-arg>
<constructor-arg name="property2">
<value>yyy</value>
</constructor-arg>
<!--setter方法 name表示属性名 value 表示属性值-->
<property name="property3">
<value>123</value>
</property>
<property name="property4">
<value>false</value>
</property>
</bean>
1.1 Properties快捷转换
Spring容器支持通过PropertyEditor直接解析value中的特定格式的字符串字面量值,并转换为一个Properties集合。后面我们也会学习集合的注入方式,但是这是一个非常好用的快捷方式!
我们来测试一下,首先有一个PropertiesDI类,内部有一个Properties(Hashtable是Properties的父类)属性:
/**
* @author lx
*/
public class PropertiesDI {
private Hashtable properties;
/**
* setter
*/
public void setProperties(Properties properties) {
this.properties = properties;
}
@Override
public String toString() {
return "PropertiesDI{" +
"properties=" + properties +
'}';
}
}
配置如下:
<!--properties-->
<bean class="com.spring.core.PropertiesDI" id="propertiesDI">
<property name="properties">
<!--直接写配置即可,自动转换为Properties-->
<value>
! 注释
# 注释
# “#”“!”开头的一行被算作注释不会解析。
# key和value可以使用 “=”、“:”、“ ”等符号分割,详见properties说明
key=value
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
ccc:ddd
aaa bbb
eee fff
</value>
</property>
</bean>
测试:
@Test
public void properties() {
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("DI.xml");
System.out.println(Arrays.toString(ac.getBeanDefinitionNames()));
System.out.println(ac.getBean("propertiesDI", PropertiesDI.class));
}
结果如下,成功注入:
[propertiesDI]
PropertiesDI{properties={jdbc.url=jdbc:mysql://localhost:3306/mydb, jdbc.driver.className=com.mysql.jdbc.Driver, eee=fff, key=value, aaa=bbb, ccc=ddd}}
2 引用其他bean
2.1 ref引用
在< constructor-arg >、< property >、< entry >标签中有一个ref属性,用于将bean的指定属性的值设置为对容器管理的另一个bean的引用。这就是引用类型属性依赖的设置方式。被引用的bean是要设置其属性的bean的依赖项,在设置该属性之前,需要对其进行初始化。ref属性的值需要与引用的目标bean的id或者name属性中的一个值相同。
当然还有一个< ref >标签,可以作为< constructor-arg >、< property >以及某些集合标签的子标签,通过< ref >标签的bean属性也可以来指定引用的目标bean。< ref >标签允许在同一容器或父容器中创建对任何bean的引用,而不管它是否在同一XML文件中。bean属性的值需要与引用的目标bean的id或者name属性中的一个值相同。
如下案例,首先有一个RefDI类,用于ref测试:
/**
* @author lx
*/
public class RefDI {
private HelloSpring helloSpring1;
private HelloSpring helloSpring2;
public RefDI(HelloSpring helloSpring1, HelloSpring helloSpring2) {
this.helloSpring1 = helloSpring1;
this.helloSpring2 = helloSpring2;
}
@Override
public String toString() {
return "RefDI{" +
"helloSpring1=" + helloSpring1 +
", helloSpring2=" + helloSpring2 +
'}';
}
}
配置文件:
<!--ref-->
<!--定义一个Bean-->
<bean name="helloSpring3" class="com.spring.core.HelloSpring"/>
<bean class="com.spring.core.RefDI" id="refDI">
<!--使用ref属性引用helloSpring3的bean-->
<constructor-arg name="helloSpring1" ref="helloSpring3"/>
<!--使用ref标签引用helloSpring3的bean-->
<constructor-arg name="helloSpring2">
<ref bean="helloSpring3"/>
</constructor-arg>
</bean>
测试:
@Test
public void ref() {
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("DI.xml");
System.out.println(Arrays.toString(ac.getBeanDefinitionNames()));
System.out.println(ac.getBean("refDI", RefDI.class));
}
成功注入了其他bean:
初始化
[helloSpring3, refDI]
RefDI{helloSpring1=HelloSpring{hello='hello'}, helloSpring2=HelloSpring{hello='hello'}}
2.2 parent继承
< bean >、< ref >等标签中还有一个parent属性,这个属性用于指定目标bean将使用父bean的属性和配置,除了autowire、scope和lazy init属性,parent属性用于相同属性以及值的复用。parent属性的值与目标父bean的id属性或name属性中的一个值相同。
对于parent继承,这里又分几种情况:
- 如果父bean有class属性,而子bean没有class属性,那么子bean就是和父bean同一个class类型,相当于创建两个相同的对象。
- 如果父bean有class属性,而子bean也有class属性,那么允许它们是不同的类型,但是子bean必须含有父bean中定义的所有的注入方式。
- 如果父bean没有class属性,那么子bean必须定义class属性,这个父bean实际上类似于一个属性和值的模版,仅仅被值bean引用,实现配置复用,不能实例化,(这时父bean必须添加abstract="true"属性,表示父bean不会被创建,类似于于抽象类,否则启动容器会尝试父bean,但是由于父bean没有class而抛出异常:No bean class specified on bean definition)。这种情况下,子bean同样必须含有父bean中定义的所有的注入方式。
- 这里的父bean和子bean 以及“继承”,并不是Java中的继承关系,仅仅是复用了注入方式,精简了代码!
- 如果子bean和父bean中注入对相同依赖同时注入的值的话,那么可能会相互覆盖对方的值。这根据依赖注入的先后顺序:父bean的构造器注入->子bean的构造器注入->父bean的setter注入->子bean的setter注入,排序在后面的对相同依赖的注入值将会覆盖之前注入的值!
如下案例,首先有三个类:
/**
* @author lx
*/
public class ParentOne {
private String property1;
public void setProperty1(String property1) {
this.property1 = property1;
}
@Override
public String toString() {
return "ParentOne{" +
"property1='" + property1 + '\\'' +
'}';
}
}
一个有意思的地方是,虽然ParentTwo继承了ParentOne,但是并没有继承私有属性property1,不过由于继承了setProperty1方法,因此仍然能够正常工作,这就是前面说的“子bean必须含有父bean中定义的所有的注入方式”的含义,对于setter方法注入来说,你没这个属性没关系,只要有个同名方法,参数类型能够兼容(从String转为参数类型)就不会报错,与返回值无关(见ParentThree)!
3 idref引用校验值
< idref >标签通常可以作为< constructor-arg >、< property >以及某些集合标签的子标签,用于将容器中另一个 bean的id或者name的字符串值(并不是引用)传递给< constructor-arg >、< property >以及某些集合标签,同时使用idref容器在部署的时候还会验证这个名称的bean是否真实存在(被定义了),这是一种防止错误的方法。该标签目前用的比较少。
如下案例,首先有一个IdrefCheck类,用于校验bean是否被定义了:
public class IdrefCheck {
private String targetName;
public void setTargetName(String targetName) {
this.targetName = targetName;
}
@Override
public String toString() {
return "IdrefDI{" +
"targetName='" + targetName + '\\'' +
'}';
}
}
配置文件:
<!--idref-->
<!--定义一个Bean-->
<bean name="helloSpring3" class="com.spring.core.HelloSpring" />
<!--idrefCheck校验bean-->
<bean class="com.spring.core.IdrefCheck" name="idrefCheck">
<!--实际上就等于<property name="targetName" value="helloSpring3">-->
<!--但是多了bean校验的功能-->
<property name="targetName">
<idref bean="helloSpring3"/>
</property>
</bean>
实际上< idref >的bean属性引用的值就是等于一个String类型的值,都是字符串,但是< idref >多了一个校验对应名称的bean是否存在的功能!
在idea中,如果idref的bean属性指定的bean名字不存在容器中,那么直接报红,如果运行,那么会抛出:Invalid bean name 'helloSpring3' in bean reference for bean property 'targetName'
。
@Test
public void idref() {
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("DI.xml");
System.out.println(Arrays.toString(ac.getBeanDefinitionNames()));
System.out.println(ac.getBean("idrefDI", IdrefCheck.class));
}
结果如下:
org.springframework.beans.factory.BeanCreationException: Error creating
bean with name 'idrefDI' defined in class path resource [DI.xml]: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean name 'helloSpring3' in bean reference for bean property 'targetName'
在很久之前(Spring2.0之前的版本中),< idref >标签的一个常用的作用是在用在ProxyFactoryBean中定义的AOP拦截器里。指定拦截器名称时使用< idref >元素可防止你拼写错误拦截器ID。但是目前用的比较少了!
4 内部bean
< bean >标签的内部可以使用< constructor-arg >、< property >以及某些集合标签,表示依赖注入。同样,在< constructor-arg >、< property >以及某些集合标签中也可以使用< bean >子标签,表示一个内部bean。原因很简单,如果我们注入的是一个对象,并且我们不想要通过ref引用其他已存在的bean,那么只有定义自己的内部的bean。
和“外部”bean的区别是,内部bean不需要定义id或者name属性,因为这个对象就相当于一个外部bean自己的对象。就算指定了,容器也不会使用这些值作为bean的名字,我们也不能通过IoC容器获取。容器在创建时也会忽略内部bean的scope作用域属性(后面会讲),因为内部 bean 始终是匿名的,并且始终使用外 bean 创建。无法独立访问内部bean,也无法将它们注入其他外部bean中。
如下案例,首先有一个InnerBean类,用于内部bean测试:
/**
* 内部bean
*
* @author lx
*/
public class InnerBean {
private InnerBeanInner innerBeanInner1;
private InnerBeanInner innerBeanInner2;
public void setInnerBeanInner1(InnerBeanInner innerBeanInner1) {
this.innerBeanInner1 = innerBeanInner1;
}
public void setInnerBeanInner2(InnerBeanInner innerBeanInner2) {
this.innerBeanInner2 = innerBeanInner2;
}
@Override
public String toString() {
return "InnerBean{" +
"innerBeanInner1=" + innerBeanInner1 +
", innerBeanInner2=" + innerBeanInner2 +
'}';
}
public static class InnerBeanInner {
private String property1;
private int property2;
public void setProperty1(String property1) {
this.property1 = property1;
}
public void setProperty2(int property2) {
this.property2 = property2;
}
@Override
public String toString() {
return "InnerBeanInner{" +
"property1='" + property1 + '\\'' +
", property2=" + property2 +
'}';
}
}
}
配置文件:
<!--内部bean-->
<bean id="innerBean" class="com.spring.core.InnerBean">
<property name="innerBeanInner1">
<!--内部bean 不需要指定id或者name-->
<bean class="com.spring.core.InnerBean.InnerBeanInner">
<property name="property1" value="aaa"/>
<property name="property2" value="111"/>
</bean>
</property>
<property name="innerBeanInner2">
<!--内部bean 指定id或者name也没用,不能通过容器获取到-->
<bean id="innerBeanInner" class="com.spring.core.InnerBean.InnerBeanInner">
<property name="property1" value="bbb"/>
<property name="property2" value="222"/>
</bean>
</property>
</bean>
测试:
@Test
public void innerBean() {
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("DI.xml");
System.out.println(Arrays.toString(ac.getBeanDefinitionNames()));
InnerBean innerBean = ac.getBean("innerBean", InnerBean.class);
System.out.println(innerBean);
}
结果:
[innerBean]
InnerBean{innerBeanInner1=InnerBeanInner{property1='aaa', property2=111}, innerBeanInner2=InnerBeanInner{property1='bbb', property2=222}}
5 集合注入
5.1 注入方式
Spring提供了详细的集合注入方式。< list >、< set >、< map >、< props >、< array >标签分别用来注入Java中的List、Set、Map、Properties、array类型的集合,主要是用于集合类型的依赖项的注入。因为集合的元素既可以是基本类型也可以是对象甚至集合,因此集合注入非常灵活。另外集合注入支持泛型转换,注入的时候会自动将value的字符串值转换为对应泛型类型!
如下案例,首先有一个CollectionDI类,用于集合注入测试:
/**
* 集合注入
*
* @author lx
*/
public class CollectionDI {
//集合属性注入
private List list;
private Set set;
private Map map;
private Properties properties;
private Object[] array;
public CollectionDI(List list, Set set, Map map, Properties properties, Object[] array) {
this.list = list;
this.set = set;
this.map = map;
this.properties = properties;
this.array = array;
}
static class CollectionInner {
private String property1;
private int property2;
public void setProperty1(String property1) {
this.property1 = property1;
}
public void setProperty2(int property2) {
this.property2 = property2;
}
@Override
public String toString() {
return "CollectionInner{" +
"property1='" + property1 + '\\'' +
", property2=" + property2 +
'}';
}
}
@Override
public String toString() {
return "CollectionDI{" +
"\\n" + "list=" + list +
"\\n" + ", set=" + set +
"\\n" + ", map=" + map +
"\\n" + ", properties=" + properties +
使用Spring框架入门二:基于注解+XML配置的IOC/DI的使用
Java--Spring之IoC控制反转;基于XML配置文件的DI
Spring 框架的概述以及Spring中基于XML的IOC配置
阶段3 2.Spring_03.Spring的 IOC 和 DI_3 spring基于XML的IOC环境搭建和入门
框架 day36 Spring3 入门,DI依赖注入,装配bean基于xml/注解, 整合Junit4,配置约束自动提示