Spring Bean的生命周期:
一、通过XML、Java annotation(注解)以及Java Configuration(配置类),等方式加载Spring Bean
二、BeanDefinitionReader:解析Bean的定义。在Spring容器启动过程中,会将Bean解析成Spring内部的BeanDefinition结构;理解为:将spring.xml中的<bean>标签转换成BeanDefinition结构(有点类似于XML解析)
三、BeanDefinition:包含了很多属性和方法。例如:id、class(类名)、scope、ref(依赖的bean)等等。其实就是将bean(例如<bean>)的定义信息
存储到这个对应BeanDefinition相应的属性中
四、BeanFactoryPostProcessor:是Spring容器功能的扩展接口。
注意:
1)BeanFactoryPostProcessor在spring容器加载完BeanDefinition之后,
在bean实例化之前执行的
2)对bean元数据(BeanDefinition)进行加工处理,也就是BeanDefinition
属性填充、修改等操作
五、BeanFactory:bean工厂。它按照我们的要求生产我们需要的各种各样的bean。
六、Aware感知接口:在实际开发中,经常需要用到Spring容器本身的功能资源
例如:BeanNameAware、ApplicationContextAware等等
BeanDefinition 实现了 BeanNameAware、ApplicationContextAware
七、BeanPostProcessor:后置处理器。在Bean对象实例化和引入注入完毕后,
在显示调用初始化方法的前后添加自定义的逻辑。(类似于AOP的绕环通知)
前提条件:如果检测到Bean对象实现了BeanPostProcessor后置处理器才会执行
Before和After方法
BeanPostProcessor
1)Before
2)调用初始化Bean(InitializingBean和init-method,Bean的初始化才算完成)
3)After
完成了Bean的创建工作
八、destory:销毁
1.Bean的初始化过程
1.xml/annotation/configuation/配置JavaBean
2.BeanDefinitionReader解析配置的JavaBean得到Beandefinition,最终得到List<BeanDefinition>集合
3.触发BeanFactoryPostProcessor,在JavaBean初始化之前执行
4.Spring中的BeanFactory,会通过List<BeanDefinition>集合遍历初始化所有的JavaBean对象
5.如果自己的JavaBean需要调动Spring上下文中的资源(方法或属性),那么需要实现*Aware感知接口
6.如果自己的JavaBean已经初始化好了,还需扩展功能,那么需要借助BeanPostProcessor后置处理器来实现
具体图解与流程图如下:
1.1代码详解
BeanFactoryPostProcessor是Spring容器功能的扩展接口,怎么理解?比如假设我们现在有一个内部类Person,其原本有三个属性。BeanFactoryPostProcessor的作用就是可以在初始化对象之前可以做一个补充,它内部类原本只有三个属性,我们可以给它加一个关联属性User进去,并对新增的关联属性User对象做出相对应的处理,具体代码详解如下:
package com.kissship.beanlife; /** * @author Kissship * @site www.Kissship.com * @company xxx公司 * @create 2023-08-18-14:17 */ public class Demo1 { public static void main(String[] args) { // BeanDefinition } } class User{ //无值 } class Person{ private int pid; private String name; private String sex; private User user;//扩展新增的关联属性(无值) public int getPid() { return pid; } public void setPid(int pid) { this.pid = pid; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public Person() { } //在初始化对象之前可以做一个补充,比如上面原本只有三个属性,我们也可以给它加一个关联属性User public Person(int pid, String name, String sex) { this.init();//扩展的内容 this.pid = pid; this.name = name; this.sex = sex; } //这里可以针对我们新增的关联属性User对象作出处理 public void init() { } }
解析配置得到List集合与遍历初始化Bean对象,具体代码详解如下:
import org.springframework.beans.factory.config.BeanDefinition; import java.util.ArrayList; import java.util.List; /** * @author Kissship * @site www.Kissship.com * @company xxx公司 * @create 2023-08-18-14:17 */ public class Demo1 { //初始化Bean public static void main(String[] args) throws ClassNotFoundException { // 2.BeanDefinitionReader解析配置的JavaBean得到Beandefinition,最终得到List<BeanDefinition>集合 List<BeanDefinition> beans = new ArrayList<>();//BeanDefinitionReader把spring-context.xml中所有的bean标签都解析成了List集合 for (BeanDefinition bean : beans) { // 4.Spring中的BeanFactory,会通过List<BeanDefinition>集合遍历初始化所有的JavaBean对象 String beanClassName = bean.getBeanClassName();//得到的就是spring-context.xml中bean标签中class里的值 Class.forName(beanClassName);//将配置的所有JavaBean进行反射实例化 } } }
如果自己的JavaBean需要调动Spring上下文中的资源(方法或属性),那么需要实现*Aware感知接口(比如BeanDefinition实现了BeanNameAware、ApplicationContextAware接口)具体详解如下:
Aware接口:
实现Aware接口:
1.2思考
那么现在有个问题,当我们的Spring容器初始化Bean完成之后,我们可能还需要加工一下,即进行再初始化。因为并不是每一个类都是我们自己写的。也许我们需要用别人的类内容,然后在此基础上再进行拓展。那么在此时我们能改动别人写的类里面的源码吗?不能。因为这样有可能影响到其他的功能,这时候我们的解决办法就是自己写一个类去继承别人的类然后进行再一波初始化,我们这里以BaseDao为例:
BaseDao为父类,MyBaseDao为子类:
我们需要在继承BaseDao方法后,在子类中进行初始化方法的重写(即再一次初始化),具体如下:
package com.kissship.beanlife; /** * @author Kissship * @site www.Kissship.com * @company xxx公司 * @create 2023-08-18-15:58 */ //继承原本的BaseDao public class MyBaseDao extends BaseDao{ public MyBaseDao(){ //重写初始化方法 super(); System.out.println("初始化..."); } }
那么在此时如果我们在初始化之后不满意,在初始化之后想修改Bean的内容,可以利用BeanPostProcessor后置处理器来完成。这时候可以在Bean对象实例化和引入注入完毕后,在显示调用初始化方法的前后添加自定义逻辑。(类似于AOP的环绕通知)环绕通知不了解的可以通过下面链接进行跳转至对应博客界面,如下:
那么到这里我们就完成了Bean的创建工作。
2.Bean的单例与多例选择
1.在Spring中JavaBean默认是单例的,但是可以配置多例。
2.单例的优点:节约内存。弊端:有变量污染。
多例的优点:无变量污染。弊端:极其消耗内存。
3.单例:JavaBean是跟着spring上下文初始化的;容器生对象生,容器死对象死(此处对象指JavaBean)。
多例:JavaBean在使用时才会创建,销毁跟着jvm走。
4.第三点并不绝对正确,个别情况会有所个例。
2.1论证单例与多例优缺点
准备工作:
ParamAction:
package com.kissship.beanlife; import java.util.List; public class ParamAction { private int age; private String name; private List<String> hobby; private int num = 1; // private UserBiz userBiz = new UserBizImpl1(); public ParamAction() { super(); } public ParamAction(int age, String name, List<String> hobby) { super(); this.age = age; this.name = name; this.hobby = hobby; } public void execute() { // userBiz.upload(); // userBiz = new UserBizImpl2(); System.out.println("this.num=" + this.num++); System.out.println(this.name); System.out.println(this.age); System.out.println(this.hobby); } }
InstanceFactory(Bean工厂):
package com.kissship.beanlife; public class InstanceFactory { public void init() { System.out.println("初始化方法"); } public void destroy() { System.out.println("销毁方法"); } public void service() { System.out.println("业务方法"); } }
spring-context.xml进行配置(生命周期部分):
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" default-autowire="byType" 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"> <!-- ioc的javabean--> <!-- 凡是在Spring配置文件spring-context.xml中配置,那么该类javabean就交给了Spring容器管理--> <bean class="com.kissship.ioc.web.UserAction" id="userAction"> <property name="userService" ref="userService"></property> <!-- <constructor-arg name="uname" value="扎克" ></constructor-arg>--> <!-- <constructor-arg name="age" value="18" ></constructor-arg>--> <!-- <constructor-arg name="hobby">--> <!-- <list>--> <!-- <value>唱,跳</value>--> <!-- <value>Rap</value>--> <!-- <value>篮球</value>--> <!-- </list>--> <!-- </constructor-arg>--> </bean> <bean class="com.kissship.ioc.web.GoodsAction" id="goodsAction"> <property name="userService" ref="userServiceImpl1"></property> <!-- <property name="gname" value="小文"></property>--> <!-- <property name="age" value="19"></property>--> <!-- <property name="peoples">--> <!-- <list>--> <!-- <value>印度飞饼</value>--> <!-- <value>意大利炮</value>--> <!-- <value>北京烤鸭</value>--> <!-- <value>墨西哥卷</value>--> <!-- </list>--> <!-- </property>--> </bean> <bean class="com.kissship.ioc.service.impl.UserServiceImpl2" id="userService"></bean> <bean class="com.kissship.ioc.service.impl.UserServiceImpl1" id="userServiceImpl1"></bean> <!-- aop相关的Javabean--> <!--目标对象--> <bean class="com.kissship.aop.biz.impl.BookBizImpl" id="bookBiz"></bean> <!--通知--> <bean class="com.kissship.aop.advice.MyMethodBeforeAdvice" id="methodBeforeAdvice"></bean> <bean class="com.kissship.aop.advice.MyAfterReturningAdvice" id="myAfterReturningAdvice"></bean> <bean class="com.kissship.aop.advice.MyMethodInterceptor" id="myMethodInterceptor"></bean> <bean class="com.kissship.aop.advice.MyThrowsAdvice" id="myThrowsAdvice"></bean> <bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" id="regexpMethodPointcutAdvisor"> <property name="advice" ref="myAfterReturningAdvice"></property> <property name="pattern" value=".*buy"></property> </bean> <!--代理--> <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy"> <!-- 配置目标对象--> <property name="target" ref="bookBiz"></property> <!-- 配置代理接口,目标对象的接口--> <property name="proxyInterfaces"> <list> <value>com.kissship.aop.biz.IBookBiz</value> </list> </property> <!-- 配置通知--> <property name="interceptorNames"> <list> <value>methodBeforeAdvice</value> <!-- <value>myAfterReturningAdvice</value>--> <value>regexpMethodPointcutAdvisor</value> <value>myMethodInterceptor</value> <value>myThrowsAdvice</value> </list> </property> </bean> <!-- Spring的生命周期--> <bean class="com.kissship.beanlife.ParamAction" id="paramAction"> <constructor-arg name="name" value="刘三金"></constructor-arg> <constructor-arg name="age" value="19"></constructor-arg> <constructor-arg name="hobby"> <list> <value>来自星星的哥</value> <value>天空一声巨响</value> <value>劳资闪亮登场</value> </list> </constructor-arg> </bean> <bean class="com.kissship.beanlife.InstanceFactory" id="instanceFactory" scope="prototype" init-method="init" destroy-method="destroy"></bean> </beans>
测试类Demo2:
package com.kissship.beanlife; import org.junit.Test; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; /* * spring bean的生命週期 * spring bean的單例多例 */ public class Demo2 { // 体现单例与多例的区别 @Test public void test1() { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("/spring-context.xml"); // ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/spring-context.xml"); ParamAction p1 = (ParamAction) applicationContext.getBean("paramAction"); ParamAction p2 = (ParamAction) applicationContext.getBean("paramAction"); // System.out.println(p1==p2); p1.execute(); p2.execute(); // 单例时,容器销毁instanceFactory对象也销毁;多例时,容器销毁对象不一定销毁; applicationContext.close(); } // 体现单例与多例的初始化的时间点 instanceFactory @Test public void test2() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/spring-context.xml"); } // BeanFactory会初始化bean对象,但会根据不同的实现子类采取不同的初始化方式 // 默认情况下bean的初始化,单例模式立马会执行,但是此时XmlBeanFactory作为子类,单例模式下容器创建,bean依赖没有初始化,只有要获取使用bean对象才进行初始化 @Test public void test3() { // ClassPathXmlApplicationContext applicationContext = new // ClassPathXmlApplicationContext("/spring-context.xml"); Resource resource = new ClassPathResource("/spring-context.xml"); BeanFactory beanFactory = new XmlBeanFactory(resource); // InstanceFactory i1 = (InstanceFactory) beanFactory.getBean("instanceFactory"); } }
执行Demo2结果如下:
那么我们换上多例后会是什么结果?
具体实操,在spring-context.xml中ParamAtion的Bean配置的class属性里加上scope属性并赋值多例(原型模式) ,如下:
修改后我们继续执行Demo2测试类看看效果:
2.2论证初始化时间点
Demo2里已经写了体现单例与多例的初始化时间点的方法,所以我们只需要在spring-context.xml中给它定一个多例的属性值然后测试看效果即可,xml代码如下:
<bean class="com.kissship.beanlife.InstanceFactory" id="instanceFactory" scope="prototype" init-method="init" destroy-method="destroy"></bean>
然后我们运行Demo2看看效果,如下:
紧接着我们换成单例,如下:
<bean class="com.kissship.beanlife.InstanceFactory" id="instanceFactory" scope="singleton" init-method="init" destroy-method="destroy"></bean>
执行方法看效果,如下:
那么现在问题又来了,虽然验证了单例与多例的初始化时间点,但是看不出到底是不是使用Spring创建的,那么现在我们上代码验证看看结果。刚刚我们在演示时多例并没有执行初始化方法,现在我们把xml文件里的单例改成多例,然后再Demo2测试类中加上以下代码:
然后再运行一次看效果:
2.3个例演示
BeanFactory会初始化bean对象,但会根据不同的实现子类采取不同的初始化方式。
默认情况下bean的初始化,单例模式会立马执行,但是此时XmlBeanFactory作为子类,单例模式下容器创建,bean依赖没有初始化·,只有要获取使用bean对象才进行初始化。
验证:
1.先把xml多例模式改为单例,然后运行Test2方法,再运行Test3方法,比较结果。
2.然后把Test3注释的代码部分放开再执行比较结果。
运行Test2的结果(先注释论证是否为Sprring创建新增的代码再运行):
运行Test3的结果:
然后取消注释,如下:
再执行一次Test3,效果如下:
最后Spring之bean的生命周期就到这里,祝大家在敲代码的路上一路通畅!