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.mzph.cn/bicheng/32281.shtml

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

相关文章

解决js打开新页面百度网盘显示不存在方法:啊哦,你所访问的页面不存在了。

用js打开新页面open或window.location.href打开百度网盘后都显示&#xff1a;啊哦&#xff0c;你所访问的页面不存在了。 window.open(baidu_url); window.location.href baidu_url;在浏览器上&#xff0c;回车后网盘资源是可以打开的&#xff0c;刷新也是打开的。这是很奇怪…

深入分析并可视化城市轨道数据

介绍 中国城市化进程加速中&#xff0c;城市轨道交通的迅速扩张成为提升城市运行效率和居民生活品质的关键。这一网络从少数大城市延伸至众多大中型城市&#xff0c;映射了经济飞跃和城市管理现代化。深入分析并可视化城市轨道数据&#xff0c;对于揭示网络特性、评估效率、理…

进程、线程的区别

进程、线程的关系 开工厂生产手机&#xff0c;制作一条生产线&#xff0c;这个生产线上有很多的器件以及材料。一条生产线就是一个进程。 只有生产线是不够的&#xff0c;使用找五个工人来进行生产&#xff0c;这个工人能够利用这些材料最终一步步的将手机做出来&#xff0c;这…

Ansible 自动化运维实践

随着 IT 基础设施的复杂性不断增加&#xff0c;手动运维已无法满足现代企业对高效、可靠的 IT 运维需求。Ansible 作为一款开源的自动化运维工具&#xff0c;通过简洁易用的 YAML 语法和无代理&#xff08;agentless&#xff09;架构&#xff0c;极大简化了系统配置管理、应用部…

LuxTrust、契约锁联合启动中欧两地跨境电子签服务

6月18日&#xff0c;欧洲领先的数字身份和电子签名厂商-LuxTrust、全球领先的数字化技术和服务的提供商-浩鲸科技一行莅临契约锁上海总部&#xff0c;并于当日下午联合举行“跨境签战略合作”现场签约仪式。 三方将以此次合作为契机&#xff0c;发挥各自领域专业优势&#xff…

DS知识点总结--线性表定义及顺序表示

数据结构知识点汇总(考研C版) 文章目录 数据结构知识点汇总(考研C版)二、线性表2.1 线性表的定义和操作2.1.1 线性表的定义2.1.2 线性表的基本操作 2.2 线性表的顺序表示2.2.1 顺序表的定义2.2.2 顺序表上的基本操作的实现 二、线性表 2.1 线性表的定义和操作 2.1.1 线性表的…

区块链会议投稿资讯CCF A--WINE 2024 截止7.15 附录用率 附录用的区块链文章

Conference&#xff1a;The Conference on Web and Internet Economics (WINE) CCF level&#xff1a;CCF A Categories&#xff1a;Cross-cutting/comprehensive/emerging Year&#xff1a;2024 Conference time&#xff1a; December 2-5, 2024 录用率&#xff1a; sele…

MSPM0G3507——PWM

在sysconfig中&#xff0c;左侧可以选择MCU的外设&#xff0c;我们找到并点击TIMER-PWM选项卡&#xff0c;在TIMER-PWM中点击ADD&#xff0c;就可以添加定时器下的PWM外设。 这里设置通道0为100Hz的频率&#xff0c;0%占空比的PWM&#xff0c;周期计数值为1000&#xff0c;比较…

我理解的文本表示模型

词袋模型与N-grams模型 1 词袋模型 (Bag of Words)1.1 one-hot 取值 (Binary)1.2 Term Frequency 取值 (TF)普通频数 r a w t f raw_{tf} rawtf​频率范数归一化对数频数 1.3 Inverse document frequency (IDF)1.4 TF-IDF scores 取值 N-Gram 最简单的文本建模场景&#xff1a…

聚类算法(2)--- ISODATA算法

本篇文章是博主在人工智能等领域学习时&#xff0c;用于个人学习、研究或者欣赏使用&#xff0c;并基于博主对人工智能等领域的一些理解而记录的学习摘录和笔记&#xff0c;若有不当和侵权之处&#xff0c;指出后将会立即改正&#xff0c;还望谅解。文章分类在AI学习笔记&#…

Vue02-第一个Vue程序

第一个Vue程序 1、什么是MVVM MVVM&#xff08;Model-View-ViewModel&#xff09;是一种软件设计模式&#xff0c;由微软WPF&#xff08;用于替代WinForm&#xff0c;以前就是用这个技术开发桌面应用程序的&#xff09;和Silverlight&#xff08;类似于Java Applet&#xff0…

新手(初学者)学R语言第一课,从学正确导入数据开始

初看题目好像我在教你怎么导入数据&#xff0c;不不不&#xff0c;我是在教你正确的导入数据&#xff0c;不是说数据导入R就叫正确导入数据了。本章为新手教程&#xff0c;老手可以跳过。 这个内容早就想写了&#xff0c;今天有点空和大家聊一下。为什么R语言对于新手而言不太友…

nginx启动之后任务管理器里面没有nginx进程

原因1&#xff1a;确保你的nginx文件夹里面只包含英文路径&#xff01;绝对不能有中文&#xff01; 原因2&#xff1a; 到conf\nginx.conf里面查看端口和IP地址是否正确设置&#xff0c;ip地址有无正确输入

【MATLAB】(高数)

参考文章 函数极限 导数与偏导 极值和最值 局部范围的最值 局部范围内的最值&#xff0c;相当于函数的极值 离散数据的最值 多元函数的极值 fminunc [x, fval] fminunc(fun, x0)fun为代求极值的函数&#xff1b;x0为起始点&#xff0c;即从这个点开始寻找极值&#xff0c;…

4、MFC:菜单栏、工具栏与状态栏

菜单栏、工具栏与状态栏 1、菜单栏1.1 简介1.2 创建属性设置菜单消息成员函数 1.3 实例 2、工具栏2.1 简介工具栏属性2.2 创建消息CToolBar类的主要成员函数 2.3 实例 3、状态栏3.1 简介3.2 创建CStatusBar类状态栏创建 3.3 实例 1、菜单栏 1.1 简介 菜单在界面设计中是经常使…

渗透测试-若依框架的杀猪交易所系统管理后台

前言 这次是带着摸鱼的情况下简单的写一篇文章&#xff0c;由于我喜欢探究黑灰产业&#xff0c;所以偶尔机遇下找到了一个加密H币的交易所S猪盘&#xff0c;我记得印象是上年的时候就打过这一个同样的站&#xff0c;然后我是通过指纹查找其它的一些站&#xff0c;那个站已经关…

海外短剧系统如何征服观众心

海外短剧系统要征服观众的心&#xff0c;需要综合考虑多个方面。 1、紧凑的剧情设计&#xff1a; 短小精悍&#xff1a;海外短剧通常每集时长不超过半小时&#xff0c;甚至有的仅有几分钟。这种紧凑的剧情设计让观众能够在短时间内迅速沉浸在故事中&#xff0c;无需花费大量时间…

STM32驱动-ads1112

汇总一系列AD/DA的驱动程序 ads1112.c #include "ads1112.h" #include "common.h"void AD5726_Init(void) {GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC, ENABLE );//PORTA、D时钟使能 G…

大脑临界状态:探索思维背后的物理机制

在深度思考或创造性灵感的涌现时刻&#xff0c;个体常体验到一种介于混乱与有序之间的特殊心理状态。这种感受实则反映了大脑在认知过程中的临界状态&#xff0c;这是一种涉及复杂物理现象的心理活动表现。近期研究表明&#xff0c;大脑结构中存在着与临界性密切相关的物理特性…

为什么挂牌量是跟踪楼市情况的核心指标?

通过挂牌量&#xff0c;可以跟踪被动卖出者的数量&#xff0c;从而理解楼市的进展。 引子 楼市的“5.17”新政落地有一个多月了&#xff0c;然而&#xff0c;资本市场对“楼市的复苏预期”却在不断地下修。 以房地产开发的龙头企业保利发展为例&#xff0c;市场在“5.17”新…