33

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

 4 years ago
source link: https://www.tuicool.com/articles/6N7FBrY
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

前言

对于每个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加载和实例化过程中都包含哪些过程

spring-bean-lifecycle.jpg

通常来说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, ClassbeanType, String beanName); ```

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

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

    image-20190827102626847.png

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设计时应该学习和借鉴的。

扩展阅读


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK