目录索引
IoC的概念
所谓IoC, 即控制反转, 将对象的创建、对象之间关系的维护交给框架而不是程序员, 传统方式是由程序员主动创建对象并维护其引用关系, 使用Spring框架可以只描述要创建对象的特征, 创建的行为交给框架去处理.
IoC的原理
在Spring中, IoC通常需要一个XML
配置文件, 框架通过读取配置信息来创建对象并维护关系, 实现IoC的简单原理大致是下面三个步骤:
-
读取配置文件(
XML
解析); -
IoC容器根据解析结果通过反射机制, 创建对象;
-
对所创建对象进行依赖注入(调用
setter
或通过有参构造器注入);
更详细的原理可以参考Spring源码, 这里不再展开, 要注意的是以下几个点:
- IoC容器底层就是一个对象工厂, 是工厂模式的实现;
- 要进行依赖注入的属性, 要么有公共的
setter
的, 要么配置有参构造器注入, 否则将注入失败.
什么是DI?
DI即依赖注入, 指的是将被引用对象的构造和引用对象解耦, 即引用对象直接得到一个完整对象并进行使用, 而不关心这个对象的创建等无关事宜. 体现到方法中就是, 从原本一个方法接受各种参数来创建一个被引用对象, 到该方法直接接受这个对象本身(对象作为参数传递), 实际上没有多复杂的东西, 我们使用Java开发应用程序本身在方法中传递对象就是在不断使用DI的思想, 因为Java的设计原则是: 一切皆对象.
为什么说Spring是一个DI框架? 因为Spring负责创建对象并维护其引用关系. 简单来说, 假设有一个类型A和类型B, 其中A中有一个属性类型是B, 那么只需要经过适当的配置, Spring就可以创建A的实例和B的实例, 并将B的实例注入到A的属性中, 对于应用程序来说, 实现了"要什么给什么"的效果, 而创建过程是透明的, 这就是典型的依赖注入, 当然Spring可以注入的情形可不止自定义类型, 还可以注入简单类型、String和集合等.
IoC做什么?
IoC主要就做一件事, 即Bean管理, 而Bean管理做两件事, 其实前面已经说过了, 即:
- Spring创建对象;
- Spring注入属性;
下面介绍一下基于XML
如何实现Bean管理.
在编码中, 实现IoC的步骤
0. 编写配置文件
配置文件可以是XML
文件、Java配置类(注解开发), 本文记录XML
实现, 后文会介绍注解开发.
1. 获取IoC容器
上文说到, IoC容器底层是一个工厂, 为了获取工厂进行对象创建, Spring提供了两个接口:
- BeanFactory: IoC容器的基本实现, 是Spring内部接口, 主要由Spring内部使用;
- ApplicationContext: BeanFactory的一个子接口, 增强了功能, 建议开发人员使用.
两个接口的主要区别:
- BeanFactory: 默认情况下, 加载配置文件时不会创建对象, 获取对象时才创建对象;
- ApplicationContext: 默认情况下, 加载配置文件时会创建好配置文件中的对象.
以一般使用的ApplicationContext为例, 其常用的实现类有:
- ClassPathXmlApplicationContext: 通过类路径解析
XML
配置文件; - FileSystemXmlApplicationContext: 通过文件路径解析
XML
配置文件; - AnnotationConfigApplicationContext: 完全注解开发下, 解析配置类.
2. 通过IoC容器, 获取对象
到这一步直接调用IoC容器的方法, 即可获取到对象, 下面做一个演示.
简单演示创建对象
- 先声明一个简单Bean, 叫做
Employee
, 如下:
package com.yjzzjy4.learning.beans;
public class Employee {
private String name;
public Employee() {}
// getter && setter...
}
- 写一个配置文件
Spring-Conf-0.xml
, 如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="emp" class="com.yjzzjy4.learning.beans.Employee"/>
</beans>
- 编写一个测试类
Test0
, 如下:
package com.yjzzjy4.learning.test;
import com.yjzzjy4.learning.beans.Employee;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test0 {
public static void main(String[] args) {
// 读取配置文件, 获取IoC容器;
ApplicationContext context = new ClassPathXmlApplicationContext("Spring-Conf-0.xml");
// 获取对象;
Employee employee = context.getBean("employee", Employee.class);
System.out.println(employee + "\n" + employee.getName());
}
}
- 运行结果如下:
![](https://img-blog.csdnimg.cn/20210315165630540.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FBTWFob25l,size_16,color_FFFFFF,t_70)
注意事项:
<bean>
标签的id
属性是用作IoC容器的getBean方法的获取对象的唯一标识;<bean>
标签的class
属性用于指定类的完整路径(带包名).
依赖注入的方式
我们可以通过Spring为创建的对象注入属性值, 其方式有多种, 下面一一进行介绍:
通过有参构造器注入
默认情况下, Spring通过无参构造器初始化对象, 若是一个类中没有无参构造器, 且并未配置构造器参数注入属性值, 则会抛出异常, 这种情况不在此演示, 下面我们介绍通过有参构造器注入属性值的方法.
- 首先修改
Employee
的声明, 为其新增有参构造器:
package com.yjzzjy4.learning.beans;
public class Employee {
private String name;
public Employee() {}
public Employee(String name) {
this.name = name;
}
// getter && setter...
}
- 然后修改配置文件, 实现有参构造器注入:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="employee0" class="com.yjzzjy4.learning.beans.Employee"/>
<bean id="employee1" class="com.yjzzjy4.learning.beans.Employee">
<constructor-arg name="name" value="yjzzjy4"/>
</bean>
</beans>
- 修改测试类
Test0
, 对比生成的两个对象:
package com.yjzzjy4.learning.test;
import com.yjzzjy4.learning.beans.Employee;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test0 {
public static void main(String[] args) {
// 读取配置文件, 获取IoC容器;
ApplicationContext context = new ClassPathXmlApplicationContext("Spring-Conf-0.xml");
// 获取对象;
Employee employee0 = context.getBean("employee0", Employee.class);
Employee employee1 = context.getBean("employee1", Employee.class);
System.out.println(employee0 + "\n" + employee0.getName());
System.out.println(employee1 + "\n" + employee1.getName());
}
}
- 运行结果如下:
![](https://img-blog.csdnimg.cn/20210315165805580.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FBTWFob25l,size_16,color_FFFFFF,t_70)
注意事项:
- 可以看到生成了两个不同的对象, employee1是使用有参构造器注入的name属性值, employee0使用无参构造器, 因此name属性是默认值null;
<constructor-arg>
标签是指定构造器参数的标签, 可以有多个, 其name
属性值指定参数名称, 也可以用index
指定参数索引(不推荐),value
属性值指定简单类型或字符串的值,ref
可以指定引用对象的值(通过<bean>
的id
属性值指定, 后面会详细说).
通过setter注入
通过配置, Spring可以先创建对象, 再使用对象的setter
进行依赖注入, 例子如下:
- 创建一个新的配置文件
Spring-Conf-1.xml
, 如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="employee" class="com.yjzzjy4.learning.beans.Employee">
<property name="name" value="Aaron"/>
</bean>
</beans>
- 新建一个测试类
Test1
, 如下:
package com.yjzzjy4.learning.test;
import com.yjzzjy4.learning.beans.Employee;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test1 {
public static void main(String[] args) {
// 读取配置文件, 获取IoC容器;
ApplicationContext context = new ClassPathXmlApplicationContext("Spring-Conf-1.xml");
// 获取对象;
Employee employee = context.getBean("employee", Employee.class);
System.out.println(employee + "\n" + employee.getName());
}
}
- 运行结果如下:
![](https://img-blog.csdnimg.cn/20210315165931786.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FBTWFob25l,size_16,color_FFFFFF,t_70)
注意事项:
- 要注入的属性必须要有对应的
setter
, 否则注入失败; <property>
属性要嵌入<bean>
中使用;<property>
的三个属性name
、value
和ref
含义和上文所述无异.
通过p名称空间注入
p名称空间其实就是setter
注入的一种简化写法, 下面进行演示:
- 创建一个新的配置文件
Spring-Conf-2.xml
, 如下:
<?xml version="1.0" encoding="UTF-8"?>
<!--引入p名称空间-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="employee" class="com.yjzzjy4.learning.beans.Employee" p:name="Tom"/>
</beans>
- 新建一个测试类
Test2
, 如下:
package com.yjzzjy4.learning.test;
import com.yjzzjy4.learning.beans.Employee;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test2 {
public static void main(String[] args) {
// 读取配置文件, 获取IoC容器;
ApplicationContext context = new ClassPathXmlApplicationContext("Spring-Conf-2.xml");
// 获取对象;
Employee employee = context.getBean("employee", Employee.class);
System.out.println(employee + "\n" + employee.getName());
}
}
- 运行结果如下:
![](https://img-blog.csdnimg.cn/20210315170032837.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FBTWFob25l,size_16,color_FFFFFF,t_70)
注意事项:
- p名称空间可以省略
<property>
标签, 直接在<bean>
标签中以p:
开头的属性指定属性注入, 其中值类型的属性可以使用p:propertyName
指定值, 引用类型的属性可以用p:propertyName-ref
注入其引用的对象; - 使用时需要引入p名称空间, 在配置文件中已有注释, 写法是固定的, 无需记忆.
引用类型的注入
上述例子演示的都是简单类型或String字面量的注入, 可以理解为值类型, 那么我们要如何注入一个引用类型的属性呢? 其实也很简单, 下面分几种情况进行说明.
注入外部Bean
- 我们先创建一个名为
Department
的类, 如下:
package com.yjzzjy4.learning.beans;
public class Department {
private String name;
public Department() {}
public Department(String name) {
this.name = name;
}
// getter && setter...
}
- 再为
Employee
对象新增一个对Department
的引用属性, 如下:
package com.yjzzjy4.learning.beans;
public class Employee {
private String name;
private Department department;
public Employee() {}
public Employee(String name) {
this.name = name;
}
// getter && setter...
}
- 创建一个新的配置文件
Spring-Conf-3.xml
, 如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="employee" class="com.yjzzjy4.learning.beans.Employee">
<constructor-arg name="name" value="Tom"/>
<property name="department" ref="department"/>
</bean>
<bean id="department" class="com.yjzzjy4.learning.beans.Department">
<constructor-arg name="name" value="财务部"/>
</bean>
</beans>
- 新建一个测试类
Test3
, 如下:
package com.yjzzjy4.learning.test;
import com.yjzzjy4.learning.beans.Employee;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test3 {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Spring-Conf-3.xml");
Employee employee = context.getBean("employee", Employee.class);
System.out.println(employee + "\n" + employee.getName());
System.out.println(employee.getDepartment().getName());
}
}
运行结果如下:
![](https://img-blog.csdnimg.cn/20210315170150868.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FBTWFob25l,size_16,color_FFFFFF,t_70)
注意事项:
- 在配置文件中配置了两个
<bean>
, 其中employee依赖于department; - 使用
<property>
的ref
属性指明要注入的对象的id
, 凡是用来指定要注入的属性的标签, 都可以用ref
属性, 如<constructor-arg>
标签; - 在测试类中, 虽然没有通过IoC容器主动取出department对象, 但还是可以看到它作为属性被注入employee中了, 这说明Spring会自动管理对象间的依赖关系, 体现了DI的思想.
注入内部Bean
刚才department的<bean>
标签是写在整个employee的<bean>
标签外面, 属于同级关系, 故称作外部Bean, 内部Bean就是在属性需要用到引用对象的时候, 直接创建一个<bean>
节点.
- 创建一个新的配置文件
Spring-Conf-4.xml
, 如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="employee" class="com.yjzzjy4.learning.beans.Employee">
<constructor-arg name="name" value="Jackson"/>
<property name="department">
<!--嵌套一个内部<bean>-->
<bean class="com.yjzzjy4.learning.beans.Department">
<constructor-arg name="name" value="财务部"/>
</bean>
</property>
</bean>
</beans>
- 新建一个测试类
Test4
, 如下:
package com.yjzzjy4.learning.test;
import com.yjzzjy4.learning.beans.Employee;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test4 {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Spring-Conf-4.xml");
Employee employee = context.getBean("employee", Employee.class);
System.out.println(employee + "\n" + employee.getName());
System.out.println(employee.getDepartment().getName());
}
}
运行结果如下:
![](https://img-blog.csdnimg.cn/20210315170255939.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FBTWFob25l,size_16,color_FFFFFF,t_70)
注意事项:
- 内部Bean的
id
属性是多余的, 即使设置也没法通过IoC容器来获取内部Bean的对象, 会抛出"NoSuchBeanDefinitionException"异常. 因为内部Bean是作为一个部分嵌套在另一个Bean中的, 无法作为完整的Bean被单独创建; - 不仅可以在
<property>
标签中嵌套<bean>
标签, 实际上任何一个需要注入对象的地方都可以使用, 比如可以在<constructor-arg>
中嵌套(前提是要有对应的构造器参数).
级联赋值
所谓级联赋值, 可以简单描述为以下过程:
- 类A依赖于类B, 且a、b分别是类A、B的一个实例;
- 为b的属性c赋值, 然后将b注入到a中, 则a.b.c也相当于获得了一个新的值.
很明显通过之前注入外部Bean的配置, 我们已经完成了一次级联赋值, 现在介绍另一种配置方式.
- 创建一个新的配置文件
Spring-Conf-5.xml
, 如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="employee" class="com.yjzzjy4.learning.beans.Employee">
<constructor-arg name="name" value="Tom"/>
<property name="department" ref="department"/>
<!--级联赋值-->
<property name="department.name" value="行政部"/>
</bean>
<bean id="department" class="com.yjzzjy4.learning.beans.Department">
<constructor-arg name="name" value="财务部"/>
</bean>
</beans>
- 新建一个测试类
Test5
, 如下:
package com.yjzzjy4.learning.test;
import com.yjzzjy4.learning.beans.Employee;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test5 {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Spring-Conf-5.xml");
Employee employee = context.getBean("employee", Employee.class);
System.out.println(employee + "\n" + employee.getName());
System.out.println(employee.getDepartment().getName());
}
}
运行结果如下:
![](https://img-blog.csdnimg.cn/20210315170450581.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FBTWFob25l,size_16,color_FFFFFF,t_70)
注意事项:
<property>
的name
属性值是属性名.属性名的形式, 可以对Bean中的复杂类型属性的对象的属性进行赋值, 前提有以下两点:
复杂类型属性的对象不能为null. 在本例中即employee中的department对象不能为null, 要么通过注入外部Bean, 要么通过注入内部Bean来保证其不为null, 否则就会抛出一个原理类似空指针异常的错误: “NullValueInNestedPathException”.
Bean中对要赋值的对象必须要有
getter
, 要赋值的对象的目标属性必须要有setter
. 因为Spring要先获取该对象, 再为其属性设置值, 本例中其原理可以大致抽象表示为:employee.getDepartment().setName("行政部");
-
理论上, 级联赋值可以超过两级, 只要满足上述赋值的条件即可;
-
级联赋值也可以注入对象, 使用
ref
属性即可, 或者注入内部Bean.
注入集合(Array、List、Set)
在开发中, 集合(或者说容器)是非常常用的一种组件, Java为我们提供了一套完善的集合框架, 而Spring自然是对这些集合作为属性的注入有着很好的支持, 下面我们演示一下.
- 员工和部门是一个典型的一对多关系, 考虑到数据层面的引用(比如级联查询等), 我们通常会在
Department
类中声明一个Employee
的容器, 修改Department
的声明如下:
package com.yjzzjy4.learning.beans;
import java.util.Set;
public class Department {
private String name;
private Set<Employee> employees;
public Department() {}
public Department(String name) {
this.name = name;
}
// getter && setter...
}
- 创建一个新的配置文件
Spring-Conf-6.xml
, 如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="employee" class="com.yjzzjy4.learning.beans.Employee">
<constructor-arg name="name" value="Susan"/>
<property name="department" ref="department"/>
</bean>
<bean id="department" class="com.yjzzjy4.learning.beans.Department">
<constructor-arg name="name" value="财务部"/>
<property name="employees">
<set>
<!--内部Bean-->
<bean class="com.yjzzjy4.learning.beans.Employee">
<constructor-arg name="name" value="Sara"/>
<property name="department" ref="department"/>
</bean>
<bean class="com.yjzzjy4.learning.beans.Employee">
<constructor-arg name="name" value="Aaron"/>
<property name="department" ref="department"/>
</bean>
<!--引用外部Bean-->
<ref bean="employee"/>
</set>
</property>
</bean>
</beans>
- 新建一个测试类
Test6
, 如下:
package com.yjzzjy4.learning.test;
import com.yjzzjy4.learning.beans.Department;
import com.yjzzjy4.learning.beans.Employee;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test6 {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Spring-Conf-6.xml");
Department department = context.getBean("department", Department.class);
System.out.println(department.getName());
for(Employee e : department.getEmployees()) {
System.out.println(e.getDepartment().getName() + " " + e.getName());
}
}
}
运行结果如下:
![](https://img-blog.csdnimg.cn/20210315170641872.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FBTWFob25l,size_16,color_FFFFFF,t_70)
注意事项:
<set>
是用于注入集合的标签, 关于这个标签, 说明如下:
集合, 容器也. 既然是容器, 一定是要往里面装东西的, 可以装对象, 也可以装简单类型的值(最常见的就是字符串直接量), 针对不同的使用场景, 有不同的写法, 如:
- 值类型的集合:
<set> <value></value> <value></value> ... <value></value> </set>
- 引用类型的集合(内部Bean写法):
<set> <bean></bean> <bean></bean> ... <bean></bean> </set>
- 引用类型的集合(外部Bean写法):
<set> <ref bean="xxx"></ref> <ref bean="yyy"></ref> ... <ref bean="zzz"></ref> </set>
当然内部Bean和外部Bean也可以混用, 本例已经演示了混用的情况.
-
其他集合类型如
Array
、List
等用法也都如Set
, 只是最外层标签从<set>
改为<array>
、<list>
而已, 这里就不再赘述, 用时举一反三即可; -
Map
由于是K-V结构的容器, 用法会稍有不同, 接下来会介绍如何注入Map
.
注入Map
- 为了方便, 我们直接新建一个Bean来进行演示,
MapInjection
声明如下:
package com.yjzzjy4.learning.beans;
import java.util.Map;
public class MapInjection {
private Map<String, String> map;
public MapInjection() {}
// getter && setter...
}
- 创建一个新的配置文件
Spring-Conf-7.xml
, 如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="mapInjection" class="com.yjzzjy4.learning.beans.MapInjection">
<property name="map">
<map>
<entry key="k0" value="v0"/>
<entry key="k1" value="v1"/>
<entry key="k2" value="v2"/>
</map>
</property>
</bean>
</beans>
- 新建一个测试类
Test7
, 如下:
package com.yjzzjy4.learning.test;
import com.yjzzjy4.learning.beans.MapInjection;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.Map;
public class Test7 {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Spring-Conf-7.xml");
MapInjection mapInjection = context.getBean("mapInjection", MapInjection.class);
for(Map.Entry<String, String> entry : mapInjection.getMap().entrySet()) {
System.out.println(entry.getKey() + " " + entry.getValue());
}
}
}
运行结果如下:
![](https://img-blog.csdnimg.cn/2021031517075349.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FBTWFob25l,size_16,color_FFFFFF,t_70)
注意事项:
<map>
应该嵌套<entry>
标签, 且<entry>
是无值标签, 一般写成<entry/>
;<entry>
有四个常用属性:key
、value
、key-ref
、value-ref
, 用于指定键、值, 且键、值都可以是引用类型, 指定时用id
属性值标识.
将集合提取为公共组件
之前注入集合属性的时候, 都是在<property>
中嵌套对应的集合标签, 那么能不能将集合提取到<bean>
之外, 与其平级, 成为一个单独的组件呢? 因为相同类型的集合可能有非常多个, 这样就不用每次需要写一个内嵌集合了, 提高复用率. 答案当然是可以的, 借助util名称空间的帮助即可, 下面进行演示.
- 创建一个新的配置文件
Spring-Conf-8.xml
, 如下:
<?xml version="1.0" encoding="UTF-8"?>
<!--引入util名称空间-->
<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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<bean id="employee" class="com.yjzzjy4.learning.beans.Employee">
<constructor-arg name="name" value="Susan"/>
<property name="department" ref="department"/>
</bean>
<bean id="department" class="com.yjzzjy4.learning.beans.Department">
<constructor-arg name="name" value="财务部"/>
<property name="employees" ref="employees"/>
</bean>
<!--提取set集合-->
<util:set id="employees">
<!--引用外部Bean-->
<ref bean="employee"/>
<!--内部Bean-->
<bean class="com.yjzzjy4.learning.beans.Employee">
<constructor-arg name="name" value="Aaron"/>
<property name="department" ref="department"/>
</bean>
<bean class="com.yjzzjy4.learning.beans.Employee">
<constructor-arg name="name" value="Sara"/>
<property name="department" ref="department"/>
</bean>
</util:set>
</beans>
- 新建一个测试类
Test8
, 如下:
package com.yjzzjy4.learning.test;
import com.yjzzjy4.learning.beans.Department;
import com.yjzzjy4.learning.beans.Employee;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test8 {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Spring-Conf-8.xml");
Department department = context.getBean("department", Department.class);
System.out.println(department.getName());
for(Employee e : department.getEmployees()) {
System.out.println(e.getDepartment().getName() + " " + e.getName());
}
}
}
运行结果如下:
![](https://img-blog.csdnimg.cn/20210315170943950.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FBTWFob25l,size_16,color_FFFFFF,t_70)
注意事项:
- 除了Array以外的集合都可以提取出来, util命名空间提供了
<util:list>
、<util:set>
、<util:map>
等标签, 通过<util:constant>
还可以定义常量; - util名称空间的引入是固定写法, 无需记忆.
FactoryBean
Spring中有两种Bean, 一种是普通Bean, 一种是FactoryBean, 这两种Bean最大的区别在于:
普通Bean在注入时只能注入其自身类型的对象, FactoryBean一般注入其他类型的对象.
FactoryBean其实是一个接口, 实现该接口的Bean就是一个FactoryBean, 我们看一眼API:
![](https://img-blog.csdnimg.cn/20210315171147422.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FBTWFob25l,size_16,color_FFFFFF,t_70)
简单翻译一下重点:
- 实现了FactoryBean接口的Bean不能被当作普通Bean使用;
- FactoryBean是一种编程约定, 实现类不应该依赖注解方式注入或者其他反射机制(创建对象);
- (IoC)容器只为FactoryBean的生命周期负责, 而不为其创建的对象负责.
我个人主观的理解:
- FactoryBean是用来提供其它Bean实例的工厂, 并不是提供自身的实例, 且一个FactoryBean对应一个Bean, 其getObject方法应返回它创建的Bean的实例;
- 不要用注解方式或者反射方式创建对象, FactoryBean中的方法getObject、getObjectType可能在启动过程提前调用, 甚至所有后置处理器之前, 由于其不确定性, 使用注解或反射可能会带来意想不到的后果, 如果需要用其他Bean, 实现BeanFactoryAware接口, 通过编程方式获取;
- 容器不会主动调用被创建的对象的销毁方法(如Closeable.close()), 因此, (如有需要)一个FactoryBean应该实现DisposableBean接口, 并将所有与销毁相关的操作通过代理的方式实现;
总之, 这是一个在Spring框架内部被大量使用, 开发者也可以用来自定义组件的一个接口, 至于如何自定义组件, 由于笔者初来乍到, 水平有限, 以后用上就会知道的, 现在先让我们看看其方法声明:
![](https://img-blog.csdnimg.cn/20210315171234511.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FBTWFob25l,size_16,color_FFFFFF,t_70)
一共就三个方法, 用法一目了然, 其他就不多说了, 注意一下isSingleton这个方法, 可以用来控制生成的对象是否是单例, 下面做一个简单演示:
- 新建一个FactoryBean实现类
FactoryBeanImpl
:
package com.yjzzjy4.learning.beans;
import org.springframework.beans.factory.FactoryBean;
public class FactoryBeanImpl implements FactoryBean<Employee> {
@Override
public boolean isSingleton() {
return false;
}
@Override
public Employee getObject() throws Exception {
return new Employee("Johnathon");
}
@Override
public Class<?> getObjectType() {
return Employee.class;
}
}
- 创建一个新的配置文件
Spring-Conf-9.xml
, 如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--这里的class写的是FactoryBean的类型-->
<bean id="factoryBeanImpl" class="com.yjzzjy4.learning.beans.FactoryBeanImpl"/>
</beans>
- 新建一个测试类
Test9
, 如下:
package com.yjzzjy4.learning.test;
import com.yjzzjy4.learning.beans.Employee;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test9 {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Spring-Conf-9.xml");
// 请注意, 这里获取的对象类型是Employee;
Employee employee = context.getBean("factoryBeanImpl", Employee.class);
System.out.println(employee + "\n" + employee.getName());
// 再获取一个对象, 观察区别;
employee = context.getBean("factoryBeanImpl", Employee.class);
System.out.println(employee + "\n" + employee.getName());
}
}
运行结果如下:
![](https://img-blog.csdnimg.cn/20210315172839827.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FBTWFob25l,size_16,color_FFFFFF,t_70)
注意事项:
- 很容易可以看出, 我们获取的对象其实是FactoryBean的getObject创建的那个对象, 但是在配置文件中声明的时候, 需要指定
<bean>
的class
属性为FactoryBean实现类的类型; - 由于设置了isSingleton的返回值为false, 因此获取的对象是不同对象, 看输出即可看出.
Bean的作用范围
上个例子中我们看到, 设置FactoryBean的isSingleton方法返回值可以决定创建的对象是否是单例, 对于普通的Bean, Spring可以通过<bean>
的scope
属性进行设置, 常用值有:
singleton
: 单例, Spring的默认值, 每次从容器中获取的都是同一个对象.prototype
: 多例, 每次获取都创建一个新的对象;session
: 用于web编程, 在一次会话(Session)中有效;request
: 用于web编程, 在一次请求(Request)中有效.
其中singleton
和prototype
属性值还有一个区别:
前面说过: 默认情况下BeanFactory到获取对象的时候才创建, ApplicationContext在加载配置文件的时候就创建对象, 而当
<bean>
标签的scope
值为prototype
时, ApplicationContext也只等到对象被获取(直接获取或被其他对象依赖)时才会创建.
我们重点关注singleton
和prototype
两个属性值, 举例来说明一下其区别:
- 创建一个新的配置文件
Spring-Conf-10.xml
, 如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="financeDept" class="com.yjzzjy4.learning.beans.Department">
<constructor-arg name="name" value="财务部"/>
</bean>
<bean id="jessie" class="com.yjzzjy4.learning.beans.Employee">
<constructor-arg name="name" value="Jessie"/>
<property name="department" ref="financeDept"/>
</bean>
<bean id="sara" class="com.yjzzjy4.learning.beans.Employee">
<constructor-arg name="name" value="Sara"/>
<property name="department" ref="financeDept"/>
</bean>
<!--唯一的多例Bean-->
<bean id="techniqueDept" class="com.yjzzjy4.learning.beans.Department" scope="prototype">
<constructor-arg name="name" value="技术部"/>
</bean>
<bean id="aaron" class="com.yjzzjy4.learning.beans.Employee">
<constructor-arg name="name" value="Aaron"/>
<property name="department" ref="techniqueDept"/>
</bean>
<bean id="johnathon" class="com.yjzzjy4.learning.beans.Employee">
<constructor-arg name="name" value="Johnathon"/>
<property name="department" ref="techniqueDept"/>
</bean>
</beans>
- 新建一个测试类
Test10
, 如下:
package com.yjzzjy4.learning.test;
import com.yjzzjy4.learning.beans.Department;
import com.yjzzjy4.learning.beans.Employee;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.HashSet;
import java.util.Set;
public class Test10 {
private static void outTest(Set<Department> departments, Set<Employee> employees) {
for(Department department : departments) {
System.out.println(department + " " + department.getName());
}
System.out.println();
for(Employee employee : employees) {
System.out.println(employee + " " + employee.getName());
System.out.println(employee.getDepartment() + " " + employee.getDepartment().getName() + "\n");
}
}
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Spring-Conf-10.xml");
Department financeDept = context.getBean("financeDept", Department.class);
Department techniqueDept = context.getBean("techniqueDept", Department.class);
Employee jessie = context.getBean("jessie", Employee.class);
Employee sara = context.getBean("sara", Employee.class);
Employee aaron = context.getBean("aaron", Employee.class);
Employee johnathon = context.getBean("johnathon", Employee.class);
// 创建集合并加入元素;
Set<Department> departments = new HashSet<>();
departments.add(financeDept);
departments.add(techniqueDept);
Set<Employee> employees = new HashSet<>();
employees.add(jessie);
employees.add(sara);
employees.add(aaron);
employees.add(johnathon);
System.out.println("Before: ");
outTest(departments, employees);
// 重新获取对象, 并修改属性值;
financeDept = context.getBean("financeDept", Department.class);
techniqueDept = context.getBean("techniqueDept", Department.class);
financeDept.setName("财务部-New");
techniqueDept.setName("技术部-New");
// 重新加入集合;
departments.add(financeDept);
departments.add(techniqueDept);
System.out.println("After: ");
outTest(departments, employees);
}
}
运行结果如下:
![](https://img-blog.csdnimg.cn/20210315172958930.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FBTWFob25l,size_16,color_FFFFFF,t_70)
对结果说明如下:
- 首先, 程序中创建了4个Employee的对象, 2个Department的对象, 其中一个Department的Bean: techniqueDept是多例, 其余均为单例(默认);
- 一开始取出这些对象, 并将其分别置于两个集合中: employees、departments, 类型均为HashSet. 而Set集合有自动去重的功能, 由于没重写这些类的equals和hashCode方法, 因此对象判重直接根据其在JVM中分配的内存地址进行;
- 输出两个容器中的所有对象的关键值, 可以看到: 依赖于单例Bean注入属性的对象, 其对应属性引用的是同一个对象, 即jessie和sara这两个对象的department属性引用同一个对象, 其地址和departments集合中的那个对象相同, 而aaron和johnathon这两个对象的department属性引用不同对象, 且其地址和departments集合中的那个对象不同;
- 再次从容器中取出两个Department的Bean, 并且对其name属性进行修改, 再依次放入departments集合中, 然后再次输出两个集合中的所有对象;
- 此时departments中有3个对象, 说明有一个对象在放入集合时被去重了, 很显然是那个financeDept对象, 与前一次结果对比, 不难看出新增了一个配置
id
为techniqueDept的对象, 且其name属性值被改成了"技术部-New", 容器中原有的那个配置id
为financeDept的对象的name属性值被改成了"财务部-New";- 此时employees中对象
id
没有发生变化, 与前一次结果对比, 只是jessie和sara这两个对象的department.name属性都变成了"财务部-New".
注意事项:
scope
属性默认使用singleton
, 则当这个<bean>
作为外部Bean注入到其他Bean中时, 可能会引发问题: 修改外部Bean的属性时, 其他Bean的相应属性也会被修改, 因为所谓"注入", 其实就是一个引用的过程(当然作为内部Bean是不存在这个问题的, 但内部Bean的scope
属性其实也没有意义). 当项目复杂的时候, 往往可能会在某个地方进行修改而影响到其他地方, 发生意想不到的结果, 因此最好只在一些无状态的Bean中使用singleton
, 比如Service层的Bean;- Spring中的单例不是线程安全的, 因此在多线程环境下不能认为用默认的单例是可靠的, 还是要自己加上同步方法.
自动装配
以上的例子中, 内部Bean也好外部Bean也好, 对于依赖注入的实现全都是手动指定的, 也就是手动装配. Spring支持自动装配, 即不用显示指定注入的Bean, 通过定义规则即可让Spring自动将符合的Bean进行装配, Spring通过<bean>
标签的autowire
属性值, 支持两种自动装配的规则:
- 按名称自动装配:
autowire="byName"
; - 按类型自动装配:
autowire="byType"
.
- 创建一个新的配置文件
Spring-Conf-11.xml
, 如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="aaron" class="com.yjzzjy4.learning.beans.Employee" autowire="byName">
<constructor-arg name="name" value="Aaron"/>
</bean>
<bean id="johnathon" class="com.yjzzjy4.learning.beans.Employee" autowire="byType">
<constructor-arg name="name" value="Johnathon"/>
</bean>
<bean id="department" class="com.yjzzjy4.learning.beans.Department"/>
</beans>
- 新建一个测试类
Test11
, 如下:
package com.yjzzjy4.learning.test;
import com.yjzzjy4.learning.beans.Employee;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test11 {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Spring-Conf-11.xml");
Employee aaron = context.getBean("aaron", Employee.class);
System.out.println(aaron + " " + aaron.getName() + " " + aaron.getDepartment());
Employee johnathon = context.getBean("johnathon", Employee.class);
System.out.println(johnathon + " " + johnathon.getName() + " " + johnathon.getDepartment());
}
}
运行结果如下:
![](https://img-blog.csdnimg.cn/2021031517305282.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FBTWFob25l,size_16,color_FFFFFF,t_70)
注意事项:
byName
: 只有当id
属性值和要装配的Bean的属性名称相同时才会进行装配, 比如在这个例子中, 有id
属性值是department的Bean, 且Employee类中有一个属性也是department, 则进行自动装配, 当然不止是名称匹配就可以, 当类型不匹配时, 可能出现装配异常;byType
: 按照类型匹配自动装配, 但是配置文件中同一类型的Bean有多个时, 则无法进行自动装配, 因为无法确定使用哪个Bean进行装配.
外部属性文件
我们可以将一些常用的属性提取成属性文件, 再引入到XML
配置文件中, 这样可以减少XML
配置文件的内容, 提高复用, 修改常用属性的时候也不需要一个一个配置文件进行修改, 只需要修改外部属性文件即可, 最常见的是数据库连接之类的全局属性可以抽取出来, 下面进行演示:
- 新建一个属性文件,
jdbc.properties
:
prop.driverClass=com.mysql.cj.jdbc.Driver
prop.url=jdbc:mysql://localhost:3306/testDb
prop.username=root
prop.password=root
- 声明一个Bean, 叫做
DataSource
, 如下:
package com.yjzzjy4.learning.beans;
public class DataSource {
private String driver;
private String url;
private String username;
private String password;
public DataSource() {}
@Override
public String toString() {
return "DataSource{" + "driver='" + driver
+ '\'' + ", url='" + url + '\''
+ ", username='" + username
+ '\'' + ", password='"
+ password + '\'' + '}';
}
// getter && setter...
}
- 创建一个新的配置文件
Spring-Conf-12.xml
, 注意context名称空间的引入, 如下:
<?xml version="1.0" encoding="UTF-8"?>
<!--引入context名称空间-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--引入外部属性文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--读取外部属性文件, 完成Bean的属性配置-->
<bean id="dataSource" class="com.yjzzjy4.learning.beans.DataSource">
<property name="driver" value="${prop.driverClass}"/>
<property name="url" value="${prop.url}"/>
<property name="username" value="${prop.username}"/>
<property name="password" value="${prop.password}"/>
</bean>
</beans>
- 新建一个测试类
Test12
, 如下:
package com.yjzzjy4.learning.test;
import com.yjzzjy4.learning.beans.DataSource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test12 {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Spring-Conf-12.xml");
DataSource ds = context.getBean("dataSource", DataSource.class);
System.out.println(ds);
}
}
运行结果如下:
![](https://img-blog.csdnimg.cn/20210315173157769.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FBTWFob25l,size_16,color_FFFFFF,t_70)
注意事项:
- 属性名称一般建议以prop开头, 以免混淆;
- 引入context名称空间才可以引入外部属性文件, 使用
<context:property-placeholder>
标签的location
属性, 注意属性值可以写file:xxx
也可以写classpath:xxx
, 取决于你想通过文件路径还是类路径定位属性文件; - 引入属性时, 要使用Spring表达式, 把要引入的属性名写在
${}
中; - 引入context名称空间的写法是固定的, 无需记忆.
Bean的生命周期
Spring中Bean的生命周期, 一共有7个部分, 如下:
-
通过构造器创建Bean实例(默认无参构造器);
-
通过
setter
为Bean的属性设置值或引用; -
将Bean的实例传递给后置处理器的方法(postProcessBeforeInitialization)进行处理;
-
调用Bean的初始化方法(需要手动配置该方法);
-
将Bean的实例传递给后置处理器的方法(postProcessAfterInitialization)进行处理;
-
Bean准备完成, 可以获取并使用了;
-
容器关闭的时候, 调用Bean的销毁方法(需要手动配置该方法).
如果配置中没有添加后置处理器, 则Bean的生命周期将不会有2和4两步, 下面进行演示:
- 先实现一个后置处理器
PostProcessor
, 如下:
package com.yjzzjy4.learning.beans;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class PostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessBeforeInitialization");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInitialization");
return bean;
}
}
- 再新建一个Bean用以测试,
BeanLifeCycle
, 如下:
package com.yjzzjy4.learning.beans;
public class BeanLifeCycle {
private String name;
public BeanLifeCycle() {
System.out.println("constructor");
}
public void initMethod() {
System.out.println("initMethod");
}
public void destroyMethod() {
System.out.println("destroyMethod");
}
public void setName(String name) {
this.name = name;
System.out.println("setter");
}
// getter...
}
- 创建一个新的配置文件
Spring-Conf-13.xml
, 如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--指定初始化方法和销毁方法-->
<bean id="beanLifeCycle" class="com.yjzzjy4.learning.beans.BeanLifeCycle" init-method="initMethod" destroy-method="destroyMethod">
<property name="name" value="value"/>
</bean>
<!--配置后置处理器-->
<bean id="postProcessor" class="com.yjzzjy4.learning.beans.PostProcessor"/>
</beans>
- 新建一个测试类
Test13
, 如下:
package com.yjzzjy4.learning.test;
import com.yjzzjy4.learning.beans.BeanLifeCycle;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test13 {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Spring-Conf-13.xml");
BeanLifeCycle blc = context.getBean("beanLifeCycle", BeanLifeCycle.class);
System.out.println("got bean instance");
System.out.println(blc);
// 关闭容器, 自动调用所有Bean的销毁方法;
((ClassPathXmlApplicationContext)context).close();
}
}
运行结果如下:
![](https://img-blog.csdnimg.cn/20210315174049516.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FBTWFob25l,size_16,color_FFFFFF,t_70)
注意事项:
- 实现了BeanPostProcessor的类可以当成后置处理器使用;
- 后置处理器是全局的, 将会在所有Bean实例化时都添加上, 前提是要先在配置文件中配置;
<bean>
标签的init-method
、destroy-method
属性可以配置初始化方法、销毁方法;- 在容器关闭时才会调用Bean的销毁方法, ApplicationContext没有close方法, 其实现类中有;
其他
补充一下注入一些特殊值的细节问题.
- 注入null值: 很简单, 直接在
<property>
中嵌套使用<null/>
即可, 或者干脆不指定值, 引用类型的属性默认值就是null, 使用<null/>
的例子如:
<property name="bookName">
<null/>
</property>
- 注入
XML
中语法不允许直接出现的特殊值, 如: <、>等, 有以下两种做法:
- 转义: <、>等, 比如:
<property name="bookName" value="<<我的大学>>"/>
- 使用CDATA, 比如:
<property name="bookName"> <value> <![CDATA[<<我的大学>>]]> </value> </property>
以上就是本人对Spring IoC的XML实现方式的一个学习总结, 难免出现疏漏和笔误的地方, 还请多多谅解, 更详细的技术原理、更新更全面的使用规范, 请自行查阅Spring官方文档、API和官网的学习版块.