对于每个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框架做扩展,或者集成一些基础中间件,这些特性和扩展点成为了不可或缺的知识。
首先必须先了解Bean加载和实例化过程中都包含哪些过程
通常来说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)是两个不一样的阶段
Bean Instantiation作为Bean生命周期的第一个阶段,这个接口提供Bean实例化过程的前置处理方法postProcessBeforeInitialization和后置处理方法postProcessAfterInitialization。通常来说,如果要扩展此接口,继承InstantiationAwareBeanPostProcessorAdapter是更好的选择。例如在Spring源码中,AutowiredAnnotationBeanPostProcessor就是作为InstantiationAwareBeanPostProcessorAdapter子类,实现了@Autowire和@Value注解的处理,同样,Nacos @NacosValue注解处理也采用了类似的方式。
另外一个子类是CommonAnnotationBeanPostProcessor,这个类用来处理 JSR-250 注解,例如我们常用的@Resource,和后面会提到的@PostConstruct,@PreDestroy。
那么如果我们要实现自定义注解的处理,可以采用类似的方式,通过实现InstantiationAwareBeanPostProcessor接口或者继承InstantiationAwareBeanPostProcessorAdapter类来完成。
正如接口名一样, 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接口提供对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 beanName the name of the bean
**/ void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, ClassbeanType, String beanName); ```
调用时间:Bean实例化后,PopulateBean前
使用场景:通过此方法可以获取到BeanDefinition信息,那么就可以Bean初始化前对BeanDefinition进行检查,或者动态修改BeanDefinition数据,但是在Spring框架中大部分用于对BeanDefinition的读取而不是变更
分别对应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;
ApplicationContextInitializer
Spring 容器在Environment创建成功后会在prepareContext方法内调用ApplicationContextInitializer#initialize
SmartInitializingSingleton
调用时间:所有的单例Bean都已经实例化,但是还没有初始化
使用场景:同样适用于自定义注解的处理,只不过处理的时机相对于InstantiationAwareBeanPostProcessor有了推迟。
SpringApplicationRunListener
Spring IOC容器启动过程中的监听,主要方法
如果熟悉Spring容器启动流程的话,就了解每个方法的含义。
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
Spring Context的ApplicationEvent
当然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,通常来说需要用到
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方法返回哪些类需要被加载
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 overrides , Spring注解模型 对 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设计时应该学习和借鉴的。
Spring Framework Documentation
Spring开闭原则的表现-BeanPostProcessor扩展点-2
Spring Boot 自动配置及 Factories 机制总结