diff --git a/Middleware.md b/Middleware.md index d7e331c..1b59e3e 100644 --- a/Middleware.md +++ b/Middleware.md @@ -5851,6 +5851,374 @@ MethodInterceptor是AOP项目中的拦截器,它拦截的目标是方法,即 # SpringBoot +## 启动流程扩展点 + +![Spring启动扩展点流程](images/Middleware/Spring启动扩展点流程.png) + +### ApplicationContextInitializer + +整个spring容器在刷新之前初始化`ConfigurableApplicationContext`的回调接口,简单来说,就是在容器刷新之前调用此类的`initialize`方法。用户可以在整个spring容器还没被初始化之前做一些事情。 + +```java +public class TestApplicationContextInitializer implements ApplicationContextInitializer { + @Override + public void initialize(ConfigurableApplicationContext applicationContext) { + System.out.println("[ApplicationContextInitializer]"); + } +} +``` + +因为这时候spring容器还没被初始化,所以想要自己的扩展的生效,有以下三种方式: + +- 在启动类中用`springApplication.addInitializers(new TestApplicationContextInitializer())`语句加入 +- 配置文件配置`context.initializer.classes=com.example.demo.TestApplicationContextInitializer` +- Spring SPI扩展,在spring.factories中加入`org.springframework.context.ApplicationContextInitializer=com.example.demo.TestApplicationContextInitializer` + + + +### BeanDefinitionRegistryPostProcessor + +在读取项目中的`beanDefinition`之后执行,提供一个补充的扩展点。使用场景是你可以在这里动态注册自己的`beanDefinition`,可以加载classpath之外的bean。 + +```java +public class TestBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor { + @Override + public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { + System.out.println("[BeanDefinitionRegistryPostProcessor] postProcessBeanDefinitionRegistry"); + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + System.out.println("[BeanDefinitionRegistryPostProcessor] postProcessBeanFactory"); + } +} +``` + + + +### BeanFactoryPostProcessor + +是`beanFactory`的扩展接口,调用时机在spring在读取`beanDefinition`信息之后,实例化bean之前。在这个时机,用户可以通过实现这个扩展接口来自行处理一些东西,比如修改已经注册的`beanDefinition`的元信息。 + +```java +public class TestBeanFactoryPostProcessor implements BeanFactoryPostProcessor { + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + System.out.println("[BeanFactoryPostProcessor]"); + } +} +``` + + + +### InstantiationAwareBeanPostProcessor + +该接口继承了`BeanPostProcess`接口,区别如下: + +**`BeanPostProcess`接口只在bean的初始化阶段进行扩展(注入spring上下文前后),而`InstantiationAwareBeanPostProcessor`接口在此基础上增加了3个方法,把可扩展的范围增加了实例化阶段和属性注入阶段。** + +该类主要的扩展点有以下5个方法,主要在bean生命周期的两大阶段:**实例化阶段** 和**初始化阶段** ,下面一起进行说明,按调用顺序为: + +- `postProcessBeforeInstantiation`:实例化bean之前,相当于new这个bean之前 +- `postProcessAfterInstantiation`:实例化bean之后,相当于new这个bean之后 +- `postProcessPropertyValues`:bean已经实例化完成,在属性注入时阶段触发,`@Autowired`,`@Resource`等注解原理基于此方法实现 +- `postProcessBeforeInitialization`:初始化bean之前,相当于把bean注入spring上下文之前 +- `postProcessAfterInitialization`:初始化bean之后,相当于把bean注入spring上下文之后 + +使用场景:这个扩展点非常有用 ,无论是写中间件和业务中,都能利用这个特性。比如对实现了某一类接口的bean在各个生命期间进行收集,或者对某个类型的bean进行统一的设值等等。 + +```java +public class TestInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor { + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + System.out.println("[TestInstantiationAwareBeanPostProcessor] before initialization " + beanName); + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + System.out.println("[TestInstantiationAwareBeanPostProcessor] after initialization " + beanName); + return bean; + } + + @Override + public Object postProcessBeforeInstantiation(Class beanClass, String beanName) throws BeansException { + System.out.println("[TestInstantiationAwareBeanPostProcessor] before instantiation " + beanName); + return null; + } + + @Override + public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException { + System.out.println("[TestInstantiationAwareBeanPostProcessor] after instantiation " + beanName); + return true; + } + + @Override + public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException { + System.out.println("[TestInstantiationAwareBeanPostProcessor] postProcessPropertyValues " + beanName); + return pvs; + } +} +``` + + + + + +### SmartInstantiationAwareBeanPostProcessor + +该扩展接口有3个触发点方法: + +- `predictBeanType`: + 该触发点发生在`postProcessBeforeInstantiation`之前(在图上并没有标明,因为一般不太需要扩展这个点),这个方法用于预测Bean的类型,返回第一个预测成功的Class类型,如果不能预测返回null;当你调用`BeanFactory.getType(name)`时当通过bean的名字无法得到bean类型信息时就调用该回调方法来决定类型信息。 +- `determineCandidateConstructors`: + 该触发点发生在`postProcessBeforeInstantiation`之后,用于确定该bean的构造函数之用,返回的是该bean的所有构造函数列表。用户可以扩展这个点,来自定义选择相应的构造器来实例化这个bean。 +- `getEarlyBeanReference`: + 该触发点发生在`postProcessAfterInstantiation`之后,当有循环依赖的场景,当bean实例化好之后,为了防止有循环依赖,会提前暴露回调方法,用于bean实例化的后置处理。这个方法就是在提前暴露的回调方法中触发。 + +```java +public class TestSmartInstantiationAwareBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor { + + @Override + public Class predictBeanType(Class beanClass, String beanName) throws BeansException { + System.out.println("[TestSmartInstantiationAwareBeanPostProcessor] predictBeanType " + beanName); + return beanClass; + } + + @Override + public Constructor[] determineCandidateConstructors(Class beanClass, String beanName) throws BeansException { + System.out.println("[TestSmartInstantiationAwareBeanPostProcessor] determineCandidateConstructors " + beanName); + return null; + } + + @Override + public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException { + System.out.println("[TestSmartInstantiationAwareBeanPostProcessor] getEarlyBeanReference " + beanName); + return bean; + } +} +``` + + + +### BeanFactoryAware + +这个类只有一个触发点,发生在bean的实例化之后,注入属性之前,也就是Setter之前。这个类的扩展点方法为`setBeanFactory`,可以拿到`BeanFactory`这个属性。 + +使用场景为,你可以在bean实例化之后,但还未初始化之前,拿到 `BeanFactory`,在这个时候,可以对每个bean作特殊化的定制。也或者可以把`BeanFactory`拿到进行缓存,日后使用。 + +```java +public class TestBeanFactoryAware implements BeanFactoryAware { + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + System.out.println("[TestBeanFactoryAware] " + beanFactory.getBean(TestBeanFactoryAware.class).getClass().getSimpleName()); + } +} +``` + + + +### ApplicationContextAwareProcessor + +该类本身并没有扩展点,但是该类内部却有6个扩展点可供实现 ,这些类触发的时机在bean实例化之后,初始化之前。该类用于执行各种驱动接口,在bean实例化之后,属性填充之后,通过执行以上红框标出的扩展接口,来获取对应容器的变量。**所以这里应该来说是有6个扩展点**: + +- `EnvironmentAware`: + 用于获取`EnviromentAware`的一个扩展类,这个变量非常有用, 可以获得系统内的所有参数。当然个人认为这个Aware没必要去扩展,因为spring内部都可以通过注入的方式来直接获得。 +- `EmbeddedValueResolverAware`: + 用于获取`StringValueResolver`的一个扩展类, `StringValueResolver`用于获取基于`String`类型的properties的变量,一般我们都用`@Value`的方式去获取,如果实现了这个Aware接口,把`StringValueResolver`缓存起来,通过这个类去获取`String`类型的变量,效果是一样的。 +- `ResourceLoaderAware`: + 用于获取`ResourceLoader`的一个扩展类,`ResourceLoader`可以用于获取classpath内所有的资源对象,可以扩展此类来拿到`ResourceLoader`对象。 +- `ApplicationEventPublisherAware`: + 用于获取`ApplicationEventPublisher`的一个扩展类,`ApplicationEventPublisher`可以用来发布事件,结合`ApplicationListener`来共同使用,下文在介绍`ApplicationListener`时会详细提到。这个对象也可以通过spring注入的方式来获得。 +- `MessageSourceAware`: + 用于获取`MessageSource`的一个扩展类,`MessageSource`主要用来做国际化。 +- `ApplicationContextAware`: + 用来获取`ApplicationContext`的一个扩展类,`ApplicationContext`应该是很多人非常熟悉的一个类了,就是spring上下文管理器,可以手动的获取任何在spring上下文注册的bean,我们经常扩展这个接口来缓存spring上下文,包装成静态方法。同时`ApplicationContext`也实现了`BeanFactory`,`MessageSource`,`ApplicationEventPublisher`等接口,也可以用来做相关接口的事情。 + +```java +class ApplicationContextAwareProcessor implements BeanPostProcessor { + private void invokeAwareInterfaces(Object bean) { + if (bean instanceof EnvironmentAware) { + ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment()); + } + if (bean instanceof EmbeddedValueResolverAware) { + ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver); + } + if (bean instanceof ResourceLoaderAware) { + ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext); + } + if (bean instanceof ApplicationEventPublisherAware) { + ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext); + } + if (bean instanceof MessageSourceAware) { + ((MessageSourceAware) bean).setMessageSource(this.applicationContext); + } + if (bean instanceof ApplicationContextAware) { + ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext); + } + } +} +``` + + + +### BeanNameAware + +这个类也是Aware扩展的一种,触发点在bean的初始化之前,也就是`postProcessBeforeInitialization`之前,这个类的触发点方法只有一个:`setBeanName` + +使用场景为:用户可以扩展这个点,在初始化bean之前拿到spring容器中注册的的beanName,来自行修改这个beanName的值。 + +```java +public class NormalBeanA implements BeanNameAware{ + public NormalBeanA() { + System.out.println("NormalBean constructor"); + } + + @Override + public void setBeanName(String name) { + System.out.println("[BeanNameAware] " + name); + } +} +``` + + + +### @PostConstruct + +这个并不算一个扩展点,其实就是一个标注。其作用是在bean的初始化阶段,如果对一个方法标注了`@PostConstruct`,会先调用这个方法。这里重点是要关注下这个标准的触发点,这个触发点是在`postProcessBeforeInitialization`之后,`InitializingBean.afterPropertiesSet`之前。使用场景:用户可以对某一方法进行标注,来进行初始化某一个属性。 + +```java +public class NormalBeanA { + public NormalBeanA() { + System.out.println("NormalBean constructor"); + } + + @PostConstruct + public void init(){ + System.out.println("[PostConstruct] NormalBeanA"); + } +} +``` + + + +### InitializingBean + +这个类,顾名思义,也是用来初始化bean的。`InitializingBean`接口为bean提供了初始化方法的方式,它只包括`afterPropertiesSet`方法,凡是继承该接口的类,在初始化bean的时候都会执行该方法。这个扩展点的触发时机在`postProcessAfterInitialization`之前。使用场景:用户实现此接口,来进行系统启动的时候一些业务指标的初始化工作。 + +```java +public class NormalBeanA implements InitializingBean{ + @Override + public void afterPropertiesSet() throws Exception { + System.out.println("[InitializingBean] NormalBeanA"); + } +} +``` + + + + + +### FactoryBean + +一般情况下,Spring通过反射机制利用bean的class属性指定支线类去实例化bean,在某些情况下,实例化Bean过程比较复杂,如果按照传统的方式,则需要在bean中提供大量的配置信息。配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。Spring为此提供了一个`org.springframework.bean.factory.FactoryBean`的工厂类接口,用户可以通过实现该接口定制实例化Bean的逻辑。`FactoryBean`接口对于Spring框架来说占用重要的地位,Spring自身就提供了70多个`FactoryBean`的实现。它们隐藏了实例化一些复杂bean的细节,给上层应用带来了便利。从Spring3.0开始,`FactoryBean`开始支持泛型,即接口声明改为`FactoryBean`的形式。 + +使用场景:用户可以扩展这个类,来为要实例化的bean作一个代理,比如为该对象的所有的方法作一个拦截,在调用前后输出一行log,模仿`ProxyFactoryBean`的功能。 + +```java +public class TestFactoryBean implements FactoryBean { + + @Override + public TestFactoryBean.TestFactoryInnerBean getObject() throws Exception { + System.out.println("[FactoryBean] getObject"); + return new TestFactoryBean.TestFactoryInnerBean(); + } + + @Override + public Class getObjectType() { + return TestFactoryBean.TestFactoryInnerBean.class; + } + + @Override + public boolean isSingleton() { + return true; + } + + public static class TestFactoryInnerBean{ + + } +} +``` + + + +### SmartInitializingSingleton + +这个接口中只有一个方法`afterSingletonsInstantiated`,其作用是是 在spring容器管理的所有单例对象(非懒加载对象)初始化完成之后调用的回调接口。其触发时机为`postProcessAfterInitialization`之后。 + +使用场景:用户可以扩展此接口在对所有单例对象初始化完毕后,做一些后置的业务处理。 + +```java +public class TestSmartInitializingSingleton implements SmartInitializingSingleton { + @Override + public void afterSingletonsInstantiated() { + System.out.println("[TestSmartInitializingSingleton]"); + } +} +``` + + + +### CommandLineRunner + +这个接口也只有一个方法:`run(String... args)`,触发时机为整个项目启动完毕后,自动执行。如果有多个`CommandLineRunner`,可以利用`@Order`来进行排序。使用场景:用户扩展此接口,进行启动项目之后一些业务的预处理。 + +```java +public class TestCommandLineRunner implements CommandLineRunner { + + @Override + public void run(String... args) throws Exception { + System.out.println("[TestCommandLineRunner]"); + } +} +``` + + + +### DisposableBean + +这个扩展点也只有一个方法:`destroy()`,其触发时机为当此对象销毁时,会自动执行这个方法。比如说运行`applicationContext.registerShutdownHook`时,就会触发这个方法。 + +```java +public class NormalBeanA implements DisposableBean { + @Override + public void destroy() throws Exception { + System.out.println("[DisposableBean] NormalBeanA"); + } +} +``` + + + +### ApplicationListener + +准确的说,这个应该不算spring&springboot当中的一个扩展点,`ApplicationListener`可以监听某个事件的`event`,触发时机可以穿插在业务方法执行过程中,用户可以自定义某个业务事件。但是spring内部也有一些内置事件,这种事件,可以穿插在启动调用中。我们也可以利用这个特性,来自己做一些内置事件的监听器来达到和前面一些触发点大致相同的事情。 + +接下来罗列下spring主要的内置事件: + +- ContextRefreshedEvent + ApplicationContext 被初始化或刷新时,该事件被发布。这也可以在`ConfigurableApplicationContext`接口中使用 `refresh()`方法来发生。此处的初始化是指:所有的Bean被成功装载,后处理Bean被检测并激活,所有Singleton Bean 被预实例化,`ApplicationContext`容器已就绪可用。 +- ContextStartedEvent + 当使用 `ConfigurableApplicationContext` (ApplicationContext子接口)接口中的 start() 方法启动 `ApplicationContext`时,该事件被发布。你可以调查你的数据库,或者你可以在接受到这个事件后重启任何停止的应用程序。 +- ContextStoppedEvent + 当使用 `ConfigurableApplicationContext`接口中的 `stop()`停止`ApplicationContext` 时,发布这个事件。你可以在接受到这个事件后做必要的清理的工作 +- ContextClosedEvent + 当使用 `ConfigurableApplicationContext`接口中的 `close()`方法关闭 `ApplicationContext` 时,该事件被发布。一个已关闭的上下文到达生命周期末端;它不能被刷新或重启 +- RequestHandledEvent + 这是一个 web-specific 事件,告诉所有 bean HTTP 请求已经被服务。只能应用于使用DispatcherServlet的Web应用。在使用Spring作为前端的MVC控制器时,当Spring处理用户请求结束后,系统会自动触发该事件 + + + ## MVC ### HTTP请求处理流程 diff --git a/images/Middleware/Spring启动扩展点流程.png b/images/Middleware/Spring启动扩展点流程.png new file mode 100644 index 0000000..ef571c6 Binary files /dev/null and b/images/Middleware/Spring启动扩展点流程.png differ diff --git a/support/Spring启动扩展点流程.drawio b/support/Spring启动扩展点流程.drawio new file mode 100644 index 0000000..796d60f --- /dev/null +++ b/support/Spring启动扩展点流程.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file