在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框架的集成和扩展,这些接口是必定需要掌握的内容,建议在日常开发过程中多关注这些接口的应用场景和方式,并视情况集成到自己的代码当中。