万隆的笔记 万隆的笔记
博文索引
笔试面试
  • 在线学站

    • 菜鸟教程 (opens new window)
    • 入门教程 (opens new window)
    • Coursera (opens new window)
  • 在线文档

    • w3school (opens new window)
    • Bootstrap (opens new window)
    • Vue (opens new window)
    • 阿里开发者藏经阁 (opens new window)
  • 在线工具

    • tool 工具集 (opens new window)
    • bejson 工具集 (opens new window)
    • 文档转换 (opens new window)
  • 更多在线资源
  • Changlog
  • Aboutme
GitHub (opens new window)
博文索引
笔试面试
  • 在线学站

    • 菜鸟教程 (opens new window)
    • 入门教程 (opens new window)
    • Coursera (opens new window)
  • 在线文档

    • w3school (opens new window)
    • Bootstrap (opens new window)
    • Vue (opens new window)
    • 阿里开发者藏经阁 (opens new window)
  • 在线工具

    • tool 工具集 (opens new window)
    • bejson 工具集 (opens new window)
    • 文档转换 (opens new window)
  • 更多在线资源
  • Changlog
  • Aboutme
GitHub (opens new window)
  • Spring

    • Spring简介
    • Spring核心思想
    • 手写实现IoC和AOP
    • Spring Web与Bean装配
    • Spring Transaction
    • Spring Validation
    • Spring IoC基础应用
    • Spring IoC高级特性
      • lazy-Init 延迟加载
      • FactoryBean和BeanFactory
      • 后置处理器
    • Spring IoC源码剖析
    • Spring AOP应用
    • Spring AOP声明式事务
    • Spring AOP源码剖析
  • SprinvMVC

  • SpringBoot

  • Spring
  • Spring
2023-01-26
目录

Spring IoC高级特性

# Spring IoC高级特性

# lazy-Init 延迟加载

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

<bean id="testBean" class="com.example.LazyBean" />
<!--等同于-->
<bean id="testBean" calss="com.example.LazyBean" lazy-init="false" />

lazy-init="false"表示为⽴即加载,表示在Spring启动时,⽴刻进⾏实例化。如果不想让⼀个singleton bean在ApplicationContext实现初始化时被提前实例化,那么可以将bean设置为延迟实例化。如下:

<bean id="testBean" class="com.example.LazyBean" lazy-init="true"/>

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

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

我们可以在容器层次中通过在 <beans>标签上使⽤ default-lazy-init = "true" 默认开启延时初始化,如下⾯配置:

<beans default-lazy-init="true">
  <!-- no beans will be eagerly pre-instantiated... -->
</beans>

注意,<bean>标签的 lazy-init优先级会比全局的设置要高,此外,如果⼀个 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作⽤类似,在Spring框架⼀些组件中FactoryBean使⽤较多,还有其他框架和Spring框架整合时使⽤。下面是它的接口定义:

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

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

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

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

}

例如有如下Company类:

public class Company {

    private String name;
    private String address;
    private int scale;
 
  	// ... setter & getter
}

我们想通过字符串以"name,address,scale"的方式去实例化bean,我们可以通过实现FactoryBean接口完成:

public class CompanyFactoryBean implements FactoryBean<Company> {

    // 创建格式: 公司名称,地址,规模
    private String companyInfo;

    public void setCompanyInfo(String companyInfo) {
        this.companyInfo = companyInfo;
    }
  
    @Override
    public Company getObject() throws Exception {
        Company company = new Company();
        String[] strings = companyInfo.split(",");
        company.setName(strings[0]);
        company.setAddress(strings[1]);
        company.setScale(Integer.parseInt(strings[2]));
        return company;
    }

    @Override
    public Class<?> getObjectType() {
        return Company.class;
    }


    /**
     * 默认true,可以不覆盖
     */
    @Override
    public boolean isSingleton() {
        return FactoryBean.super.isSingleton();
    }

}

XML配置如下:

<bean id="company" class="com.example.transfer.factory.CompanyFactoryBean">
    <property name="companyInfo" value="万隆同学,广州,1"/>
</bean>

测试用例,获取FactoryBean以及它产⽣的对象:

ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
// 获取FactoryBean产⽣的对象:
Object company = applicationContext.getBean("company");
System.out.println(company); // com.example.transfer.pojo.Company@5b38c1ec
// 获取FactoryBean:
Object companyFactoryBean = applicationContext.getBean("&company");
System.out.println(companyFactoryBean); // com.example.transfer.factory.CompanyFactoryBean@338fc1d8

# 后置处理器

Spring提供了两种后处理Bean的扩展接⼝,分别为BeanPostProcessor和BeanFactoryPostProcessor,两者在使⽤上是有所区别的

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

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

# SpringBean的⽣命周期

SpringBean的⽣命周期如下:

spring_bean_life

生命周期描述:

  1. 根据配置情况调⽤ Bean 构造⽅法或⼯⼚⽅法实例化 Bean。

  2. 利⽤依赖注⼊完成 Bean 中所有属性值的配置注⼊。

  3. 如果 Bean 实现了 BeanNameAware 接⼝,则 Spring 调⽤ Bean 的 setBeanName() ⽅法传⼊当前 Bean 的 id 值。

  4. 如果 Bean 实现了 BeanFactoryAware 接⼝,则 Spring 调⽤ setBeanFactory() ⽅法传⼊当前⼯⼚实例的引⽤。

  5. 如果 Bean 实现了 ApplicationContextAware 接⼝,则 Spring 调⽤ setApplicationContext() ⽅法传⼊当前 ApplicationContext 实例的引⽤。

  6. 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调⽤该接⼝的预初始化⽅法 postProcessBeforeInitialzation() 对 Bean 进⾏加⼯操作,此处⾮常重要,Spring 的 AOP 就是利⽤它实现的。

  7. 如果 Bean 实现了 InitializingBean 接⼝,则 Spring 将调⽤ afterPropertiesSet() ⽅法。

  8. 如果在配置⽂件中通过 init-method 属性指定了初始化⽅法,则调⽤该初始化⽅法。

  9. 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调⽤该接⼝的初始化⽅法 postProcessAfterInitialization()。此时,Bean 已经可以被系统使⽤了。

  10. 如果在 <bean> 中指定了该 Bean 的作⽤范围为 scope="singleton",则将该 Bean 放⼊ Spring IoC 的缓存池中,将触发 Spring 对该 Bean 的⽣命周期管理;如果在 <bean> 中指定了该 Bean 的作⽤范围为 scope="prototype",则将该 Bean 交给调⽤者,调⽤者管理该 Bean 的⽣命周期,Spring 不再管理该 Bean。

  11. 如果 Bean 实现了 DisposableBean 接⼝,则 Spring 会调⽤ destory() ⽅法将 Spring 中的 Bean 销毁;

    如果在配置⽂件中通过 destory-method 属性指定了 Bean 的销毁⽅法,则 Spring 将调⽤该⽅法对 Bean 进⾏销毁。

注意:Spring 为 Bean 提供了细致全⾯的⽣命周期过程,通过实现特定的接⼝或 <bean> 的属性设置,都可以对 Bean 的⽣命周期过程产⽣影响。虽然可以随意配置 <bean> 的属性,但是建议不要过多地使⽤ Bean 实现接⼝,因为这样会导致代码和 Spring 的聚合过于紧密。

# BeanPostProcessor

BeanPostProcessor是针对Bean级别的处理,可以针对某个具体的Bean。定义如下:

public interface BeanPostProcessor {

  /**
  * 预初始化⽅法
  */
	@Nullable
	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

	/**
	 * 初始化⽅法
	 */
	@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容器的实例化和依赖注⼊之后。

# BeanFactoryPostProcessor

BeanFactory级别的处理,是针对整个Bean的⼯⼚进⾏处理,典型应⽤:PropertyPlaceholderConfigurer。接口定义如下:

@FunctionalInterface
public interface BeanFactoryPostProcessor {

	/**
	 * Modify the application context's internal bean factory after its standard
	 * initialization. All bean definitions will have been loaded, but no beans
	 * will have been instantiated yet. This allows for overriding or adding
	 * properties even to eager-initializing beans.
	 * @param beanFactory the bean factory used by the application context
	 * @throws org.springframework.beans.BeansException in case of errors
	 */
	void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

}

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

spring_bean_factory

其中有个⽅法名为getBeanDefinition的⽅法,我们可以根据此⽅法,找到我们定义bean的BeanDefinition对象(xml.bean标签对象)。然后我们可以对定义的属性进⾏修改(EL表达式替换),以下是BeanDefinition中的⽅法:

spring_bean_definition

Spring容器启动的过程中,会将Bean解析成Spring内部的BeanDefinition结构

⽅法名字类似我们bean标签的属性,类名、scope、属性、构造函数参数列表、依赖的bean、是否是单例类、是否是懒加载等,其实就是将Bean的定义信息存储到这个BeanDefinition相应的属性中,后⾯对Bean的操作就直接对BeanDefinition进⾏,例如拿到这个BeanDefinition后,可以根据⾥⾯的类名、构造函数、构造函数参数,使⽤反射进⾏对象创建setBeanClassName对应bean标签中的class属性,所以当我们拿到BeanDefinition对象时,我们可以⼿动修改bean标签中所定义的属性值。

BeanDefinition对象:我们在 XML 中定义的bean标签,Spring 解析 bean 标签成为⼀个 JavaBean,这个JavaBean 就是 BeanDefinition。注意:调⽤ BeanFactoryPostProcessor ⽅法时,这时候bean还没有实例化,此时 bean 刚被解析成BeanDefinition对象。

上次更新: 7/25/2023, 1:09:44 AM
Spring IoC源码剖析

Spring IoC源码剖析→

最近更新
01
2025
01-15
02
Elasticsearch面试题
07-17
03
Elasticsearch进阶
07-16
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式