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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/357135.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

拦截器Interceptor

概念&#xff1a;是一种动态拦截方法调用的机制&#xff0c;类似于过滤器。Spring框架中提供的&#xff0c;用来动态拦截方法的执行。 作用&#xff1a;拦截请求&#xff0c;在指定的方法调用前后&#xff0c;根据业务需要执行预先设定的代码。

STM32单片机BKP备份寄存器和RTC实时时钟详解

文章目录 1. Unix时间戳 2. UTC/GMT 3. 时间戳转换 4. BKP简介 5. BKP基本结构 6. RTC简介 7. RTC框架图 8. RTC基本结构 9. 代码示例 1. Unix时间戳 实时时钟&#xff0c;本质上是一个定时器&#xff0c;专门用来产生年月日时分秒。 Unix 时间戳&#xff08;Unix T…

判断题无答案22届期末复习

判断: 1-3.结构体变量不能进行整体输入输出。 1-4.不同类型的结构变量之间也可以直接赋值。 1-5假设结构指针p已定义并正确赋值,其指向的结构变量有一个成员是int型的num,则语句 (*p).num = 100; 等价于p->num=1…

【Arthas案例】应用包含两个相同全限定类名StaticLoggerBinder,引起log4j.Level类找不到异常

3分钟内解决问题 两个不同的GAV依赖冲突&#xff0c;包含相同全限定类名&#xff0c;引起ClassNotFoundException Maven依赖的三坐标体系GAV(G-groupId&#xff0c;A-artifactId&#xff0c;V-version) 【案例1】某应用依赖两个GAV不同的jar&#xff0c;但包含两个相同全限定类…

Java | Leetcode Java题解之第179题最大数

题目&#xff1a; 题解&#xff1a; class Solution {public String largestNumber(int[] nums) {int n nums.length;// 转换成包装类型&#xff0c;以便传入 Comparator 对象&#xff08;此处为 lambda 表达式&#xff09;Integer[] numsArr new Integer[n];for (int i 0;…

css-vxe列表中ant进度条与百分比

1.vxe列表 ant进度条 <vxe-column field"actualProgress" title"进度" align"center" width"200"><template #default"{ row }"><a-progress:percent"Math.floor(row.actualProgress)"size"s…

Android面试题之动画+事件处理篇

1、Android 中的动画有哪几类 帧动画、补间动画、属性动画 2、动画能组合在一起使用么&#xff1f; 可以将动画组合在一起使用AnimatorSet&#xff0c; AnimatorSet.play() 播放当前动画的同时可以 .with() &#xff1a;将现有动画和传入的动画同时执行 .after() &#xff1a…

高考填报志愿选专业,要善于发掘自身优势

每年的高考季&#xff0c;如何填报志愿又再成为困扰家长以及学生的难题&#xff0c;可能在面对大量的专业时&#xff0c;无论是考生还是家长都不知道应该如何选择&#xff0c;好的专业孩子不一定有优势&#xff0c;感兴趣的冷门专业又担心日后找工作难。 实际上&#xff0c;专业…

基于matlab的BP神经网络分类预测

1.神经网络结构 本文网络结构如图1所示&#xff1a; 图1 网络结构 图1给出的并不是单纯的bp神经网络结构这里设置了三个隐藏层&#xff0c;神经元个数分别为6&#xff0c;3&#xff0c;3&#xff0c;输入层12个特征输入&#xff0c;输出层输出4个类型结果。 2.代码 %% 清空环…

Android 你应该知道的学习资源 进阶之路贵在坚持

coderzheaven 覆盖各种教程&#xff0c;关于Android基本时案例驱动的方式。 非常推荐 thenewcircle 貌似是个培训机构&#xff0c;多数是收费的&#xff0c;不过仍然有一些free resources值得你去挖掘。 coreservlets 虽然主打不是android&#xff0c;但是android的教程也​ 是…

Webpack4从入门到精通以及和webpack5对比_webpack现在用的是哪个版本

3.1 打包样式资源css-loader、style-loader… {// 匹配哪些文件test: /\.less$/,// 使用哪些loader进行处理use: [// use数组中loader执行顺序&#xff1a;从右到左&#xff0c;从下到上&#xff0c;依次执行(先执行css-loader)// style-loader&#xff1a;创建style标签&#…

STM32读写备份寄存器和实时时钟

文章目录 1. 硬件电路 2. RTC操作注意事项 操作步骤 3. 代码实现 3.1 读写备份寄存器 3.1.1 main.c 3.2 实时时钟 3.2.1 MyRTC.c 3.2.2 MyRTC.h 3.2.3 main.c 1. 硬件电路 对于BKP备份寄存器和RTC实时时钟的详细解析可以看下面这篇文章&#xff1a; STM32单片机BKP备…

HTML(19)——Flex

Flex布局也叫弹性布局&#xff0c;是浏览器提倡的布局模型&#xff0c;非常适合结构化布局&#xff0c;提供了强大的空间分布和对齐能力。 Flex模型不会产生浮动布局中脱标现象&#xff0c;布局网页更简单、更灵活。 Flex-组成 设置方式&#xff1a;给父元素设置display:fle…

Django数据驾驶舱

Django数据驾驶舱 1.项目介绍2.项目结构3.库表结构3.1 appcsdn的models3.2 appssq的models3.3 appweather的models3.4 appweibo的models 4.功能展示5.解决问题5.1 路由配置5.2 后端数据与前端echarts展示5.3 长图表丝滑滚动条 6.遗留问题7.资源分享 1.项目介绍 这里介绍本人最…

java打印金字塔paremid和空心金字塔

java打印金字塔 首先确定每行打印几个空格&#xff0c;在确定每行打印几个* 设总层数为layers&#xff0c;当前层数为i。 则每行打印空格数layers-i&#xff0c;每行打印星号数2*i-1 import java.util.Scanner;public class Paremid{public static void main(String[] args) …

Golang三色标记法

简介 在JVM中&#xff0c;GC采用可达性分析法来判断对象是否死亡&#xff1b;在python虚拟机中&#xff0c;GC采用引用计数法加循环检测器来判断对象是否死亡&#xff0c;而在golang中&#xff0c;使用的是三色表记法来判断对象是否死亡。 什么是三色抽象 总所周知在GC时&am…

分享:Javascript开源桌面环境-Puter

Puter这是一个运行在浏览器里的桌面操作系统&#xff0c;提供了笔记本、代码编辑器、终端、画图、相机、录音等应用和一些小游戏。该项目作者出于性能方面的考虑没有选择 Vue 和 React 技术栈&#xff0c;而是采用的 JavaScript 和 jQuery 构建&#xff0c;支持 Docker 一键部署…

DAC测试实验——FPGA学习笔记7

一、DAC简介 DAC全称Digital to Analog Converter&#xff0c;即数模转换器。它用于将主控芯片产生的数字值(0和1)转换为模拟值(电压值)。 1、DAC参数指标 2、DAC类型 常用的DAC可大致分为权电阻网络DAC、T型电阻网络DAC、倒T型电阻网络DAC以及权电流型DAC。 3、AD9708/3PD9…

2024 Testing Expo China – Automotive I 风丘与您相约上海世博馆

2024汽车测试及质量监控博览会&#xff08;中国&#xff09;——&#xff08;Testing Expo China – Automotive&#xff09;是面向整车、零部件和系统开发的各种技术和服务的盛会&#xff0c;展示了汽车测试、开发和验证技术的各个方面&#xff0c;每年在上海举行&#xff0c;…

微积分-导数1(导数与变化率)

切线 要求与曲线 C C C相切于 P ( a , f ( a ) ) P(a, f(a)) P(a,f(a))点的切线&#xff0c;我们可以在曲线上找到与之相近的一点 Q ( x , f ( x ) ) Q(x, f(x)) Q(x,f(x))&#xff0c;然后求出割线 P Q PQ PQ的斜率&#xff1a; m P Q f ( x ) − f ( a ) x − a m_{PQ} \…