【Spring Bean生命周期】聊透扩展点的流程及应用

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第4天
Spring Bean生命周期,从入门到进阶:
【Spring Bean生命周期】小白也能看懂的入门篇
【Spring Bean生命周期】高手如何解读源码(一) - 资源的扫描和注册
【Spring Bean生命周期】高手如何解读源码(二) - Bean的生命周期

  Spring之所以具备如此强大的生态,扩展能力强是很重要的一方面,这得益于它本身提供了非常多的扩展点。本节我们针对Spring生命周期中涉及到的扩展点来看一看,究竟它们是何方神圣?

1 扩展点流程

  Spring生命周期的主要扩展点如图:

image.png

2 扩展点解析及应用场景

ApplicationContextInitializer接口

  用于在执行refresh()之前初始化ConfigurationApplicationContext的回调接口。
通常用于需要对应用程序上下文进行编程初始化的web应用程序中。例如,注册属性源或根据上下文环境激活配置文件。 比如:Spring boot就写了几个ApplicationContextInitializer的实现类,常见作用:

  • ConfigurationWarningsApplicationContextInitializer:对于一般配置错误在日志中作出警告
  • ContextIdApplicationContextInitializer:设置ApplicationContext#getId()所获取的ID值,默认取spring.application.name属性值,没有配置时默认为 application
  • DelegatingApplicationContextInitializer:使用环境属性context.initializer.classes指定的初始化器(initializers)进行初始化工作,如果没有指定则什么都不做
    ......
    实现方式如下
public class CustomApplicationContextInitializer implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        // 打印当前方法类名和方法名,调试用
        CountUtil.println(getClass(),Thread.currentThread().getStackTrace()[2].getMethodName());
    }
}
复制代码

在配置文件中设置:

context.initializer.classes=com.zyq.demo.expand.CustomApplicationContextInitializer
复制代码

BeanDefinitionRegistryPostProcessor

  这个接口在读取项目中的beanDefinition之后执行,提供一个补充的扩展点。使用场景:可以在这里动态注册自己的beanDefinition,可以加载classpath之外的bean。

  比如我们在经常使用的mybatis的mapper和我们的feignClient,往往只需要定义接口而不需要写实现,那么这些实现的生成及注入就是在此处完成的。

@Component
public class CustomBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        // 可以在此处自定义扫描mapper,并生成BeanDefinition信息,通过registry注册到容器中
        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Test001.class);
        registry.registerBeanDefinition("test001",rootBeanDefinition);
        
        CountUtil.println(getClass(),Thread.currentThread().getStackTrace()[2].getMethodName());
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        CountUtil.println(getClass(),Thread.currentThread().getStackTrace()[2].getMethodName());
    }
}
复制代码

BeanFactoryPostProcessor

  这个接口是beanFactory的扩展接口,调用时机在spring在读取beanDefinition信息之后,实例化bean之前。在这个时机,用户可以通过实现这个扩展接口来自行处理一些东西,比如修改已经注册的beanDefinition的元信息。根据注释可知,此时只能承上(即修改元信息),坚决不允许启下(即触发实例化)

  实际工作中,自定义BeanFactoryPostProcessor的情况确实少,比如对敏感信息的解密处理,在数据库的连接配置中,用户名和密码都是明文配置的,这就存在泄漏风险,还有redis的连接配置、shiro的加密算法、rabbitmq的连接配置等等,凡是涉及到敏感信息的,都需要进行加密处理,信息安全非常重要。

  配置的时候以密文配置,在真正用到之前在spring容器中进行解密,然后用解密后的信息进行真正的操作。

@Component
public class CustomBeanFactoryAware implements BeanFactoryPostProcessor {
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        MutablePropertySources propSources = environment.getPropertySources();
        StreamSupport.stream(propSources.spliterator(), false)
                .filter(ps -> ps instanceof OriginTrackedMapPropertySource)
                .collect(Collectors.toList())
                .forEach(ps -> convertPropertySource((PropertySource<LinkedHashMap>) ps));
        LOGGER.info("敏感信息解密完成.....");
    }
}
复制代码

InstantiationAwareBeanPostProcessor

  InstantiationAwareBeanPostProcessor接口继承BeanPostProcessor接口,它内部提供了3个方法,再加上BeanPostProcessor接口内部的2个方法,所以实现这个接口需要实现5个方法。可以称作是Spring生命周期的大宝剑!Spring的核心功能之一AOP就是基于它来实现的。

@Component
public class CustomInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
    //在实例化之前被调用
    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        if(beanClass == TargetClass.class){
            //使用cglib实现动态代理AOP
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(beanClass);
            enhancer.setCallback(new TargetClassProxy());
            TargetClass proxyClass = (TargetClass)enhancer.create();
            return proxyClass;
        }
        return null;
    }
    }
复制代码

BeanNameAware

  这个类也是Aware扩展的一种,触发点在bean的初始化之前,也就是postProcessBeforeInitialization之前,这个类的触发点方法只有一个:setBeanName。使用场景:用户可以扩展这个点,在初始化bean之前拿到spring容器中注册的的beanName,来自行修改这个beanName的值。整个约定了在编写服务代码时,就可以知道客户端调用时的bean名称,可在此处打标签,用于一些特殊bean的捕获。

public class CustomBeanNameAware implements BeanNameAware {
    @Override
    public void setBeanName(String name) {
        
        System.out.println("在这里修改Bean名称");
    }
}
复制代码

@PostConstruct

  @PostConstruct这个并不算一个扩展点,其实就是一个标注。从Java EE5规范开始,Servlet中增加的注解,被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。其作用是在bean的初始化阶段,如果对一个方法标注了@PostConstruct,会先调用这个方法。这里重点是要关注下这个标准的触发点,这个触发点是在postProcessBeforeInitialization之后,InitializingBean.afterPropertiesSet之前。

  使用场景:如果想在生成对象时候完成某些初始化操作,而偏偏这些初始化操作又依赖于依赖注入,那么就无法在构造函数中实现。为此,可以使用@PostConstruct注解一个方法来完成初始化,@PostConstruct注解的方法将会在依赖注入完成后被自动调用。

  spring自带的@schedule,没有开关,项目启动总会启动一个线程;做项目的时候就使用Java的timer,这个设置开关即可自由的控制,关闭的时候,不会启动线程;Java的timer也需要找到一个启动类,可以放到main函数里面启动,这样的话,代码的耦合性太高了,而使用PostConstruct是很干净的。
复制代码
@PostConstruct
    public void init() {
        // 启动定时任务线程
    }
 
复制代码

InitializingBean

  InitializingBean的作用是Bean注入到Spring容器且初始化后,执行特定业务化的操作。Spring允许容器中的Bean,在Bean初始化完成后或者Bean销毁前,执行特定业务化的操作,常用的实现方式有以下三种:

  • 通过实现InitializingBean/DisposableBean接口来处理初始化后/销毁前的操作;
  • 通过标签的init-method/destroy-method属性处理初始化后/销毁前的操作;
  • 在指定方法上加上@PostConstruct或@PreDestroy注解来处理初始化后/销毁前的操作。

在spring初始化bean的时候,如果该bean是实现了InitializingBean接口,并且同时在配置文件中指定了init-method,系统则是先调用afterPropertiesSet方法,然后在调用init-method中指定的方法

public class CustomInitializingBean implements InitializingBean {

    @Override
    public void afterPropertiesSet() throws Exception {

        if (StringUtils.isEmpty(path)) {
            log.error("PLEASE SET THE RULE'S PATH: (spring.drools.path = XXX).");
        }
    }
}
复制代码

BeanPostProcessor

  在Bean对象在实例化和依赖注入完毕后,在显示调用初始化方法的前后添加我们自己的逻辑。注意是Bean实例化完毕后及依赖注入完成后触发的。

public interface BeanPostProcessor {

   /**
    * Apply this {@code BeanPostProcessor} to the given new bean instance <i>before</i> any bean
    * initialization callbacks (like InitializingBean's {@code afterPropertiesSet}
    * or a custom init-method). The bean will already be populated with property values.
    * The returned bean instance may be a wrapper around the original.
    */
   @Nullable
   default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
      return bean;
   }

   /**
    * Apply this {@code BeanPostProcessor} to the given new bean instance <i>after</i> any bean
    * initialization callbacks (like InitializingBean's {@code afterPropertiesSet}
    * or a custom init-method). The bean will already be populated with property values.
    * The returned bean instance may be a wrapper around the original.
    */
   @Nullable
   default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
      return bean;
   }

}
复制代码

从代码可以看出来,返回的时经过处理后的Bean,这种就很适合代理类的场景,如果需要实现类似AOP的逻辑,可以在此处理。

好了,上面就是涉及到Spring生命周期的一些主要扩展点,主要是帮助我们集合Spring生命周期来理解Spring的整体逻辑。

猜你喜欢

转载自juejin.im/post/7175820824769724476