Spring IOC⾼级特性

163 阅读7分钟

lazy-Init 延迟加载

延迟加载:Bean的延迟加载(延迟创建)

ApplicationContext 容器的默认⾏为是在启动服务器时将所有 singleton bean 提前进⾏实例化。提前实例化意味着作为初始化过程的⼀部分,ApplicationContext 实例会创建并配置所有的singleton bean。

⽐如:

<bean id="testBean" class="cn.lagou.LazyBean" />

<!-- lazy-init默认配置为false,即不进行延迟加载,也即立即加载:-->

<bean id="testBean" calss="cn.lagou.LazyBean" lazy-init="false" />

lazy-init="false",⽴即加载,表示在spring启动时,⽴刻进⾏实例化。

如果不想让⼀个singleton bean 在 ApplicationContext实现初始化时被提前实例化,那么可以将bean设置为延迟实例化。如下:

<bean id="testBean" calss="cn.lagou.LazyBean" lazy-init="true" />l

设置 lazy-init 为 true 的 bean 将不会在 ApplicationContext 启动时提前被实例化,⽽是第⼀次向容器通过 getBean 索取 bean 时实例化的。

如果⼀个设置了⽴即加载的 bean1,引⽤了⼀个延迟加载的 bean2 ,那么 bean1 在容器启动时被实例化,⽽ bean2 由于被 bean1 引⽤,所以也被实例化,这种情况也符合延时加载的 bean 在第⼀次调⽤时才被实例化的规则。

演示

1.演示立即加载的情况

<!--
    lazy-init:延迟加载,true代表延迟,false代表立即加载,默认是false
-->
<bean id="lazyResult" class="com.lagou.edu.pojo.Result"/>

测试类如下:

/**
 * 测试bean的lazy-init属性
 */
@Test
public void testBeanLazy(){
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    Object lazyResult = applicationContext.getBean("lazyResult");
    System.out.println(lazyResult);
    applicationContext.close();
}

演示结果如下:

查看applicationContext变量中的beanFactory属性中的singletonObjects属性存放的对象如下

image.png

修改配置为延迟加载如下:

<bean id="lazyResult" class="com.lagou.edu.pojo.Result" lazy-init="true"/>

image.png

image.png

也可以在容器层次中通过在 元素上使⽤ "default-lazy-init" 属性来控制延时初始化。如下⾯配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans  xmlns="http://www.springframework.org/schema/beans"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
" default-lazy-init="true">

    <!--
        lazy-init:延迟加载,true代表延迟,false代表立即加载,默认是false
    -->
    <bean id="lazyResult" class="com.lagou.edu.pojo.Result"   init-method="initMethod"/>
</beans>

如果⼀个 bean 的 scope 属性为 scope="pototype" 时,即使设置了 lazy-init="false",容器启动时也不会实例化bean,⽽是调⽤ getBean ⽅法实例化的。

延迟加载的应用场景:

  • (1)开启延迟加载⼀定程度提⾼容器启动和运转性能

  • (2)对于不常使⽤的 Bean 设置延迟加载,这样偶尔使⽤的时候再加载,不必要从⼀开始该 Bean 就占⽤资源

FactoryBean 和 BeanFactory

BeanFactory接⼝是容器的顶级接⼝,定义了容器的⼀些基础⾏为,负责⽣产和管理Bean的⼀个⼯⼚,具体使⽤它下⾯的⼦接⼝类型,⽐如ApplicationContext;此处我们重点分析FactoryBean

Spring中Bean有两种,⼀种是普通Bean,⼀种是⼯⼚Bean(FactoryBean),FactoryBean可以⽣成某⼀个类型的Bean实例(返回给我们),也就是说我们可以借助于它⾃定义Bean的创建过程。Bean创建的三种⽅式中的静态⽅法和实例化⽅法和FactoryBean作⽤类似,FactoryBean使⽤较多,尤其在Spring框架⼀些组件中会使⽤,还有其他框架和Spring框架整合时使⽤。

FactoryBean接口的方法如下:

/**
 * 可以让我们⾃定义Bean的创建过程(完成复杂Bean的定义)
 */
public interface FactoryBean<T> {

    /**
     * 返回FactoryBean创建的Bean实例,如果isSingleton返回true,则该实例会放到Spring容器的单例对象缓存池中Map
     */
    @Nullable
    T getObject() throws Exception;

    /**
     * 返回FactoryBean创建的Bean类型
     */
    @Nullable
    Class<?> getObjectType();

    /**
     * 返回作⽤域是否单例
     */
    default boolean isSingleton() {
        return true;
    }
}

示例

1.Company类如下:

public class Company {

    private String name;
    private String address;
    private int scale;

    //省略get,set和tostring方法
}

2.CompanyFactoryBean类如下:

public class CompanyFactoryBean implements FactoryBean<Company> {

    private String companyInfo;//公司名称,地址,规模

    public void setCompanyInfo(String companyInfo) {
        this.companyInfo = companyInfo;
    }

    /**
     * 返回FactoryBean创建的Bean实例,如果isSingleton返回true,则该实例会放到Spring容器
     * 的单例对象缓存池中Map
     */
    @Override
    public Company getObject() throws Exception {
        //创建复杂对象company
        Company company = new Company();
        String[] info = companyInfo.split(",");
        company.setName(info[0]);
        company.setAddress(info[1]);
        company.setScale(Integer.parseInt(info[2]));
        return company;
    }

    /**
     * 返回作⽤域是否单例
     */
    @Override
    public boolean isSingleton() {
        return true;
    }
    
    /**
     * 返回bean的类型
     */
    @Override
    public Class<?> getObjectType() {
        return Company.class;
    }
}

3.applicationContext.xml中的配置如下:

<bean id="companyBean" class="com.lagou.edu.factory.CompanyFactoryBean">
    <property name="companyInfo" value="拉钩,中关村,500"></property>
</bean>

4.测试类如下:

@Test
public void testFactoryBean(){
    ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    Company company = (Company) ac.getBean("companyBean");
    System.out.println(company);
}

但是如果想要获取获取FactoryBean,即CompanyFactoryBean类对象怎么获取呢?,需要在id之前添加“&,如下:

@Test
public void testFactoryBean(){
    ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    CompanyFactoryBean factoryBean = (CompanyFactoryBean) ac.getBean("&companyBean");
    System.out.println(factoryBean);
}

后置处理器

Spring提供了两种后处理bean的扩展接⼝,分别为 BeanPostProcessorBeanFactoryPostProcessor,两者在使⽤上是有所区别的。

  • 在BeanFactory初始化之后可以使⽤BeanFactoryPostProcessor进⾏后置处理做⼀些事情

  • 在Bean对象实例化(并不是Bean的整个⽣命周期完成)之后可以使⽤BeanPostProcessor进⾏后置处理做⼀些事情

注意:对象不⼀定是springbean,⽽springbean⼀定是个对象

SpringBean的⽣命周期如下图所示

image.png

BeanPostProcessor

BeanPostProcessor是针对Bean级别的处理,可以针对某个具体的Bean.

public interface BeanPostProcessor {
    /**
     * bean初始化之前执行,beanName为bean的id
     */
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
    
    /**
     * bean初始化之后执行,beanName为bean的id
     */
    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

该接⼝提供了两个⽅法,分别在Bean的初始化⽅法前和初始化⽅法后执⾏,具体这个初始化⽅法指的是什么⽅法,类似我们在定义bean时,定义了init-method所指定的⽅法。

定义⼀个类实现了BeanPostProcessor,默认是会对整个Spring容器中所有的bean进⾏处理。如果要对具体的某个bean处理,可以通过⽅法参数判断,两个类型参数分别为Object和String,第⼀个参数是每个bean的实例,第⼆个参数是每个bean的name或者id属性的值。所以我们可以通过第⼆个参数,来判断我们将要处理的具体的bean。

注意:处理是发⽣在Spring容器的实例化和依赖注⼊之后。

bean完整生命周期演示如下:

/**
 * 拦截实例化之后的对象(实例化了并且属性注入了)
 */
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if("lazyResult".equalsIgnoreCase(beanName)) {
            System.out.println("MyBeanPostProcessor  before方法拦截处理lazyResult");
        }

        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if("lazyResult".equalsIgnoreCase(beanName)) {
            System.out.println("MyBeanPostProcessor  after方法拦截处理lazyResult");
        }
        return bean;
    }
}
public class Result implements BeanNameAware,BeanFactoryAware,ApplicationContextAware,InitializingBean, DisposableBean {

    private String status;
    private String message;

    //省略get,set和tostring

    /**
     * BeanNameAware接口实现方法
     */
    @Override
    public void setBeanName(String name) {
        System.out.println("注册我成为bean时定义的id:" + name);
    }

    /**
     * BeanFactoryAware接口实现方法
     */
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("管理我的beanfactory为:" + beanFactory);
    }

    /**
     * ApplicationContextAware接口实现方法
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("高级容器接口ApplicationContext:" + applicationContext);
    }
    
    /**
     * InitializingBean接口实现方法
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("afterPropertiesSet......");
    }


    public void initMethod() {
        System.out.println("init-method....");
    }

    @PostConstruct
    public void postCoustrcut() {
        System.out.println("postCoustrcut");
    }


    @PreDestroy
    public void PreDestroy(){
        System.out.println("PreDestroy...");
    }

    /**
     * DisposableBean接口实现方法
     */
    @Override
    public void destroy() throws Exception {
        System.out.println("destroy.....");
    }
}

applicationContext.xml配置如下:

<bean id="lazyResult" class="com.lagou.edu.pojo.Result"  init-method="initMethod"/>

测试类如下:

@Test
public void test(){
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    Object lazyResult = applicationContext.getBean("lazyResult");
    System.out.println(lazyResult);
    applicationContext.close();
}

测试结果如下:

image.png

BeanFactoryPostProcessor

BeanFactory级别的处理,是针对整个Bean的⼯⼚进⾏处理,典型应⽤:PropertyPlaceholderConfifigurer

@FunctionalInterface
public interface BeanFactoryPostProcessor {
    void postProcessBeanFactory(ConfigurableListableBeanFactory var1) throws BeansException;
}

此接⼝只提供了⼀个⽅法,⽅法参数为ConfifigurableListableBeanFactory,该参数类型定义了⼀些⽅法

image.png

其中有个⽅法名为getBeanDefifinition的⽅法,我们可以根据此⽅法,找到我们定义bean的 BeanDefifinition对象。然后我们可以对定义的属性进⾏修改,BeanDefinition就是将applicationContext中的每一个bean标签中配置的对象信息封装成的一个java类对象。

以下是BeanDefifinition中的⽅法

image.png

⽅法名字类似我们bean标签的属性,setBeanClassName对应bean标签中的class属性,所以当我们拿到BeanDefifinition对象时,我们可以⼿动修改bean标签中所定义的属性值。

BeanDefifinition对象:我们在 XML 中定义的 bean标签,Spring 解析 bean 标签成为⼀个 JavaBean,这个JavaBean 就是 BeanDefifinition

注意:调⽤ BeanFactoryPostProcessor ⽅法时,这时候bean还没有实例化,此时 bean 刚被解析成BeanDefifinition对象

image.png

演示通过BeanFactoryPostProcessor来修改bean的延迟加载属性:

在applicationContext中为立即加载,即默认情况lazy-init为false

<bean id="lazyResult" class="com.lagou.edu.pojo.Result"/>
/**
 * @Auther: huangshuai
 * @Date: 2021/9/20 20:25
 * @Description:自定义BeanFactoryPostProcessor修改bean的lazy-init属性
 * @Version:
 */
@Component
public class TestBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        BeanDefinition beanDefinition = configurableListableBeanFactory.getBeanDefinition("lazyResult");
        //修改bean的id为lazyResult的对象为延迟加载
        beanDefinition.setLazyInit(true);
    }
}

测试结果如下:

image.png