Spring的启动扩展点机制详解

news2024/11/25 22:29:51

在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.coloradmin.cn/o/1846694.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

34 张动图:线缆,以太网与网络寻址

以太网 本地主机访服务器 以太网标准 非屏蔽双绞线&#xff08;UTP&#xff09; 电磁干扰 串扰 双绞线 真实的双绞线 线缆与引脚对齐方式 科普&#xff1a;网线每根芯的作用 引脚连接 数据传输与接收 主机直连到交换机 主机直连到主机&#xff08;或交换器&#xff09; Auto M…

C++ 矩阵的最小路径和解法

描述 给定一个 n * m 的矩阵 a&#xff0c;从左上角开始每次只能向右或者向下走&#xff0c;最后到达右下角的位置&#xff0c;路径上所有的数字累加起来就是路径和&#xff0c;输出所有的路径中最小的路径和。 数据范围: 1≤&#x1d45b;,&#x1d45a;≤5001≤n,m≤500&…

# 消息中间件 RocketMQ 高级功能和源码分析(九)

消息中间件 RocketMQ 高级功能和源码分析&#xff08;九&#xff09; 一、消息中间件 RocketMQ 源码分析&#xff1a; 同步刷盘分析 1、刷盘机制 RocketMQ 的存储是基于 JDK NIO 的内存映射机制&#xff08;MappedByteBuffer&#xff09;的&#xff0c;消息存储首先将消息追…

好用的便签是什么 电脑桌面上好用的便签

作为一名文字工作者&#xff0c;我经常需要在繁杂的思绪中捕捉灵感&#xff0c;记录下那些一闪而过的想法。在寻找一款适合电脑桌面的便签应用时&#xff0c;我偶然发现了敬业签便签软件简直是为我量身定制的&#xff0c;它不仅界面简洁&#xff0c;操作便捷&#xff0c;更重要…

前瞻展望,中国信通院即将发布“2024云计算十大关键词”

人类对于未知领域的探索欲望&#xff0c;似乎总是无穷无尽&#xff0c;而探索欲反过来推动了技术的革新与进步。今年以来&#xff0c;AI大模型成为科技领域最为确定的趋势之一。在大模型开启的AI原生时代&#xff0c;AI原生正在重构云计算的演化逻辑和发展走向&#xff0c;MaaS…

含铬废水有哪些危害?含铬废水怎么处理呢?

铬化物可以通过消化道、呼吸道、皮肤和粘膜侵人人体&#xff0c;主要积聚在肝、肾、内分泌系统和肺部。毒理作用是影响体内物质氧化、还原和水解过程&#xff0c;与核酸、核蛋白结合影响组织中的磷含量。铬化合物具有致癌作用。 铬化合物以蒸汽和粉尘的方式进入人体组织中&…

gstreamer+qt5实现简易视频播放器

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、安装环境1.QT52.gstreamer 二、代码1.Windows实现 三、测试效果总结 前言 最近在研究mpp&#xff0c;通过gstreamer实现了硬解码&#xff0c;但是我在想我…

ip地址怎么写才是的对的?合法ip地址正确的格式

IP地址怎么写才是的对的&#xff1f;在互联网的世界里&#xff0c;IP地址就像是我们生活中的门牌号码&#xff0c;它是每个设备在网络中的唯一标识。正确的书写IP地址对于确保网络通信的顺畅至关重要。本文将带您了解合法IP地址的正确格式与书写规范&#xff0c;并深入探讨其在…

【MySQL】索引的原理及其使用

文章目录 什么叫索引减少磁盘IO次数缓存池(Buffer Pool&#xff09;MySQL的页页内目录页目录 正确理解索引结构为什么Innodb的索引是B树结构各种存储引擎支持的索引聚簇索引和非聚簇索引索引类型 关于索引的操作创建主键索引唯一索引的创建普通索引的创建查看索引删除索引 什么…

6月21日(周五)AH股总结:沪指失守3000点,恒生科技指数跌近2%,多只沪深300ETF午后量能显著放大

内容提要 沪指全天围绕3000点关口来回拉锯&#xff0c;收盘跌破3000点。白酒及光刻机概念集体走低&#xff0c;中芯国际港股跌超2%。CRO医药概念及水利股逆势走强。 A股低开低走 沪指全天围绕3000点关口来回拉锯&#xff0c;收盘跌破3000点&#xff0c;跌0.24%。深成指跌0.04…

如何在 MySQL 中创建和使用事务?

目录 1. 环境准备 2. 创建事务 3. 事务执行 4. 事务撤消 5. 总结 事务是数据库区别于文件系统的重要特征之一&#xff0c;当我们有了事务就会让数据库始终保持一致&#xff0c;同时我们还能通过事务机制恢复到某个时间点&#xff0c;这样可以保证已提交到数据库的修改不会…

【Linux】基础IO_2

文章目录 六、基础I/O2. 系统文件I/O磁盘的存储结构 未完待续 六、基础I/O 2. 系统文件I/O 磁盘的存储结构 系统中不是所有对文件都是打开的状态&#xff0c;大部分的文件都是没有被打开的。这些文件一般都被存储在磁盘当中。磁盘通过柱面&#xff0c;扇面&#xff0c;扇区确…

JS 【详解】树的遍历(含深度优先遍历和广度优先遍历的算法实现)

用 js 描述树 let tree [{label:a,children:[{label:b,children:[{label:d},{label:e}]},{label:c,children:[{label:f}]}]} ]使用数组是因为树的节点有顺序 深度优先遍历 从根节点出发&#xff0c;优先遍历最深的节点 遍历顺序为 abdecf function DFS(tree) {tree.forEach(…

c++编译器优化不显示拷贝构造函数

一.错误情景&#xff08;无法打印拷贝函数&#xff09; #include<iostream> using namespace std;class person { public:person(){cout << "person默认构造函数调用" << endl;}person(int age){cout << "有参构造函数调用" <…

IIS代理配置-反向代理

前后端分离项目&#xff0c;前端在开发中使用proxy代理解决跨域问题&#xff0c;打包之后无效。 未配置前无法访问 部署环境为windows IIS&#xff0c;要在iis设置反向代理 安装代理模块 需要在iis中实现代理&#xff0c;需要安装Application Request Routing Cache和URL重…

在Verilog HDL中使用任务(task)

代码&#xff1a; sort4.v module sort4(ra,rb,rc,rd,a,b,c,d);output[3:0] ra,rb,rc,rd;input[3:0] a,b,c,d;reg[3:0] ra,rb,rc,rd;reg[3:0] va,vb,vc,vd;always (a or b or c or d)begin{va,vb,vc,vd}{a,b,c,d};sort2(va,vc); //va 与vc互换。sort2(vb,vd);…

为什么在React中一定要使用setState来更新数据?

为什么在React中一定要使用setState来更新数据&#xff1f; 因为我们修改了State后&#xff0c;我们希望看到React更具最新的State来重新渲染界面&#xff0c;但是这种方式修改的React并不知道数据发送的改变&#xff1b; 在React中并没有使用像Vue3或者Vue2中的数据劫持来监…

时间?空间?复杂度??

1.什么是时间复杂度和空间复杂度&#xff1f; 1.1算法效率 算法效率分析分为两种&#xff1a;第一种是时间效率&#xff0c;第二种是空间效率。时间效率被称为时间复杂度&#xff0c;而空间效率被称为空间复杂度。 时间复杂度主要衡量的是一个算法的运行速度&#xff0c;而空…

爱眼小妙招:台灯怎么选?学生如何正确使用台灯?

视力是心灵的窗户&#xff0c;尤其对于儿童来说更为重要。然而&#xff0c;随着现代生活方式的改变&#xff0c;孩子们面临越来越多的视力挑战。据统计&#xff0c;在近视学生中&#xff0c;近10%的人患有高度近视&#xff0c;而这一比例随年级的增加而逐渐上升。从幼儿园的小小…

Redis-数据类型-Bit的基本操作-getbit-setbit-Bitmap

文章目录 0、Bitmaps&#xff08;位图&#xff09;1、查看redis是否启动2、通过客户端连接redis3、切换到db7数据库4、设置&#xff08;或覆盖&#xff09;一个键&#xff08;key&#xff09;的值&#xff08;value&#xff09;5、获取存储在给定键&#xff08;key&#xff09;…