Spring的启动扩展点机制详解

在Java的世界中,我们知道Spring是当下最主流的开发框架,没有之一。而在使用Dubbo、Mybatis等开源框架时,我们发现可以采用和Spring完全一样的使用方式来使用它们。

可能你在平时的使用过程中并没有意识到这一点,但仔细想一想,你会觉得这是一件比较神奇的事情。本来就是不同的框架,怎么能够无缝的集成在一起呢?这就是今天我们要讨论的话题,即Spring为我们内置了一组功能非常强大的启动扩展点。通过这些启动扩展点,可以实现我们想要的集成效果。

系统初始化

我们先来看两个非常常见的Spring启动扩展点InitializingBean和DisposableBean。在Spring中,这两个扩展点分别作用于Bean的初始化和销毁阶段,开发人员可以通过他们实现一些定制化的处理逻辑。

顾名思义,InitializingBean应该是用于初始化Bean,该接口定义如下。

public interface InitializingBean {

void afterPropertiesSet() throws Exception;

}

我们看到InitializingBean接口只有一个方法,即afterPropertiesSet。从命名上看,这个方法应该作用于属性被设置之后。也就是说,该方法的初始化会晚于属性的初始化。

实际上,InitializingBean只是Spring初始化时可以采用的其中一个扩展点。与InitializingBean类似的一种机制是InitMethod。我们知道在Spring中可以配置Bean的init-method属性,具体使用方式是这样的。

<bean class="com.xiaoyiran.springinitialization. TestInitBean" init-method="initMethod"></bean>

这两种Spring初始化扩展机制都非常常见,我们在阅读Dubbo、Mybatis、Spring Cloud等框架源码时会经常遇到。那么,这里就有一个问题,既然它们都能对初始化过程做一定的控制,执行顺序是怎么样的呢?我们通过一个示例来分析各个机制的执行顺序,示例如代码如下所示。

public class TestInitBean implements InitializingBean {

public TestInitBean (){

System.out.println("constructMethod");

}

@Override

public void afterPropertiesSet() throws Exception {

System.out.println("afterPropertiesSet");

}

public void initMethod() {

System.out.println("initMethod");

}

}

上述示例的执行结果如下所示。

constructMethod

afterPropertiesSet

initMethod

显然,基于以上结果,我们可以得出这三者的生效先后顺序。

结论已经有了,我们简单对这个结论做源码分析。在Spring中,我们找到AbstractAutowireCapableBeanFactory的initializeBean方法,这个方法完成了这里提到的相关操作。在表现形式,我们对该方法上做一些简化,可以得到如下所示的代码结构。

protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {

//执行Aware方法

invokeAwareMethods(beanName, bean);

Object wrappedBean = bean;

//在初始化之前执行PostProcessor方法

wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);

//执行初始化方法

invokeInitMethods(beanName, wrappedBean, mbd);

//在初始化之后执行PostProcessor方法

wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);

return wrappedBean;

}

这段代码的执行流程如下图所示。

我们来看这里的invokeInitMethods方法。从命名上看,该方法的作用是调用一批初始化方法,我们继续对这个方法的代码结构做一些简化调整以便容易理解,如下所示。

protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd) throws Throwable {

boolean isInitializingBean = (bean instanceof InitializingBean);

//判断是否实现InitializingBean接口

if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {

//直接调用afterPropertiesSet方法

((InitializingBean) bean).afterPropertiesSet();

}

if (mbd != null) {

String initMethodName = mbd.getInitMethodName();

if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&!mbd.isExternallyManagedInitMethod(initMethodName)) {

//执行自定义的init-method

invokeCustomInitMethod(beanName, bean, mbd);

}

}

}

可以看到,这里首先判断当前Bean是否是一个InitializingBean接口的实例,如果是就直接调用它的afterPropertiesSet方法。然后我们根据Bean的定义获取它的init-method属性,如果设置了该属性,那么就调用一个invokeCustomInitMethod方法。该方法会找到init-method属性并执行指定的方法。因为在代码执行流程上的前后顺序,决定了afterPropertiesSet方法是在init-method之前被触发。

Aware机制

我们在前面的执行流程图中还看到了一个invokeAwareMethods方法。这个invokeAwareMethods就涉及到接下来要介绍的Spring中所提供的Aware系列扩展机制。

在Spring中,Aware接口是一个空接口,但却有一大批直接或间接的子接口。比方说,以常见的ApplicationContextAware接口为例,定义如下所示。

public interface ApplicationContextAware extends Aware {

void setApplicationContext(ApplicationContext applicationContext) throws BeansException;

}

ApplicationContextAware的使用方法也非常简单,我们可以直接使用它所提供的setApplicationContext方法把传入的ApplicationContext暂存起来使用。通过这种方法,我们就可以获取上下文对象ApplicationContext。一旦获取了ApplicationContext,那么就可以对Spring中所有的Bean进行操作了。

事实上, 各种Aware接口中都只有一个类似setApplicationContext的set方法。如果一个Bean想要获取并使用Spring容器中的相关对象,我们就不需要再次执行重复的启动过程,而是可以通过Aware接口所提供的这些方法直接引入相关对象即可。

Dubbo基于启动扩展点集成Spring原理分析

了解了Spring内置的系统初始化方法和Aware机制,我们将基于具体的开源框架分析如何与Spring完成启动过程的无缝集成。在今天的内容中,我们讨论的对象是非常经典的分布式服务框架Dubbo。

Dubbo服务器端启动过程分析

在Dubbo中,负责执行服务器端启动的是ServiceBean。我们先来看ServiceBean这个Bean的类定义以及主体代码结构。

public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, BeanNameAware {

public void afterPropertiesSet() {}

...

public void setApplicationContext(ApplicationContext applicationContext) {}

...

public void setBeanName(String name) {}

...

public void onApplicationEvent(ApplicationEvent event) {}

...

public void destroy() {}

}

可以看到ServiceBean实现了Spring的InitializingBean、DisposableBean ApplicationContextAware和ApplicationListener等接口,重写了afterPropertiesSet、 destroy、setApplicationContext、onApplicationEvent等方法。这些方法就是Dubbo和Spring整合的关键,我们在自己实现与Spring框架的集成时也通常会使用到这些方法。

我们首先关注ServiceBean 中实现InitializingBean接口的afterPropertiesSet方法,这个方法非常长,但结构并不复杂,我们来展示该方法的结构,如下所示。

public void afterPropertiesSet(){

getProvider();

getApplication()

getModule();

getRegistries();

getMonitor();

getProtocols();

getPath();

if (!isDelay()) {

export();

}

}

这个代码结构中的getProvider、getApplication、getModule、getMonitor等方法的执行逻辑和流程基本一致。以getProvider方法为例,Dubbo首先会从配置文件中读取<dubbo:provide>配置项。显然,Provider、Application和Module在Dubbo中应该只能出现一次。通过执行上述的afterPropertiesSet方法,相当于在Dubbo框架启动的同时,执行了Spring容器的初始化过程,并把这里所获取的一组Dubbo对象加载到了Spring容器中。

而因为ServiceBean也实现了Spring的ApplicationContextAware接口,所以我们不难想象存在如下所示的setApplicationContext方法。

@Override

public void setApplicationContext(ApplicationContext applicationContext) {

this.applicationContext = applicationContext; SpringExtensionFactory.addApplicationContext(applicationContext);

}

可以看到,Dubbo通过该方法获取了ApplicationContext,然后通过自己的SpringExtensionFactory工厂类将上下文对象保存到了框架内部,以便后续进行使用。

Dubbo客户器端启动过程分析

有了Dubbo服务器端的理解基础,我们再看Dubbo的客户端就会变得比较简单明了。Dubbo的客户端启动方法需要参考ReferenceBean类,如下所示。

public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean {

}

可以看到,ReferenceBean实现了Spring提供的FactoryBean、ApplicationContextAware、InitializingBean和DisposableBean这四个扩展点。这次,我们先挑最简单的进行介绍。FactoryBean和ApplicationContextAware接口实现非常简单,setApplicationContext方法只是把传入的applicationContext同时保存在ReferenceBean内部以及SpringExtensionFactory中。

接下来,我们关注InitializingBean接口的afterPropertiesSet方法,这个方法同样非常长,但结构也不复杂,而且与ServiceBean中的afterPropertiesSet方法结构比较对称。这里,我们不再给出该方法的主体代码结构,而是直接来到该方法的末端,试图找到该方法中最核心的代码。显然,如下所示的就是最核心的代码。

getObject();

这个getObject方法实际上并不是ReferenceBean自身的代码,而是实现了FactoryBean接口中的同名方法。这里的FactoryBean接口前面没有介绍过,它的定义如下所示。

public interface FactoryBean<T> {

T getObject() throws Exception;

Class<?> getObjectType();

boolean isSingleton();

}

实际上,FactoryBean是Spring框架中非常核心的一个接口,负责从容器中获取具体的Bean对象。我们重点来看ReferenceBean中的getObject方法,该方法又调用了ReferenceBean的父类ReferenceConfig中的get方法,如下所示。

public synchronized T get() {

if (destroyed) {

throw new IllegalStateException("Already destroyed!");

}

if (ref == null) {

init();

}

return ref;

}

显然,这里的核心应该是init方法。这个init方法与ServiceConfig中的export方法一样,做了非常多的准备和校验工作,最终来到了如下所示的这行代码。

ref = createProxy(map);

顾名思义,createProxy方法用来创建代理对象;通过代理对象,客户端访问远程服务就像在调用本地方法一样。至此,Dubbo客户端启动过程也介绍完毕。

总结

作为系统启动和初始化相关的常见扩展点,本文中介绍InitializingBean接口和Aware系列接口可以说应用非常广泛。我们会发现主流的Dubbo、Mybatis等框架都是基于这些扩展性接口完成了与Spring框架的整合。如果我们需要实现与Spring框架的集成和扩展,这些接口是必定需要掌握的内容,建议在日常开发过程中多关注这些接口的应用场景和方式,并视情况集成到自己的代码当中。

相关推荐
Lyqfor4 分钟前
云原生学习
java·分布式·学习·阿里云·云原生
WangYaolove13145 分钟前
请解释Python中的装饰器是什么?如何使用它们?
linux·数据库·python
我是黄大仙26 分钟前
利用飞书多维表格自动发布版本
运维·服务器·数据库·飞书
曾经的三心草26 分钟前
Mysql之约束与事件
android·数据库·mysql·事件·约束
程序猿麦小七28 分钟前
今天给在家介绍一篇基于jsp的旅游网站设计与实现
java·源码·旅游·景区·酒店
WuMingf_33 分钟前
redis
数据库·redis
张某布响丸辣41 分钟前
SQL中的时间类型:深入解析与应用
java·数据库·sql·mysql·oracle
喜欢打篮球的普通人1 小时前
rust模式和匹配
java·算法·rust
java小吕布1 小时前
Java中的排序算法:探索与比较
java·后端·算法·排序算法