Java – Spring – 基于XML管理bean
简介
Spring IoC的其中一种管理实现,Spring 通过配置xml管理bean的方式。
所谓IoC,指的是我们的实现类,并不通过我们自己进行实例化,而是通过xml配置的模式交由 Spring 进行管理,获取对象也直接由Spring提供。
实现思路
Spring 底层默认通过反射技术调用组件类的无参构造器来创建组件对象,这一点需要注意。如果在需要
无参构造器时,没有无参构造器,则会抛出下面的异常:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name
'helloworld' defined in class path resource [applicationContext.xml]: Instantiation of bean
failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed
to instantiate [com.atguigu.spring.bean.HelloWorld]: No default constructor found; nested
exception is java.lang.NoSuchMethodException: com.atguigu.spring.bean.HelloWorld.<init>()
实现环境配置
引入依赖
Spring依赖包相对比较多,主要引入spring-context包,系统会自动引入相关包的依赖项。
<dependencies>
<!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
创建类
创建一个实体类,这个实体类将交由Spring管理,当我们需要使用这个类时,直接在Spring中获取
public class HelloWorld {
public void sayHello(){
System.out.println("helloworld");
}
}
创建Spring的配置文件
在Spring的配置文件中配置bean
<?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">
<!--
配置HelloWorld所对应的bean,即将HelloWorld的对象交给Spring的IOC容器管理
通过bean标签配置IOC容器所管理的bean
属性:
id:设置bean的唯一标识
class:设置bean所对应类型的全类名
-->
<bean id="HelloWorldOne" class="cn.unsoft.mybatis.pojo.Emp"></bean>
</beans>
创建测试类测试
@Test
public void testHelloWorld(){
ApplicationContext ac = new
ClassPathXmlApplicationContext("applicationContext.xml");
HelloWorld helloworld = (HelloWorld) ac.getBean("helloworld");
helloworld.sayHello();
}
获取bean
通过上面的配置后,就可以把实体类交给Spring进行处理,而我们无需直接new实体类,通过Spring自动创建类对象,我们只要获取Spring创建的对象就可以了。
根据id获取
由于 id 属性指定了 bean 的唯一标识,所以根据 bean 标签的 id 属性可以精确获取到一个组件对象。
上个实验中我们使用的就是这种方式。
@Test
public void testHelloWorld(){
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
HelloWorld bean = ac.getBean("HelloWorldOne");
bean.sayHello();
}
根据类型获取
@Test
public void testHelloWorld(){
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
HelloWorld bean = ac.getBean(HelloWorld.class);
bean.sayHello();
}
根据id和类型
@Test
public void testHelloWorld(){
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
HelloWorld bean = ac.getBean("helloworld", HelloWorld.class);
bean.sayHello();
}
例外情况获取bean
多个相同bean的获取
当beans中包含了多个相同类型的 bean 时
<bean id="helloworldOne" class="cn.unsoft.spring.bean.HelloWorld"></bean>
<bean id="helloworldTwo" class="cn.unsoft.spring.bean.HelloWorld"></bean>
只能通过以下两种方式获取
@Test
public void testHelloWorld(){
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
HelloWorld bean = ac.getBean("helloworldOne", HelloWorld.class);
HelloWorld bean = ac.getBean("helloworldOne");
bean.sayHello();
}
使用以下方法获取会报异常
HelloWorld bean = ac.getBean("HelloWorld.class");
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean
of type 'com.atguigu.spring.bean.HelloWorld' available: expected single matching bean but
found 2: helloworldOne,helloworldTwo
使用接口获取bean
当这个实现类实现了某个接口时,我们也可以通过接口获取bean.但前提是,这个接口有且仅有这个实现类对接口实现了。
// 定义了一个接口
public interface UserInterFace { }
// 定义了一个实现实体类
public class User implements UserInterFace {}
// 可以通过获取接口来获得User实体类对象
@Test
public void testHelloWorld(){
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
UserInterFace bean = ac.getBean(UserInterFace.class);
sout(bean);
}
如果有两个以上的实体类实现了这个接口,那么则不可以通过接口获取实体类对象,因为bean不唯一。
根据类型来获取bean时,在满足bean唯一性的前提下,其实只是看:『对象 instanceof 指定的类型』的返回结果,只要返回的是true就可以认定为和类型匹配,能够获取到。
注入赋值
创建User实体类
public class User implements UserInterFace {
public User() {
}
public User(int id, String username, String password, int age, String email) {
this.id = id;
this.username = username;
this.password = password;
this.age = age;
this.email = email;
}
private int id;
private String username;
private String password;
private int age;
private String email;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", age=" + age +
", email='" + email + '\'' +
'}';
}
}
使用 property 赋值
通过直接赋值属性来注入数据到实体类。
<bean id="User" class="cn.unsoft.mybatis.pojo.User">
<!-- property标签:通过组件类的setXxx()方法给组件对象设置属性 -->
<!-- name属性:指定属性名(这个属性名是getXxx()、setXxx()方法定义的,和成员变量无关)
-->
<!-- value属性:指定属性值 -->
<property name="username" value="张三"></property>
<property name="password" value="123456"></property>
</bean>
注意:这里的 name 属性,与成员变量无关,与getXXX和setXXX有关
使用 构造器(constructor-arg) 赋值
<bean id="User" class="cn.unsoft.mybatis.pojo.User">
<constructor-arg name="username" value="张三" type="java.lang.String"></constructor-arg>
<constructor-arg name="password" value="123456" type="java.lang.Integer"></constructor-arg>
</bean>
注意:使用 constructor-arg
构造器赋值和实体类的构造方法有关,如果构造器中出现类型冲突时(如参数个数一样,且类型一样),可以通过增加 name 或 type 属性指定对应构造器。
特殊值赋值
null值
对于 property 标签中的 value 属性只允许对字面量进行赋值,如果是null值,value="null"
只会把【null】值当作文本形处理
<bean id="HelloWorldOne" class="cn.unsoft.mybatis.pojo.User">
<property name="username" value="null"></property>
</bean>
username = 'null' 而非 null
可在标签插槽中定义null值
<property name="username">
<null />
</property>
特殊符号值
在 xml 规范中,【<】和【>】号在xml中是有特殊意义的,它代表了一个标签的开头与结尾,所以它在任何时候,都不能作为字面量表达。
要表达【<】和【>】号只能使用它们的转义表达符【<】和【>】表示
<bean id="HelloWorldOne" class="cn.unsoft.mybatis.pojo.User">
<property name="username" value="a<>b"></property>
</bean>
username = 'a<>b'
value标签
除了可以在属性中定义 value
属性外,也可以在插槽中使用 value 标签进行赋值
<bean id="HelloWorldOne" class="cn.unsoft.mybatis.pojo.User">
<property name="username">
<value>张三</value>
</property>
</bean>
使用CDATA区赋值
针对部分特殊字符与xml规范发生冲突的影响,xml提供一种原生输出的特殊标签,包裹在它里面的任何字符都会原生输出,以字面量形式输出。
保CDATA区不可用在属性值中,只能用在标签插槽中。
<bean id="HelloWorldOne" class="cn.unsoft.mybatis.pojo.User">
<property name="username">
<value><![CDATA[ a<>b ]]></value>
</property>
</bean>
对类类型进行赋值(ref属性)
在多对一或一对多等情况下,实体类会包含其它实体类的属性(或成员变量),value 属性在实体类数据前不可用。
perperty 标签除了使用 value 属性值可对实体类中的字面量进行赋值外,还支持使用引用类型的赋值 ref
标签。
<bean id="HelloWorldOne" class="cn.unsoft.mybatis.pojo.User">
<property name="clazz" ref="ClazzBean"></property>
</bean>
// 定义一个引用类型的 bean 其它 bean 可以通过 ref 属性引用
<bean id="ClazzBean" class="cn.unsoft.mybatis.pojo.Clazz">
<property name="clazzID" value="1111"></property>
<property name="clazzName" value="最强班"></property>
</bean>
也可以在插槽中定义ref
<bean id="HelloWorldOne" class="cn.unsoft.mybatis.pojo.User">
<property name="clazz">
<ref bean="ClazzBean"></ref>
</property>
</bean>
// 定义一个引用类型的 bean 其它 bean 可以通过 ref 属性引用
<bean id="ClazzBean" class="cn.unsoft.mybatis.pojo.Clazz">
<property name="clazzID" value="1111"></property>
<property name="clazzName" value="最强班"></property>
</bean>
对类类型进行赋值(内部bean方式)
Spring 的 IoC 支持通过级联的方式对类类型数据进行赋值,但需要预先对类类型的数据进行实例化,转化到xml中就是需要预先给类类型进行bean的定义,在Spring中提供内部bean功能,即在 property
中包含bean标签。
内bean只允许在bean内部使用,不能通过getBean
获取其实例化。
<bean id="HelloWorldOne" class="cn.unsoft.mybatis.pojo.User">
<property name="clazz">
<bean id="clazzInner" class="cn.unsoft.mybatis.pojo.Clazz">
<property name="clazzID" value="1111"></property>
<property name="clazzName" value="远航班"></property>
</bean>
</property>
</bean>
对数组类型进行赋值
Spring 的 bean 约束中提供了 array
标签,使用 array
标签对数组进行赋值
// 对数组中的数据进行赋值
<bean id="HelloWorldOne" class="cn.unsoft.mybatis.pojo.User">
<property name="hobby">
<array>
// 如果数组值为普通字面量数据时使用 value 赋值
<value>字面量数据</value>
// 如果数组成员为对象实例时,可以使用 ref 赋值
<ref bean="hobbyBean"></ref>
</array>
</property>
</bean>
对List集合进行赋值使用 list 标签
// 对List集合中的数据进行赋值
<bean id="HelloWorldOne" class="cn.unsoft.mybatis.pojo.User">
<property name="hobby">
<list>
// 如果数组值为普通字面量数据时使用 value 赋值
<value>字面量数据</value>
// 如果数组成员为对象实例时,可以使用 ref 赋值
<ref bean="hobbyBean"></ref>
</list>
</property>
</bean>
使用 util
约束对List集合进行赋值,使用 util:list
标签
// 对List集合中的数据进行赋值
<bean id="HelloWorldOne" class="cn.unsoft.mybatis.pojo.User">
<property name="hobby">
<util:list>
// 如果数组值为普通字面量数据时使用 value 赋值
<value>字面量数据</value>
// 如果数组成员为对象实例时,可以使用 ref 赋值
<ref bean="hobbyBean"></ref>
</util:list>
</property>
</bean>
注意:util
约束不在 bean 约束中,所以要使用 util
约束中的标签需要引入约束包
xmlns:util="http://www.springframework.org/schema/util"
<bean id="HelloWorldOne" class="cn.unsoft.mybatis.pojo.User">
<property name="hobby">
<ref bean="hobbyList"></ref>
</property>
</bean>
<util:list id="hobbyList">
<ref bean="hobby"></ref>
<ref bean="hobby"></ref>
<ref bean="hobby"></ref>
</util:list>
对Map类型进行赋值
Spring 的 bean 约束中提供了 map
标签,使用 map
标签对数组进行赋值
<bean id="HelloWorldOne" class="cn.unsoft.mybatis.pojo.User">
<property name="hobby">
<map>
<entry key="字面量key值" value="字面量value值"></entry>
<entry key-ref="keyRef" value-ref="valueRef"></entry>
</map>
</property>
</bean>
使用 util
约束对Map集合进行赋值,使用 util:map
标签
<bean id="HelloWorldOne" class="cn.unsoft.mybatis.pojo.User">
<property name="hobby" ref="hobbyMap"></property>
</bean>
<util:map id="hobbyMap">
<entry key="字面量key值" value="字面量value值"></entry>
<entry key-ref="keyRef" value-ref="valueRef"></entry>
</util:map>
注意:util
约束不在 bean 约束中,所以要使用 util
约束中的标签需要引入约束包
使用 p 约束进行赋值
通过引入【p】约束,对各个属性进行赋值
<bean id="HelloWorldOne" class="cn.unsoft.mybatis.pojo.User"
p:username="李四"
p:age="20"
p:password="123456"
p:clazz-ref="ClazzRef"
></bean>
注意:p
约束不在 bean 约束中,所以要使用 p
约束中的标签需要引入约束包
xmlns:p="http://www.springframework.org/schema/p"
作用域Scope
作用域是指实体类在Spring中的实例化模式,默认实体类在Spring中为singleton(单例模式)
可以在bean中设置scope属性设置其实例是否为多例。
// 设置为多例
<bean id="UserBean" class="cn.unsoft.mybatis.pojo.User" scope="prototype"></bean>
singleton
: 单例模式
prototype
: 多例模式
bean的生命周期
对于由Spring所管理的实体类,具有执行的生命周期:
1.bean对象创建(调用无参构造器)
public User() {
System.out.println("生命周期1:调用无参构造方法");
}
2.给bean对象设置属性
public int getId() {
System.out.println("生命周期2:给对象设置属性");
return id;
}
3.bean对象初始化之前操作(由bean的后置处理器负责)
初始化前的操作由Spring提供的接口BeanPostProcessor
中的方法 postProcessBeforeInitialization
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("生命周期3:后置处理器postProcessBeforeInitialization");
return bean;
}
4.bean对象初始化(需在配置bean时指定初始化方法)
在bean配置中可以设定指定实体类中某一方法作为Spring对bean初始化时的方法
<bean id="UserBean" class="cn.unsoft.mybatis.pojo.User" init-method="initMethod"></bean>
通过实现 InitializingBean接口方法实现属性装配后执行方法
public class UserServiceImpl implements UserService, InitializingBean {
@Override
public void afterPropertiesSet() throws Exception{
...
}
}
5.bean对象初始化之后操作(由bean的后置处理器负责)
初始化前的操作由Spring提供的接口BeanPostProcessor
中的方法postProcessAfterInitialization
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("生命周期3:后置处理器postProcessAfterInitialization");
return bean;
}
6.bean对象就绪可以使用
在这个生命周期中,对象将被创建完毕,可由getBean获取对象实例
7.bean对象销毁(需在配置bean时指定销毁方法)
在bean配置中可以设定指定实体类中某一方法作为Spring对bean销毁时的方法
<bean id="UserBean" class="cn.unsoft.mybatis.pojo.User" destroy-method="destoryMethod"></bean>
8.IOC容器关闭
ioc.close();
当ioc容器关闭时,会调用【7.bean对象销毁】的方法,并关闭ioc容器。
注意
生命周期在单例模式中,在ioc创建完成时,即会进行,无需到达getBean时才进行。
如果实体类为多例模式时,在ioc创建完成时,生命周期不会被执行,而是在getBean时才执行。
FactoryBean 工厂Bean
FactoryBean是Spring提供的一种整合第三方框架的常用机制。和普通的bean不同,配置一个FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是getObject()方法的返回值。通过这种机制,Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来,只把最简洁的使用界面展示给我们。
将来我们整合Mybatis时,Spring就是通过FactoryBean机制来帮我们创建SqlSessionFactory对象的。
在Spring中的bean配置中,通常我们在xml中配置bean的属性数,同时也可以使用FactoryBean工厂Bean代为创建bean对象,通过实现接口的方式代替bean的配置,从而省略我们在xml中配置bean的配置操作。
FactoryBean 实现Spring的一个 FactoryBean<T> 接口,提供三个方法
public class UserFactoryBean implements FactoryBean<User> {}
【getObject】方法:当Spring需要通过FactoryBean取到实体类对象时,会调用此方法取得实例对象
public User getObject() throws Exception {
return new User();
}
【getObjectType】方法:当Spring需要获取这个实体类的类型时,会调用此方法取得实体类的类型
@Override
public Class<?> getObjectType() {
return User.class;
}
【isSingleton】方法:返回这个实体类类型的模式是单例模式还是多例模式
@Override
public boolean isSingleton() {
return FactoryBean.super.isSingleton();
}
静态 FactoryBean
静态 FactoryBean 指的是创建一个类,创建一个静态方法,该静态方法用于返回Bean对象
静态 FactoryBean 的作用是,可以在创建Bean对象之前,可以做一些自定义操作,如对Bean对象的属性设置等
public class MyBeanFactory{
public static Bean getBean(){
...... 初始化操作
return new Bean();
}
}
在XML中,可以定义Bean
<bean id="Bean" class="cn.unsoft.beanfactory.MyBeanFactory" factory-method="getBean"></bean>
案例:Spring 获取非自定义Bean(1)
案例一:通过Spring 获取 SimpleDataFormat 对象,通过 Spring 管理对象的方式,对日期进行解析
// 创建 SimpleDateFormat 对象
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = simpleDateFormat.parse("2023-02-21 12:00:00");
分析:
1.通过创建 SimpleDateFormat 对象,需要先 new 一个 SimpleDateFormat 对象,在Spring管理中,就可以使用 <bean> 来让 Spring 自动创建对象
2.SimpleDateFormat 对象调用对象方法 parse ,在Spring中,可以使用【factory-bean】来使用一个已存在的bean中的方法。
<!-- 利用 Spring 先创建一个 SimpleDateFormat 对象实例 -->
<bean id="simpleDateFormat" class="java.text.SimpleDateFormat">
<constructor-arg name="pattern" value="yyyy-MM-dd HH:mm:ss"></constructor-arg>
</bean>
<!-- 使用 factory-bean 来获取在 Spring 中已有的实例对象,并通过使用 factory-method 定义调用的方法 -->
<bean id="date" factory-bean="simpleDateFormat" factory-method="parse">
<constructor-arg name="source" value="2023-02-21 12:00:00"></constructor-arg>
</bean>
通过调用 getBean 就能实现执行 Spring 中管理的Bean对应的方法。
// 利用 XML 和 Spring 自动创建 SimpleDateFormat 实例对象
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
Object data = ac.getBean("date");
System.out.println(data);
案例:Spring 获取非自定义Bean(2)
案例一:通过Spring 获取 Connection 对象,来获得 JDBC 连接池
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql:///database", "root", "root");
通过以上的代码,我们分析如下:
1.要使用DriverManager之前,需要调用 Class.forName 方法,该方法,在Spring 中可以定义为一个静态工厂方法
2.DriverManager.getConnection 实际上也是一个静态工厂方法,因此可以使用XML配置如下
<bean id="clazz" class="java.lang.Class" factory-method="forName">
<constructor-arg name="className" value="com.mysql.cj.jdbc.Driver"></constructor-arg>
</bean>
<bean id="connection" class="java.sql.DriverManager" factory-method="getConnection">
<constructor-arg name="url" value="jdbc:mysql:///database"></constructor-arg>
<constructor-arg name="user" value="root"></constructor-arg>
<constructor-arg name="password" value="root"></constructor-arg>
</bean>
导入properties文件
Spring 除了可以管理我们自己的实体类外,还可以管理其它第三方类库,比如DataSource
的Durid
,或Mybatis
等。
但第三方类库可能需要从外部导入数据,Spring 支持导入properties文件,使用【${}】语法取得数据
使用【context】
约束【property-placeholder】
标签导入properties文件,就可以使用 properties 文件中的数据了
// 导入 jdbc 文件
<context:property-placeholder location="jdbc.properties"></context:property-placeholder>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.Driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
测试用例自动创建IOC
我们在测试中,经常需要在测试用例代码中,创建如下代码来获取Bean对象
ApplicationContext ioc = new ClassPathXmlApplicationContext("ioc.xml");
ioc.getBean(Bean类型)
我们可以在测试用例中,通过 @ContextConfiguration
注解省略上面的代码,让测试用例自动帮我们创建ioc
// Spring 配合 JUnit 输出日志文件
@RunWith(SpringJUnit4ClassRunner.class)
// 通过 @ContextConfiguration 注解导入 IOC Bean 文件
// 注意需要在前面加 classpath 用于表示是项目内文件路径
@ContextConfiguration("classpath:ioc.xml")
public class JdbcTemplateTest {
// 在成员变量中设定要取出的Bean对象,我们就可以在其它地方使用这个Bean类了
// 前提是该类型已经在 xml 中配置了 Bean 类,或配置了自动装配
@Autowired
private JdbcTemplate jdbcTemplate;
}
<!-- 导入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties" />
<!-- 配置数据源 -->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 配置 JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 装配数据源 -->
<property name="dataSource" ref="druidDataSource"/>
</bean>
Spring XML 解析 Bean 原理
Spring容器在进行初始化时,会将xml配置的<bean>的信息封装成一个BeanDefinition对象,所有的 BeanDefinition 存储到一个名为 beanDefinitionMap 的 Map 集合中去,Spring框架在对该Map进行遍历,使用反射创建Bean实例对象,创建好的Bean对象存储在一个名为singletonObjects的Map集合中,当调用getBean方法时则最终从该Map集合中取出Bean实例对象返回。
BeanDefinition 对象由 DefaultListableBeanFactory 类进行管理,其中在XML中解析的Bean 成为BeanDefinition后都将存储在 DefaultListableBeanFactory 中
Bean 实例化的基本流程
加载xml配置文件,解析获取配置中的每个<bean>的信息,封装成一个个的BeanDefinition对象;
将BeanDefinition存储在一个名为beanDefinitionMap的Map<String,BeanDefinition>中;
ApplicationContext底层遍历beanDefinitionMap,创建Bean实例对象;
创建好的Bean实例对象,被存储到一个名为singletonObjects的Map<String,Object>中;
当执行applicationContext.getBean(beanName)时,从singletonObjects去匹配Bean实例返回。
Bean 后处理器
Spring的后处理器是Spring对外开发的重要扩展点,允许我们介入到Bean的整个实例化流程中来,以达到动态注册
BeanDefinition,动态修改BeanDefinition,以及动态修改Bean的作用。Spring主要有两种后处理器:
BeanFactoryPostProcessor
Bean工厂后处理器,在BeanDefinitionMap填充完毕,Bean实例化之前执行。
我们知道,Spring 会在XML 中解析出所有 <bean> 标签,并预先存放到一个叫 BeanDefinitionMap 的地方,BeanDefinitionMap 保存的则是【未实例化,但准备实例化】的Bean
而当Bean被实例化后,这些Bean会以BeanDefinition对象的形式转存到singletonObjects 中保存。
BeanFactoryPostProcessor 将会在 Bean 进行实例化之前执行,意味着,在BeanFactoryPostProcessor 实现阶段,所有的 Bean 都还没有被实例化,此时,我们可以通过实现方法postProcessBeanFactory 中提交的beanFactory 来自定义BeanDefinition插入BeanDefinitionMap 中。
注意:beanFactory 类型是 ConfigurableListableBeanFactory 类,实际上是DefaultListableBeanFactory 的子类,ConfigurableListableBeanFactory 类中没有 registerBeanDefinition 插入(注册)BeanDefinition方法,因此可以通过强转实现。
BeanDefinitionRegistryPostProcessor
为了方便使用registerBeanDefinition方法,BeanFactoryPostProcessor 有一个子接口BeanDefinitionRegistryPostProcessor,可以使用该接口中的postProcessBeanDefinitionRegistry 方法,直接调用registerBeanDefinition插入BeanDefinition。
关于BeanDefinitionRegistryPostProcessor和 BeanFactoryPostProcessor 的执行先后顺序
BeanDefinitionRegistryPostProcessor. postProcessBeanDefinitionRegistry 先执行
BeanDefinitionRegistryPostProcessor.postProcessBeanFactory 其次
BeanFactoryPostProcessor. postProcessBeanFactory 最后执行。
如果遇到多个实现BeanDefinitionRegistryPostProcessor 接口的,那么执行顺序如下:
MyBeanDefinitionRegistryPostProcessor1 -> postProcessBeanDefinitionRegistry
MyBeanDefinitionRegistryPostProcessor2 -> postProcessBeanDefinitionRegistry
MyBeanDefinitionRegistryPostProcessor1 -> postProcessBeanFactory
MyBeanDefinitionRegistryPostProcessor2 -> postProcessBeanFactory
MyBeanFactoryPostProcessor1 -> postProcessBeanFactory
MyBeanFactoryPostProcessor2 -> postProcessBeanFactory
BeanPostProcessor
BeanPostProcessor 是在Bean实例化完成之后,即将把实例化对象存储到 singletonObjects 之前的操作,属于最后的Bean处理操作,也是最后执行的操作。
在处理BeanPostProcessor接口方法之前,Bean会先被实例化,
postProcessBeforeInitialization 方法为 实例化 Bean 对象存入singletonObjects 之前执行的方法
postProcessAfterInitialization 方法为 实例化 Bean 对象存入singletonObjects 之后执行的方法,如果方法返回 null.则singletonObjects 中将不存入Bean对象
Spring Bean 生命周期
Bean 大致生命周期图
Bean的实例化阶段:
Spring框架会取出BeanDefinition的信息进行判断当前Bean的范围是否是singleton的,是否不是延迟加载的,是否不是FactoryBean等,最终将一个普通的singleton的Bean通过反射进行实例化;
Bean的初始化阶段:
Bean创建之后还仅仅是个"半成品",还需要对Bean实例的属性进行填充、执行一些Aware接口方法、执行BeanPostProcessor方法、执行InitializingBean接口的初始化方法、执行自定义初始化init方法等。该阶段是Spring最具技术含量和复杂度的阶段,Aop增强功能,后面要学习的Spring的注解功能等、 spring高频面试题Bean的循环引用问题都是在这个阶段体现的;
Bean实例属性填充:
普通属性注入
注入普通属性,String、int或存储基本类型的集合时,直接通过set方法的反射设置进去;
单向对象注入(A对象的成员中有B对象)
注入单向对象引用属性时,从容器中getBean获取后通过set方法反射设置进去,如果容器中没有,则先创建被注入对象Bean实例(完成整个生命周期)后,在进行注入操作;
双向对象注入(A对象中有B,B对象中有A)
注入双向对象引用属性时,就比较复杂了,涉及了循环引用(循环依赖)问题(重点)
双向注入流程图
其中这三个缓存的定义如下
当存在UserService和UserDao时,UserService中需要注入UserDao,UserDao中需要注入UserSrevice时,它们的执行流程如下
Bean的完成阶段:
经过初始化阶段,Bean就成为了一个完整的Spring Bean,被存储到单例池singletonObjects中去了,即完成了Spring Bean的整个生命周期。
Aware 发现接口
Aware 接口是用于在Bean中获得上层内容的接口。
例如:我们使用 ClassPathXmlApplicationContext 创建Bean容器 -> 代码如下
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
容器对象【applicationContext】在 Main 方法中被调,而 applicationContext.xml 中配置了多个Bean.
在applicationContext对象创建时,applicationContext.xml 中多个Bean 也同时被 Spring 实例化并管理。
问题:我如何在 Spring Bean 中获取到 applicationContext 容器对象?
解决方法:实现Aware 接口
Aware接口 | 回调方法 | 作用 |
ServletContextAware | setServletContext(ServletContext context) | Spring框架回调方法注入ServletContext对象,web环境下才生效 |
BeanFactoryAware | setBeanFactory(BeanFactory factory) | Spring框架回调方法注入beanFactory对象 |
BeanNameAware | setBeanName(String beanName) | Spring框架回调方法注入当前Bean在容器中的beanName |
ApplicationContextAware | setApplicationContext(ApplicationContext applicationContext) | Spring框架回调方法注入applicationContext对象 |
以下是 UserService 实现 ApplicationContextAware 接口,在UserService 中获取到 ApplicationContext 容器的方法:
public class UserServiceImpl implements UserService, ApplicationContextAware {
/**
* 在 UserService Bean 中获取到 ApplicationContext 对象,并对其进行操作
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// 使用 applicationContext
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String name : beanDefinitionNames) {
System.out.println(name);
}
}
}
共有 0 条评论