spring(3)

发布于:2024-04-01 ⋅ 阅读:(78) ⋅ 点赞:(0)

1、bean生命周期

1.1 bean生命周期之五步

第一步:实例化Bean
第二步:Bean属性赋值
第三步:初始化Bean
第四步:使用Bean
第五步:销毁Bean
注意 初始化bean和销毁bean 都是需要我们在类中自己写,自己配置到xml文件中的。名字随便写。

package com.cky.bean;

public class User {
    private String name;

    public User() {
        System.out.println("第一步 无参构造执行");
    }

    public void setName(String name) {
        System.out.println("第二步 属性配置");
        this.name = name;
    }
    public void initbean(){
        System.out.println("第三步 初始化bean");
    }
    public void destorybean(){
        System.out.println("第五步 销毁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">

<bean id="user" class="com.cky.bean.User" init-method="initbean" destroy-method="destorybean">
    <property name="name" value="cui"></property>
</bean>
</beans>
package com.cky.test;

import com.cky.bean.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Mytest {
    @Test
    public void test(){
        ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring.xml");
        User user1 = applicationContext.getBean("user", User.class);
        ClassPathXmlApplicationContext context = (ClassPathXmlApplicationContext) applicationContext;
        System.out.println("第四步 使用bean");
        context.close();
    }
}

在这里插入图片描述

1.2bean生命周期之七步

第一步:实例化Bean
第二步:Bean属性赋值
第三步:执行“Bean后处理器”的before方法。
第四步:初始化Bean
第五步:执行“Bean后处理器”的after方法。
第六步:使用Bean
第七步:销毁Bean

在以上的5步中,第3步是初始化Bean,如果你还想在初始化前和初始化后添加代码,可以加入“Bean后处理器”。
编写一个类实现BeanPostProcessor类,并且重写before和after方法:

package com.cky.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class beanPostpropoess implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("bean后处理器 before");
        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("bean后处理器 after");
        return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
    }
}

<?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 class="com.cky.bean.beanPostpropoess"></bean>
<bean id="user" class="com.cky.bean.User" init-method="initbean" destroy-method="destorybean" >
    <property name="name" value="cui"></property>
</bean>
</beans>

在这里插入图片描述
注意 该后处理器 是对该配置中所有bean对象都适用。但是只是对这一个配置类中的所有bean。

1.3 bean生命周期之十步

点位1:在“Bean后处理器”before方法之前
干了什么事儿?
检查Bean是否实现了Aware相关的接口,如果实现了接口则调用这些接口中的方法。
然后调用这些方法的目的是为了给你传递一些数据,让你更加方便使用。
点位2:在“Bean后处理器”before方法之后
干了什么事儿?
检查Bean是否实现了InitializingBean接口,如果实现了,则调用接口中的方法。
点位3:使用Bean之后,或者说销毁Bean之前
干了什么事儿?
检查Bean是否实现了DisposableBean接口,如果实现了,则调用接口中的方法。

添加的这三个点位的特点:都是在检查你这个Bean是否实现了某些特定的接口,如果实现了这些接口,则Spring容器会调用这个接口中的方法。
在这里插入图片描述
Aware相关的接口包括:BeanNameAware、BeanClassLoaderAware、BeanFactoryAware
● 当Bean实现了BeanNameAware,Spring会将Bean的名字传递给Bean。
● 当Bean实现了BeanClassLoaderAware,Spring会将加载该Bean的类加载器传递给Bean。
● 当Bean实现了BeanFactoryAware,Spring会将Bean工厂对象传递给Bean。
测试以上10步,可以让User类实现5个接口,并实现所有方法:
● BeanNameAware
● BeanClassLoaderAware
● BeanFactoryAware
● InitializingBean
● DisposableBean

package com.cky.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;

public class User implements BeanNameAware, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean {
    private String name;

    public User() {
        System.out.println("第一步 无参构造执行");
    }

    public void setName(String name) {
        System.out.println("第二步 属性配置");
        this.name = name;
    }
    public void initbean(){
        System.out.println("第三步 初始化bean");
    }
    public void destorybean(){
        System.out.println("第五步 销毁bean");
    }

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        System.out.println("类加载器"+classLoader);
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("类工厂"+beanFactory);

    }

    @Override
    public void setBeanName(String name) {
        System.out.println("bean"+name);

    }

    @Override
    public void destroy() throws Exception {
        System.out.println("销毁之前");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("初始化之前");
    }
}

在这里插入图片描述

1.4 bean作用域与管理周期

Spring 根据Bean的作用域来选择管理方式。
● 对于singleton作用域的Bean,Spring 能够精确地知道该Bean何时被创建,何时初始化完成,以及何时被销毁;
对于单例模式,spring管理整个生命周期,包括创建和销毁。
● 而对于 prototype 作用域的 Bean,Spring 只负责创建,当容器创建了 Bean 的实例后,Bean 的实例就交给客户端代码管理,Spring 容器将不再跟踪其生命周期。
对于多例模式,spring只管到初始化之后 即销毁之前的和销毁这两个步骤spring是不管的
当我把scope改为scope=“prototype”
之后我们可以看出 销毁前的方法和销毁方法,spring是不管的。
在这里插入图片描述

2、把自己new的对象交给spring管理

package com.cky.test;

import com.cky.bean.Student;
import com.cky.bean.User;
import org.junit.Test;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Mytest {
    @Test
    public void test(){

        Student student=new Student();
        DefaultListableBeanFactory defaultListableBeanFactory=new DefaultListableBeanFactory();
        defaultListableBeanFactory.registerSingleton("studentbean",student);
        Student studentbean1 = defaultListableBeanFactory.getBean("studentbean", Student.class);
        System.out.println(studentbean1);

    }
}

3、Bean循环依赖

3.1 set+singleton

在singleton + setter模式下,为什么循环依赖不会出现问题,Spring是如何应对的?
主要的原因是,在这种模式下Spring对Bean的管理主要分为清晰的两个阶段:
第一个阶段:在Spring容器加载的时候,实例化Bean,只要其中任意一个Bean实例化之后,马上进行"曝光"【不等属性赋值就曝光】
第二个阶段:Bean“曝光”之后,再进行属性的赋值(调用set方法。)。
核心解决方案是:实例化对象和对象的属性赋值分为两个阶段来完成的。
注意:只有在scope是singleton的情况下,Bean才会采取提前“曝光”的措施。

package com.cky.bean;


public class Husband {
    private String name;
    private Wife wife;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setWife(Wife wife) {
        this.wife = wife;
    }

    @Override
    public String toString() {
        return "Husband{" +
                "name='" + name + '\'' +
                ", wife=" + wife.getName() +
                '}';
    }
}

package com.cky.bean;

public class Wife {
    private String name;
    private Husband husband;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setHusband(Husband husband) {
        this.husband = husband;
    }

    @Override
    public String toString() {
        return "Wife{" +
                "name='" + name + '\'' +
                ", husband=" + husband.getName() +
                '}';
    }
}

<?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="husbandBean" class="com.cky.bean.Husband" scope="singleton">
        <property name="name" value="张三"/>
        <property name="wife" ref="wifeBean"/>
    </bean>
    <bean id="wifeBean" class="com.cky.bean.Wife" scope="singleton">
        <property name="name" value="小花"/>
        <property name="husband" ref="husbandBean"/>
    </bean>
</beans>

在ApplicationContext applicationContext=new ClassPathXmlApplicationContext(“spring1.xml”);spring容器就开始实例化bean对象以及给bean对象赋值。

3.2 构造+singleton

这种方法,循环依赖会出问题
因为我们无法提前曝光,在构造时,就需要给属性赋值才能实例化,而由于依赖另一个对象,另一个对象又依赖于当前对象,无法进行实例化,会出现BeanCurrentlyInCreationException 当前的Bean正在处于创建中异常。

3.3 propotype+set注入

循环依赖也会出现问题。会出现BeanCurrentlyInCreationException 当前的Bean正在处于创建中异常。由于是多例的,我们在实例化一个对象之后给对象赋值时会需要另外一个bean对象,但是这两个对象都是多例的,所以会一直创建新的对象,双方相互实例化,没有停止…

注意但是这种情况是两个对象都是propotype时才会出现的问题,只有一个时singleton,循环依赖就不会出现问题,因为比如上述例子,妻子是singleton,丈夫是propotype的,妻子在创建时只会创建一个,给妻子赋值时会去创建丈夫对象,此时丈夫对象又会去找妻子对象,由于妻子对象是单例的,只有一个所以该丈夫对象可以创建且成功赋值,妻子对象便也可以成功赋值。

3.4 bean循环依赖源码分析:

Bean生命周期的管理,可以参考Spring的源码:AbstractAutowireCapableBeanFactory类的doCreateBean()方法。在这里插入图片描述
源码分析:
DefaultSingletonBeanRegistry类中有三个比较重要的缓存:
private final Map<String, Object> singletonObjects 一级缓存
private final Map<String, Object> earlySingletonObjects 二级缓存
private final Map<String, ObjectFactory<?>> singletonFactories 三级缓存

这三个缓存都是Map集合。
Map集合的key存储的都是bean的name(bean id)。

一级缓存存储的是:单例Bean对象。完整的单例Bean对象,也就是说这个缓存中的Bean对象的属性都已经赋值了。是一个完整的Bean对象。
二级缓存存储的是:早期的单例Bean对象。这个缓存中的单例Bean对象的属性没有赋值。只是一个早期的实例对象。
三级缓存存储的是:单例工厂对象。这个里面存储了大量的“工厂对象”,每一个单例Bean对象都会对应一个单例工厂对象。
这个集合中存储的是,创建该单例对象时对应的那个单例工厂对象。

3.5 常见面试问题

谈谈Spring是如何解决循环依赖的?

谈谈Spring有哪几种循环依赖问题?

Spring为什么不能解决构造器注入以及多例bean的循环依赖?

只有一级缓存、三级缓存的话,能不能解决循环依赖?

为什么需要使用二级缓存earlySingletonObject?

Spring三级缓存中为什么保存的是ObjectFacory对象工厂,而不是原始实例对象?

Spring的循环依赖只能使用三级缓存才能解决?【只解决循环依赖】是否可以减少为二级缓存。

那为什么Spring没有选择使用二级缓存的方案,而是额外加一级,用三级缓存。涉及AOP。
本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到