Spring之bean的生命周期

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: Spring之bean的生命周期

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的生命周期就到这里,祝大家在敲代码的路上一路通畅!

感谢大家的观看 !


目录
相关文章
|
18天前
|
XML 安全 Java
|
2天前
|
XML Java 数据格式
🌱 深入Spring的心脏:Bean配置的艺术与实践 🌟
本文深入探讨了Spring框架中Bean配置的奥秘,从基本概念到XML配置文件的使用,再到静态工厂方式实例化Bean的详细步骤,通过实际代码示例帮助读者更好地理解和应用Spring的Bean配置。希望对你的Spring开发之旅有所助益。
26 3
|
1月前
|
缓存 Java Spring
实战指南:四种调整 Spring Bean 初始化顺序的方案
本文探讨了如何调整 Spring Boot 中 Bean 的初始化顺序,以满足业务需求。文章通过四种方案进行了详细分析: 1. **方案一 (@Order)**:通过 `@Order` 注解设置 Bean 的初始化顺序,但发现 `@PostConstruct` 会影响顺序。 2. **方案二 (SmartInitializingSingleton)**:在所有单例 Bean 初始化后执行额外的初始化工作,但无法精确控制特定 Bean 的顺序。 3. **方案三 (@DependsOn)**:通过 `@DependsOn` 注解指定 Bean 之间的依赖关系,成功实现顺序控制,但耦合性较高。
实战指南:四种调整 Spring Bean 初始化顺序的方案
|
16天前
|
安全 Java 开发者
Spring容器中的bean是线程安全的吗?
Spring容器中的bean默认为单例模式,多线程环境下若操作共享成员变量,易引发线程安全问题。Spring未对单例bean做线程安全处理,需开发者自行解决。通常,Spring bean(如Controller、Service、Dao)无状态变化,故多为线程安全。若涉及线程安全问题,可通过编码或设置bean作用域为prototype解决。
27 1
|
2月前
|
XML Java 数据格式
Spring从入门到入土(bean的一些子标签及注解的使用)
本文详细介绍了Spring框架中Bean的创建和使用,包括使用XML配置文件中的标签和注解来创建和管理Bean,以及如何通过构造器、Setter方法和属性注入来配置Bean。
80 9
Spring从入门到入土(bean的一些子标签及注解的使用)
|
3月前
|
缓存 安全 Java
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
从底层源码入手,通过代码示例,追踪AnnotationConfigApplicationContext加载配置类、启动Spring容器的整个流程,并对IOC、BeanDefinition、PostProcesser等相关概念进行解释
312 24
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
|
2月前
|
Java 测试技术 Windows
咦!Spring容器里为什么没有我需要的Bean?
【10月更文挑战第11天】项目经理给小菜分配了一个紧急需求,小菜迅速搭建了一个SpringBoot项目并完成了开发。然而,启动测试时发现接口404,原因是控制器包不在默认扫描路径下。通过配置`@ComponentScan`的`basePackages`字段,解决了问题。总结:`@SpringBootApplication`默认只扫描当前包下的组件,需要扫描其他包时需配置`@ComponentScan`。
|
3月前
|
XML Java 数据格式
Spring IOC—基于XML配置Bean的更多内容和细节(通俗易懂)
Spring 第二节内容补充 关于Bean配置的更多内容和细节 万字详解!
245 18
Spring IOC—基于XML配置Bean的更多内容和细节(通俗易懂)
|
3月前
|
XML Java 数据格式
spring复习02,xml配置管理bean
详细讲解了Spring框架中基于XML配置文件管理bean的各种方式,包括获取bean、依赖注入、特殊值处理、属性赋值、集合类型处理、p命名空间、bean作用域及生命周期和自动装配。
spring复习02,xml配置管理bean
|
2月前
|
Java 开发者 Spring
Spring bean的生命周期详解!
本文详细解析Spring Bean的生命周期及其核心概念,并深入源码分析。Spring Bean是Spring框架的核心,由容器管理其生命周期。从实例化到销毁,共经历十个阶段,包括属性赋值、接口回调、初始化及销毁等。通过剖析`BeanFactory`、`ApplicationContext`等关键接口与类,帮助你深入了解Spring Bean的管理机制。希望本文能助你更好地掌握Spring Bean生命周期。
134 1
下一篇
DataWorks