基于Spring和Spring Boot框架的集成与扩展点

张佳

前言

对于每个Java程序员来说,Spring应该是我们Java学习中第一个开始接触的技术框架,而且应该是每个Java程序员使用和熟悉程度最高的技术框架。近些年来,Spring的生态慢慢变得的越来越丰富,Spring Boot作为一种开箱即用的集大成者,整合了常用的一些模块,大大减少了重复性的工作,这离不开Spring框架的扩展性,同时Spring Cloud也基于Spring衍生出了微服务实施的一整套解决方案,大大推进了微服务的技术落地。从2004年Spring框架发布,已经历经15年发展,Spring框架慢慢的成为Java生态中的基础设施,正是其不断的发展,让Java生命力的越来越强大。

最近在工作中使用到了Nacos作为配置中心,顺便熟悉了Nacos Spring Boot Starter和Nacos Spring Cloud的一些代码,发现对于Spring中的一些特性慢慢生疏,对此作了一些反思,主要平时更加专注于业务场景解决方案和其他中间件的使用,而且技术学习的重心更加倾向于原理介绍和最佳实践,然而目前作为一名Java开发人员,Java原生包是Java技术体系的根基,Spring框架是Java生态的基础设施,两者都需要持续不断的学习,更新知识体系。

伴随着对Nacos源码的阅读,慢慢带着问题重新学习了一些Spring框架中的常用类和结构,如果我们要基于Spring框架做扩展,或者集成一些基础中间件,这些特性和扩展点成为了不可或缺的知识。

Spring Bean和BeanFactory

Bean生命周期中的扩展点

Bean的生命周期

首先必须先了解Bean加载和实例化过程中都包含哪些过程

bean-lifecycle

通常来说Bean生命周期过程执行顺序包含了 1. 创建BeanDefinition
2. 创建Bean,初始化字段
3. 执行Aware接口(包括常见的BeanNameAware,BeanFactoryAware,EnvironmentAware,ApplicationContextAware等)
4. 执行BeanPostProcessor的postProcessBeforeInitialization
5. 执行InitializingBean#AfterProperiesSet方法
6. 执行init-method(xml)或者@InitMethod或者@Bean(initMethod="")指定的初始化方法
7. 执行BeanPostProcessor#postProcessAfterInitialization方法
8. 执行DisposableBean#destroy方法

请注意:实例化(Instantiation)和初始化(Initialization)是两个不一样的阶段

InstantiationAwareBeanPostProcessor接口

Bean Instantiation作为Bean生命周期的第一个阶段,这个接口提供Bean实例化过程的前置处理方法postProcessBeforeInitialization和后置处理方法postProcessAfterInitialization。通常来说,如果要扩展此接口,继承InstantiationAwareBeanPostProcessorAdapter是更好的选择。例如在Spring源码中,AutowiredAnnotationBeanPostProcessor就是作为InstantiationAwareBeanPostProcessorAdapter子类,实现了@Autowire和@Value注解的处理,同样,Nacos @NacosValue注解处理也采用了类似的方式。

另外一个子类是CommonAnnotationBeanPostProcessor,这个类用来处理JSR-250注解,例如我们常用的@Resource,和后面会提到的@PostConstruct,@PreDestroy。

那么如果我们要实现自定义注解的处理,可以采用类似的方式,通过实现InstantiationAwareBeanPostProcessor接口或者继承InstantiationAwareBeanPostProcessorAdapter类来完成。

Aware接口

正如接口名一样,Aware接口通常用于我们需要对一些对象需要感知,通常使用较多的一些子类有

  • EnvironmentAware

    通过setEnvironment方法感知环境信息,包括了profile和当前profile下的配置信息。Spring中的@Value和Spring Boot中的@PropertySource的Property值都是从Spring当前的Environment对象中获取,严格的Environment对象不仅仅包含了我们常用的application-{profile}.properties配置文件,同时包含了多个层级而且有优先级顺序的多种参数,例如JVM参数,系统参数等,可以通过Spring Boot配置说明文档了解更多。Nacos作为分布式配置中心就是作为一个配置对象添加到Environment配置列表中。

  • ApplicationContextAware

  • BeanNameAware

  • BeanFactoryAware

  • ApplicationEventPublisherAware

BeanPostProcessor接口

BeanPostProcessor接口提供对Bean初始化的前置和后置处理方法,主要包含了两个方法,具体执行时间参考生命周期图

  • postProcessBeforeInitialization

  • postProcessAfterInitialization

    其中一些常用子类主要包含

  • MergedBeanDefinitionPostProcessor

    ```java /** * Post-process the given merged bean definition for the specified bean. - @param beanDefinition the merged bean definition for the bean

    • @param beanType the actual type of the managed bean instance
    • @param beanName the name of the bean

      **/ void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class beanType, String beanName); ```

    • 调用时间:Bean实例化后,PopulateBean前

    • 使用场景:通过此方法可以获取到BeanDefinition信息,那么就可以Bean初始化前对BeanDefinition进行检查,或者动态修改BeanDefinition数据,但是在Spring框架中大部分用于对BeanDefinition的读取而不是变更

    image-20190827102626847

InitializingBean和DisposableBean接口

分别对应JSR-250里面的注解* @PostConstruct和@PreDestroy注解

其他

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;
  • 调用时间:在Spring容器读取配置信息,但是Bean实例化前调用
  • 使用场景:在所有配置信息读取后需要对某些Bean的BeanDefiniition进行修改,例如我们通常会使用Spel表达式来定义一些字段的值,Spring中的实现就是通过BeanFactoryPostProcessor的实现类PropertyPlaceholderConfigurer来解析并修改BeanDefiniition中的MutablePropertyValues,提前对应的表达式值解析,具体参考PropertyPlaceholderConfigurer#processProperties方法

容器启动过程扩展

  • ApplicationContextInitializer

    Spring 容器在Environment创建成功后会在prepareContext方法内调用ApplicationContextInitializer#initialize

  • SmartInitializingSingleton

    调用时间:所有的单例Bean都已经实例化,但是还没有初始化

    使用场景:同样适用于自定义注解的处理,只不过处理的时机相对于InstantiationAwareBeanPostProcessor有了推迟。

  • SpringApplicationRunListener

    Spring IOC容器启动过程中的监听,主要方法

    • starting
    • environmentPrepared
    • contextPrepared
    • contextLoaded
    • started
    • running
    • failed

    如果熟悉Spring容器启动流程的话,就了解每个方法的含义。

ApplicationEvent和ApplicationListener

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

/**
* Handle an application event.
* @param event the event to respond to
*/
void onApplicationEvent(E event);

}

相对于SpringApplicationRunListener来说,ApplicationListener具有更强的扩展性,并且Spring内置了很多的Event类,通过这些Event,我们可以更好的监听IOC容器的启动过程,如果要自定义ApplicationEvent来扩展,可以通过实现ApplicationEventMulticaster接口管理来ApplicationListener和主动广播Event来实现。

下面是Spring Boot扩展的的ApplicationEvent

  • ApplicationEnvironmentPreparedEvent
  • ApplicationEnvironmentPreparedEvent
  • ApplicationPreparedEvent
  • ApplicationPreparedEvent
  • ApplicationStartedEvent
  • ApplicationStartedEvent
  • ApplicationReadyEvent
  • ApplicationReadyEvent
  • ApplicationFailedEvent
  • ApplicationFailedEvent
  • ApplicationStartingEvent
  • ApplicationStartingEvent
  • ContextRefreshedEvent

Spring Context的ApplicationEvent

  • ContextClosedEvent
  • ContextRefreshedEvent
  • ContextStoppedEvent
  • ContextStartedEvent

@EventListener

当然Spring 4.2版本后提供了一种更为简单的扩展方式,我们可以在作为EventListener来处理事件的方法上使用@EventListener注解,方法的参数通常代表了Event类,可以是一个任意的类型,然后通过注入ApplicationEventPublisher来发布对应的Event,

  /**
     * Notify all <strong>matching</strong> listeners registered with this
     * application of an application event. Events may be framework events
     * (such as RequestHandledEvent) or application-specific events.
     * @param event the event to publish
     * @see org.springframework.web.context.support.RequestHandledEvent
     */
    default void publishEvent(ApplicationEvent event) {
      publishEvent((Object) event);
    }

    /**
     * Notify all <strong>matching</strong> listeners registered with this
     * application of an event.
     * <p>If the specified {@code event} is not an {@link ApplicationEvent},
     * it is wrapped in a {@link PayloadApplicationEvent}.
     * @param event the event to publish
     * @since 4.2
     * @see PayloadApplicationEvent
     */
    void publishEvent(Object event);

通过API可以看到,在Spring 4.2后支持任意类型的Event发布,而不需要实现ApplicationEvent接口

自动配置

  • spring.factories

    Spring Boot开箱即用的特性,是通过大量AutoXXXConfiguration类来完成一些Configuration Metadata的自动配置,而实现和扩展auto-configuration的入口便是spring.factories文件。

    那么,如果我们要提供一些spring-boot-starter包,并且完成自定义Configuration的auto-configuration,只需要在jar包内的META-INF/spring.factories 文件中增加 org.springframework.boot.autoconfigure.EnableAutoConfiguration=CustomClass就可以实现自动配置。官方文档也给出了更为详细的说明。

  • 动态Import

    ​ 除了通过AutoXXXConfiguration类来完成自动配置,我们也经常在Spring boot中看到@EnableXXX这样的注解,这些类提供了对应特性的手动开启,而底层就是在对应的@EnableXXX注解上添加@Import实现,通过@Import来实现对应Configuration类的导入。

    一种用法是直接通过@Import指定要加载的类。

    另外一种常用方法是通过在@Enable*注解中指定Import对应特定的类,并通过注解的Metadata来让用户指定对应的参数,这样就可以更加灵活,例如Spring中的@EnableAsync和@EnableCaching。那么,如果需要根据注解的Metadata来动态加载Bean,通常来说需要用到

    • ImportSelector

    java /** * Select and return the names of which class(es) should be imported based on * the {@link AnnotationMetadata} of the importing @{@link Configuration} class. */ String[] selectImports(AnnotationMetadata importingClassMetadata); selectImports方法返回哪些类需要被加载

    • ImportBeanDefinitionRegistrar

    java /** * Register bean definitions as necessary based on the given annotation metadata of * the importing {@code @Configuration} class. * <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be * registered here, due to lifecycle constraints related to {@code @Configuration} * class processing. * @param importingClassMetadata annotation metadata of the importing class * @param registry current bean definition registry */ public void registerBeanDefinitions( AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry); 通过AnnotationMetadata参数可以获取@EnableXXX注解的一些配置信息,然后通过BeanDefinitionRegistry修改BeanDefinition,比如设置Bean的某些Field的值。

工具类

  • ReflectionUtils

    反射工具类

  • Assert

    断言工具类,提供一些常用的isTrue,isEmpty之类的断言判断方法

  • StopWatch

    类似于guava中的Stopwatch类,用于记录执行耗时

  • AnnotationUtils和AnnotatedElementUtils

    区别在于前者不支持annotation attribute overridesSpring注解模型annotation attribute overrides有详细说明

  • MethodIntrospector

    方法内省,ReflectionUtils提供了类级别的一些基本的方法,而MethodIntrospector只针对方法,内置的多个selectMethods可以对包含特性信息的方法进行查找,例如@EventListener的注解处理类EventListenerMethodProcessor就是通过这个工具来查找包含了@EventListener注解的方法。

  • BeanWrapper和BeanWrapperImpl

    Bean操作类,相对于apache commons的BeanMap类,BeanWrapper支持对嵌套属性的访问和修改,以及对Map,List特定元素的修改。

如同Spring 设计哲学中提到的Provide choice at every level一样,Spring在设计过程中通过极强的扩展性和良好的API,我们可以即使在系统已经完成设计后,通过不同的扩展接口来无侵入对系统进行扩展和改造,并且Spring优秀的接口和类结构设计也为Spring生态的持续健康发展提供了保证,我想这也是我在API设计时应该学习和借鉴的。

扩展阅读