mini-spring源码分析
IOC模块
关键解释
beanFactory:beanFactory是一个hashMap, key为beanName, Value为 beanDefination
beanDefination:
BeanDefinitionRegistry,BeanDefinition注册表接口,定义注册BeanDefinition的方法
beanReference:增加BeanReference类,包装一个bean对另一个bean的引用。实例化beanA后填充属性时,若PropertyValue#value为BeanReference,引用beanB,则先去实例化beanB。 由于不想增加代码的复杂度提高理解难度,暂时不支持循环依赖,后面会在高级篇中解决该问题
Resource:

BeanFactoryPostProcessor和BeanPostProcessor是spring框架中具有重量级地位的两个接口,理解了这两个接口的作用,基本就理解spring的核心原理了。为了降低理解难度分两个小节实现。
BeanFactoryPostProcessor是spring提供的容器扩展机制,允许我们在bean实例化之前修改bean的定义信息即BeanDefinition的信息。其重要的实现类有PropertyPlaceholderConfigurer和CustomEditorConfigurer,PropertyPlaceholderConfigurer的作用是用properties文件的配置值替换xml文件中的占位符,CustomEditorConfigurer的作用是实现类型转换。BeanFactoryPostProcessor的实现比较简单,看单元测试BeanFactoryPostProcessorAndBeanPostProcessorTest#testBeanFactoryPostProcessor追下代码。
BeanPostProcessor也是spring提供的容器扩展机制,不同于BeanFactoryPostProcessor的是,BeanPostProcessor在bean实例化后修改bean或替换bean。BeanPostProcessor是后面实现AOP的关键
//在bean实例化之前,执行BeanFactoryPostProcessor invokeBeanFactoryPostProcessors(beanFactory);
然后重写beanFactory方法,对入参beanFactory进行interface 重写。
应用上下文ApplicationContext是spring中较之于BeanFactory更为先进的IOC容器,ApplicationContext除了拥有BeanFactory的所有功能外,还支持特殊类型bean如上一节中的BeanFactoryPostProcessor和BeanPostProcessor的自动识别、资源加载、容器事件和监听器、国际化支持、单例bean自动初始化等。
BeanFactory是spring的基础设施,面向spring本身;而ApplicationContext面向spring的使用者,应用场合使用ApplicationContext。
具体实现查看AbstractApplicationContext#refresh方法即可。注意BeanFactoryPostProcessor和BeanPostProcessor的自动识别,这样就可以在xml文件中配置二者而不需要像上一节一样手动添加到容器中了。
ApplicationContext把使用者的步骤打包了
getBeanOfType
@Override
public <T> Map<String, T> getBeansOfType(Class<T> type) throws BeansException {Map<String, T> result = new HashMap<>();beanDefinitionMap.forEach((beanName, beanDefinition) -> {Class beanClass = beanDefinition.getBeanClass();if (type.isAssignableFrom(beanClass)) {T bean = (T) getBean(beanName);result.put(beanName, bean);}});return result;
}
ApplicationContext容器提供了完善的事件发布和事件监听功能。
ApplicationEventMulticaster接口是注册监听器和发布事件的抽象接口,AbstractApplicationContext包含其实现类实例作为其属性,使得ApplicationContext容器具有注册监听器和发布事件的能力。在AbstractApplicationContext#refresh方法中,会实例化ApplicationEventMulticaster、注册监听器并发布容器刷新事件ContextRefreshedEvent;在AbstractApplicationContext#doClose方法中,发布容器关闭事件ContextClosedEvent。

support 比较实现类的CustomEvent
这是一种很好的泛型写法,可以运行时反射到(调用方发现是ApplicatonListerner, 使用方可以通过CustomEvent告诉兴趣事件。)


AOP引入
关键概念
Joinpoint,织入点,指需要执行代理操作的某个类的某个方法(仅支持方法级别的JoinPoint);Pointcut是JoinPoint的表述方式,能捕获JoinPoint。
最常用的切点表达式是AspectJ的切点表达式。需要匹配类,定义ClassFilter接口;匹配方法,定义MethodMatcher接口。PointCut需要同时匹配类和方法,包含ClassFilter和MethodMatcher,AspectJExpressionPointcut是支持AspectJ切点表达式的PointCut实现,简单实现仅支持execution函数。
AopProxy是获取代理对象的抽象接口,JdkDynamicAopProxy的基于JDK动态代理的具体实现。TargetSource,被代理对象的封装。MethodInterceptor,方法拦截器,是AOP Alliance的"公民",顾名思义,可以拦截方法,可在被代理执行的方法前后增加代理行为。
JDK实现
Advisor是Pointcut和Advice的组合
public class DynamicProxyTest {@Testpublic void testJdkDynamicProxy() throws Exception {WorldService worldService = new WorldServiceImpl();AdvisedSupport advisedSupport = new AdvisedSupport();TargetSource targetSource = new TargetSource(worldService);WorldServiceInterceptor methodInterceptor = new WorldServiceInterceptor();MethodMatcher methodMatcher = new AspectJExpressionPointcut("execution(* org.springframework.test.service.WorldService.explode(..))").getMethodMatcher();advisedSupport.setTargetSource(targetSource);advisedSupport.setMethodInterceptor(methodInterceptor);advisedSupport.setMethodMatcher(methodMatcher);WorldService proxy = (WorldService) new JdkDynamicAopProxy(advisedSupport).getProxy();proxy.explode();}
}
三级缓存singletonFactories里的对象里面有getObject里面会调用wrapifnessary,在这里会创建新的代理对象,如果二级缓存的话,会存在引用不相等的问题。
protected Object wrapIfNecessary(Object bean, String beanName) {//避免死循环if (isInfrastructureClass(bean.getClass())) {return bean;}Collection<AspectJExpressionPointcutAdvisor> advisors = beanFactory.getBeansOfType(AspectJExpressionPointcutAdvisor.class).values();try {ProxyFactory proxyFactory = new ProxyFactory();for (AspectJExpressionPointcutAdvisor advisor : advisors) {ClassFilter classFilter = advisor.getPointcut().getClassFilter();if (classFilter.matches(bean.getClass())) {TargetSource targetSource = new TargetSource(bean);proxyFactory.setTargetSource(targetSource);proxyFactory.addAdvisor(advisor);proxyFactory.setMethodMatcher(advisor.getPointcut().getMethodMatcher());}}if (!proxyFactory.getAdvisors().isEmpty()) {return proxyFactory.getProxy();}} catch (Exception ex) {throw new BeansException("Error create proxy bean for: " + beanName, ex);}return bean;}
基于CGLIB的动态代理实现逻辑也比较简单,查看CglibAopProxy。与基于JDK的动态代理在运行期间为接口生成对象的代理对象不同,基于CGLIB的动态代理能在运行期间动态构建字节码的class文件,为类生成子类,因此被代理类不需要继承自任何接口。
public class DynamicProxyTest {private AdvisedSupport advisedSupport;@Beforepublic void setup() {WorldService worldService = new WorldServiceImpl();advisedSupport = new AdvisedSupport();TargetSource targetSource = new TargetSource(worldService);WorldServiceInterceptor methodInterceptor = new WorldServiceInterceptor();MethodMatcher methodMatcher = new AspectJExpressionPointcut("execution(* org.springframework.test.service.WorldService.explode(..))").getMethodMatcher();advisedSupport.setTargetSource(targetSource);advisedSupport.setMethodInterceptor(methodInterceptor);advisedSupport.setMethodMatcher(methodMatcher);}@Testpublic void testCglibDynamicProxy() throws Exception {WorldService proxy = (WorldService) new CglibAopProxy(advisedSupport).getProxy();proxy.explode();}
}
// 使用JDK动态代理advisedSupport.setProxyTargetClass(false);WorldService proxy = (WorldService) new ProxyFactory(advisedSupport).getProxy();proxy.explode();// 使用CGLIB动态代理advisedSupport.setProxyTargetClass(true);proxy = (WorldService) new ProxyFactory(advisedSupport).getProxy();proxy.explode();
增加AOP代理工厂ProxyFactory,由AdvisedSupport#proxyTargetClass属性决定使用JDK动态代理还是CGLIB动态代理(引用还是一样的)
如果要使用二级缓存解决循环依赖,意味着所有Bean在实例化后就要完成AOP代理,这样违背了Spring设计的原则,Spring在设计之初就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP
每层缓存的意义
第三级缓存是个lamdba表达式值捕获bean引用 ,进行advisor判断,然后进行代理升级。
getBeansByType

Application refresh
@Overridepublic void refresh() throws BeansException {//创建BeanFactory,并加载BeanDefinitionrefreshBeanFactory();ConfigurableListableBeanFactory beanFactory = getBeanFactory();//添加ApplicationContextAwareProcessor,让继承自ApplicationContextAware的bean能感知beanbeanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));//在bean实例化之前,执行BeanFactoryPostProcessorinvokeBeanFactoryPostProcessors(beanFactory);//BeanPostProcessor需要提前与其他bean实例化之前注册registerBeanPostProcessors(beanFactory);//初始化事件发布者initApplicationEventMulticaster();//注册事件监听器registerListeners();//注册类型转换器和提前实例化单例beanfinishBeanFactoryInitialization(beanFactory);//发布容器刷新完成事件finishRefresh();}
//在bean实例化之前,执行BeanFactoryPostProcessor invokeBeanFactoryPostProcessors(beanFactory);
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {Map<String, BeanFactoryPostProcessor> beanFactoryPostProcessorMap = beanFactory.getBeansOfType(BeanFactoryPostProcessor.class);for (BeanFactoryPostProcessor beanFactoryPostProcessor : beanFactoryPostProcessorMap.values()) {// 接口的妙用,用接口判断,然后传入beanMap,接口进行处理,和上面泛型接口刚好服务方和调用者做了一个翻转。beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);}}
拓展篇
TODO三级缓存,和

类型转换服务bean
spring在org.springframework.core.convert.converter包中定义了三种类型转换器接口:Converter、ConverterFactory、GenericConverter。

Converter<S,T>接口适合一对一的类型转换,如果要将String类型转换为Ineger/Long/Float/Double/Decimal等类型,就要实现一系列的StringToInteger/StringToLongConverter/StringToFloatConverter转换器,非常不优雅。
ConverterFactory接口则适合一对多的类型转换,可以将一种类型转换为另一种类型及其子类。比如将String类型转换为Ineger/Long/Float/Double/Decimal等Number类型时,只需定义一个ConverterFactory转换器:

做成一个服务的factoryBean
为了方便使用,提供了创建ConversionService的FactoryBean——ConversionServiceFactoryBean。
如果有定义ConversionService,在AbstractApplicationContext#finishBeanFactoryInitialization方法中设置到容器中。
- 为bean填充属性时,见AbstractAutowireCapableBeanFactory#applyPropertyValues
- 处理@Value注解时,见AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues



多切面AOP
组装advisor key可以进行缓存,但最好用method作为key


MethodInvocation只是简单的将拦截器链的所有拦截器一一执行,最后再触发当前的method方法。这是很简单高效的方法,但问题是我们希望某些增强比如AfterReturningAdvice能够在方法执行完才被执行,这就涉及到不同增强的执行顺序的问题了。而MethodInvocation显然没有考虑顺序的问题,一个AfterReturningAdvice很可能在BeforeAdvice之前被调用。那么该如何保证顺序问题呢?
答案是,控制增强的调用顺序其实由每个拦截器负责,所以我们需要分析MethodBeforeAdviceInterceptor和AfterReturningAdviceInterceptor,
但是代理的顺序不同还是会影响返回值,但是相对于一次的方法位置是符合预期的。

三级缓存实现
三级缓存解决问题是解决三个转态转换的状态机(用于单例bean) 1.属性填充完成 2.代理完成对象 3.bean定义(lamdba表达式)
也是一种可重入的判断
也是共享引用的思想
解决有代理对象时的循环依赖问题,需要提前暴露代理对象的引用,而不是暴露实例化后的bean的引用(这是上节的遗留问题的原因,应该提前暴露A的代理对象的引用)。
spring中用singletonFactories(一般称第三级缓存)解决有代理对象时的循环依赖问题。在实例化后提前暴露代理对象的引用(见AbstractAutowireCapableBeanFactory#doCreateBean方法第6行)。
getBean()时依次检查一级缓存singletonObjects、二级缓存earlySingletonObjects和三级缓存singletonFactories中是否包含该bean。如果三级缓存中包含该bean,则挪至二级缓存中,然后直接返回该bean。见AbstractBeanFactory#getBean方法第1行。
protected Object doCreateBean(String beanName, BeanDefinition beanDefinition) {Object bean;try {bean = createBeanInstance(beanDefinition);//为解决循环依赖问题,将实例化后的bean放进缓存中提前暴露if (beanDefinition.isSingleton()) {Object finalBean = bean;addSingletonFactory(beanName, new ObjectFactory<Object>() {@Overridepublic Object getObject() throws BeansException {return getEarlyBeanReference(beanName, beanDefinition, finalBean);}});}//实例化bean之后执行boolean continueWithPropertyPopulation = applyBeanPostProcessorsAfterInstantiation(beanName, bean);if (!continueWithPropertyPopulation) {return bean;}//在设置bean属性之前,允许BeanPostProcessor修改属性值applyBeanPostProcessorsBeforeApplyingPropertyValues(beanName, bean, beanDefinition);//为bean填充属性applyPropertyValues(beanName, bean, beanDefinition);//执行bean的初始化方法和BeanPostProcessor的前置和后置处理方法bean = initializeBean(beanName, bean, beanDefinition);} catch (Exception e) {throw new BeansException("Instantiation of bean failed", e);}//注册有销毁方法的beanregisterDisposableBeanIfNecessary(beanName, bean, beanDefinition);Object exposedObject = bean;if (beanDefinition.isSingleton()) {//如果有代理对象,此处获取代理对象exposedObject = getSingleton(beanName);addSingleton(beanName, exposedObject);}return exposedObject;}
相关文章:
mini-spring源码分析
IOC模块 关键解释 beanFactory:beanFactory是一个hashMap, key为beanName, Value为 beanDefination beanDefination: BeanDefinitionRegistry,BeanDefinition注册表接口,定义注册BeanDefinition的方法 beanReference:增加Bean…...
黑马程序员Java项目实战《苍穹外卖》Day01
苍穹外卖-day01 课程内容 软件开发整体介绍苍穹外卖项目介绍开发环境搭建导入接口文档Swagger 项目整体效果展示: 管理端-外卖商家使用 用户端-点餐用户使用 当我们完成该项目的学习,可以培养以下能力: 1. 软件开发整体介绍 作为一…...
uniapp开发支付宝小程序自定义tabbar样式异常
解决方案: 这个问题应该是支付宝基础库的问题,除了依赖于官方更新之外,开发者可以利用《自定义 tabBar》曲线救国 也就是创建一个空内容的自定义tabBar,这样即使 tabBar 被渲染出来,但从视觉上也不会有问题 1.官方文…...
python+django5.1+docker实现CICD自动化部署springboot 项目前后端分离vue-element
一、开发环境搭建和配置 # channels是一个用于在Django中实现WebSocket、HTTP/2和其他异步协议的库。 pip install channels#channels-redis是一个用于在Django Channels中使用Redis作为后台存储的库。它可以用于处理#WebSocket连接的持久化和消息传递。 pip install channels…...
python代码示例(读取excel文件,自动播放音频)
目录 python 操作excel 表结构 安装第三方库 代码 自动播放音频 介绍 安装第三方库 代码 python 操作excel 表结构 求出100班同学的平均分 安装第三方库 因为这里的表结构是.xlsx文件,需要使用openpyxl库 如果是.xls格式文件,需要使用xlrd库 pip install openpyxl /…...
【第十课】Rust并发编程(一)
目录 前言 Fork和Join 前言 本节会介绍Rust中的并发编程,并发编程在编程中是提升cpu使用率的一大利器,通过多线程技术提升效率,Rust的并发和其他编程语言的并发不同的地方在于,Rust号称无畏并发。更重要的一点是安全。Rust中所有…...
图形渲染性能优化
variable rate shading conditional render 设置可见性等, 不需要重新build command buffer indirect draw glMultiDraw* - 直接支持多次绘制glMultiDrawIndirect - 间接多次绘制multithreading 多线程录制 实例化渲染 lod texture array 小对象剔除 投影到…...
elasticsearch的索引模版使用方法
5 索引模版⭐️⭐️⭐️⭐️⭐️ 索引模板就是创建索引时要遵循的模板规则索引模板仅对新创建的索引有效,已经创建的索引并不受索引模板的影响 5.1 索引模版的基本使用 1.查看所有的索引模板 GET 10.0.0.91:9200/_index_template2.创建自定义索引模板 xixi &…...
论文学习——进化动态约束多目标优化:测试集和算法
论文题目:Evolutionary Dynamic Constrained Multiobjective Optimization: Test Suite and Algorithm 进化动态约束多目标优化:测试集和算法(Guoyu Chen ,YinanGuo , Member, IEEE, Yong Wang , Senior Member, IEEE, Jing Liang , Senior …...
C++中的volatile关键字
作用: 1.它用于修饰变量,告知编译器该变量的值可能会在程序的外部被改变,编译器不能对这个变量的访问进行优化。这是因为编译器通常会对代码进行优化,例如把变量的值缓存到寄存器中,但对于 volatile 变量,…...
linux桌面qt应用程序UI自动化实现之dogtail
1. 前言 Dogtail适用于Linux 系统上进行 GUI 自动化测试,利用 Accessibility 技术与桌面程序通信;Dogtail 包含一个名为 sniff 的组件,这是一个嗅探器,用于 GUI 程序追踪; 源码下载:dogtail PyPI 可通过sudo python setup.py install安装或sudo pip install dogt…...
Hello World C#
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System; 引入了System命名空间,基本输入输出。一般只用这个,后面的不用 using System.Collections.Generic; 包含了定…...
SAP开发语言ABAP开发入门
1. 了解ABAP开发环境和基础知识 - ABAP简介 - ABAP(Advanced Business Application Programming)是SAP系统中的编程语言,主要用于开发企业级的业务应用程序,如财务、物流、人力资源等模块的定制开发。 - 开发环境搭建 - 首先需…...
应急响应靶机——easy溯源
载入虚拟机,开启虚拟机: (账户密码:zgsfsys/zgsfsys) 解题程序.exe是额外下载解压得到的: 1. 攻击者内网跳板机IP地址 2. 攻击者服务器地址 3. 存在漏洞的服务(提示:7个字符) 4. 攻击者留下的flag(格式…...
【前端】vscode报错: 无法加载文件 D:\nodejs\node_global\yarn.ps1,因为在此系统上禁止运行脚本。
vscode运行前端代码时候,执行yarn install时候报错 问题: 无法加载文件 D:\nodejs\node_global\yarn.ps1,因为在此系统上禁止运行脚本。 解决方式: 首先用管理员身份运行vscode 查看 get-ExecutionPolicy,Restrict…...
Spring Web MVC(详解中)
文章目录 Spring MVC(中)RESTFul风格设计RESTFul风格概述RESTFul风格特点RESTFul风格设计规范RESTFul风格好处RESTFul风格实战需求分析RESTFul风格接口设计后台接口实现 基于RESTFul风格练习(前后端分离模式)案例功能和接口分析功…...
Flutter:encrypt插件 AES加密处理
1、pubspec.yaml导入插件 cupertino_icons: ^1.0.8 # 密码加密 encrypt: 5.0.3encrypt封装 import package:encrypt/encrypt.dart; /// 加密类 class EncryptUtil {static final EncryptUtil _instance EncryptUtil._internal();factory EncryptUtil() > _instance;Encrypt…...
Python bytes类型及用法
在Python中,bytes类型是一种不可变的字节序列,用于存储原始的二进制数据。bytes对象通常用于处理文件、网络通信和其他需要处理原始字节数据的场景。 以下是bytes类型的一些基本用法和特性: 1. 创建bytes对象 可以通过多种方式创建bytes对…...
阅读《基于蒙特卡洛法的破片打击无人机易损性分析》_笔记
目录 基本信息 1 引言 1.1 主要研究内容 1.2 研究必要性(为什么要研究) 1.3 该领域研究现状(别人做了什么/怎么做的) 2 主要研究过程(我们做了什么) 2.1 建立目标仿真模型 2.2 确定毁伤依据 2.3 无…...
【vim】vim怎么把某一列内容复制到另一列
1. vim 怎么把某一列内容复制到另一列 移动光标到你想复制的列的第一个字符上。按下 ctrlv 进入选择模式。按下方向键选择多行。按下 h 或 j 或 k 或 l,选择整列。按下 y 复制所选择的列。移动光标到你想粘贴内容的列的第一个字符上。按下 p 粘贴内容。...
Vue记事本应用实现教程
文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展:显示创建时间8. 功能扩展:记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...
Xshell远程连接Kali(默认 | 私钥)Note版
前言:xshell远程连接,私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...
Linux云原生安全:零信任架构与机密计算
Linux云原生安全:零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言:云原生安全的范式革命 随着云原生技术的普及,安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测,到2025年,零信任架构将成为超…...
什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...
【Java学习笔记】BigInteger 和 BigDecimal 类
BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点:传参类型必须是类对象 一、BigInteger 1. 作用:适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...
技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
浪潮交换机配置track检测实现高速公路收费网络主备切换NQA
浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求,本次涉及的主要是收费汇聚交换机的配置,浪潮网络设备在高速项目很少,通…...
RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发,后来由Pivotal Software Inc.(现为VMware子公司)接管。RabbitMQ 是一个开源的消息代理和队列服务器,用 Erlang 语言编写。广泛应用于各种分布…...
Axure 下拉框联动
实现选省、选完省之后选对应省份下的市区...
第一篇:Liunx环境下搭建PaddlePaddle 3.0基础环境(Liunx Centos8.5安装Python3.10+pip3.10)
第一篇:Liunx环境下搭建PaddlePaddle 3.0基础环境(Liunx Centos8.5安装Python3.10pip3.10) 一:前言二:安装编译依赖二:安装Python3.10三:安装PIP3.10四:安装Paddlepaddle基础框架4.1…...
