深入剖析 Spring 核心数据结构:BeanFactory

深入剖析 Spring 核心数据结构:BeanFactory

D瓜哥
在 深入剖析 Spring 核心数据结构:BeanDefinition 中,介绍了 BeanDefinition。网上很多文章介绍 BeanDefinition 的 API,D瓜哥却要反其道而行之,从内部属性来分析一下。下面我们开始。 继承体系 Spring 非常好地遵循了面向对象的设计原则:面向接口编程。不放过任何可以提取出成接口的机会。虽然感觉似乎增加了类的继承关系,增加了一点的复杂度。但是,却带来了非常好的可扩展性。而 BeanFactory 的继承体系就是一个非常典型的例子。我们来看一下它的继承体系: Figure 1. BeanFactory 继承体系 AliasRegistry:别名注册器。Spring 中,别名注册相关的功能就是从这里实现的。 SimpleAliasRegistry:别名注册器的一个简单实现,从内部属性可以看出,它是把别名映射信息存到一个 Map 中了。 DefaultSingletonBeanRegistry:默认的单例 Bean 注册器,从内部属性来说,也是基于 Map 实现的。 FactoryBeanRegistrySupport: FactoryBean 注册器。 SingletonBeanRegistry:单例 Bean 注册器。 BeanDefinitionRegistry: BeanDefinition 注册器。 BeanFactory:容器的基类。 ListableBeanFactory:在基本容器基础上,增加了遍历相关功能。 HierarchicalBeanFactory:在基本容器基础上,增加了父子上下级容器关联。 AutowireCapableBeanFactory:在基本容器基础上,增加了自动注入功能。 ConfigurableBeanFactory:对容器增加可配置性,比如父级容器、ClassLoader、TypeConverter 等。 ConfigurableListableBeanFactory:可配置可遍历容器。 AbstractBeanFactory:容器的抽象实现类,实现了容器的基础功能。 AbstractAutowireCapableBeanFactory:带自动装配功能的抽象容器类。 DefaultListableBeanFactory:这是 Spring 内部使用的默认容器实现。也是 Spring 中最重要的一个类。 核心属性 Registry Map<String, String> aliasMap = new ConcurrentHashMap<>(16):别名到 Bean 名称的映射。 Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256):Bean 名称到单例 Bean 的映射。可以理解成,这就是所谓的容器。
深入剖析 Spring 核心数据结构:BeanDefinition

深入剖析 Spring 核心数据结构:BeanDefinition

D瓜哥
林纳斯·托瓦兹(Linus Torvalds)说:“我从心底认为,优秀的程序员与平庸的程序员之间的区别,是在于认为自己的代码重要还是数据结构更加重要。平庸的程序员眼里只有代码,优秀的程序员则关注数据结构及之前的关系。” 也许很多人觉得 Spring 神秘莫测,但是如果了解了它的核心数据结构,很多问题迎刃而解。 Spring 中两个数据结构最核心:① BeanDefinition,用于表示 Bean 的定义;② BeanFactory,用于表示整个 IoC 容器。 在前面文章 Spring Bean 生命周期概述中,介绍了 Spring Bean 的生命周期。不知道大家有没有思考过 Spring 在内部是如何表示一个 Bean 的?本篇文章,就来聊一聊 BeanDefinition 问题 使用 Spring 时,尤其是使用 XML 配置的时候,也许我们会这样的问题: Bean 怎么表示? Bean 的依赖怎么表示? init-method 方法怎么存储? Bean 的一些属性,比如 lazy-init 等,怎么表示? Bean 构造函数的参数怎么存储? …​ Java 也有类似的问题,比如怎么表示一个类?Java 通过反射 API 来解决这个问题: Class Method Field Constructor Annotation 但是,为什么 Spring 还要自己定义一套呢?主要原因是 Java 反射 API 不满足 Spring 的需求,比如,它没办法表示哪些类是 SCOPE_SINGLETON,哪些类是 SCOPE_PROTOTYPE。 另外,Spring 的 Bean 抽象也并不是完全自定义的,它是基于 Java 反射 API 又增加了自定义功能,其核心 API 就是 BeanDefinition。下面,我们来仔细看一下它的继承体系以及内部核心属性。
Spring AOP 处理流程概述

Spring AOP 处理流程概述

D瓜哥
AOP 是 Spring 框架的最核心的两个功能之一,在前面的 Spring 启动流程概述 和 Spring Bean 生命周期概述 两篇文章中,分别介绍了 Spring 启动过程和 Spring Bean 的生命周期,对 IoC 有了一个细致介绍。这里来细致分析一下 Spring AOP 的实现原理和处理流程。 基本概念 先来了解几个基本概念,D瓜哥私以为这些概念是 AOP 中最核心的内容,了解了基本概念,可以说基本上掌握了一半的 AOP 内容。 学习概念最权威的地方,当然就是官方文档。所以,这些概念可以在 Spring Framework Documentation: AOP Concepts 中看到最权威的介绍。 Join point(连接点): 所谓的连接点是指那些被拦截到的点。在 Spring 中,连接点指的是方法,因为 Spring 只支持方法类型的连接点。在 Spring 中,使用 Pointcut(切入点): 所谓的切入点,是指要对哪些 Join point(连接点) 进行拦截的定义。如果 Join point(连接点) 是全集,那么 Pointcut(切入点) 就是被选中的子集。写 AOP 代码的时候,一般是用 Pointcut(切入点) 表达式进行对 Join point(连接点) 进行选择。 Advice(通知/增强): 所谓的通知就是指拦截到 Join point(连接点) 之后所要做的事情。通知根据作用位置不同,又细分为: Before advice(前置通知): 在 Join point(连接点) 之前运行的通知。这种通知,不能阻止执行流程继续到 Join point(连接点)。
Spring Bean 生命周期概述

Spring Bean 生命周期概述

D瓜哥
在 Spring 启动流程概述 中,分析了 Spring 的启动流程。本文就来说明一下 Spring Bean 整个生命周期。如果有不清楚的地方,可以参考上文的“附录:启动日志”。 直接上图:Spring Bean 生命周期流程图。内容较多,图片文字偏小,请放大看(矢量图,可以任意放大): Figure 1. Spring Bean 生命周期流程图 下面是文字说明。 Bean 生命周期简述 调用 InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation,主要是判断 AnnotationAwareAspectJAutoProxyCreator 是否可以生成代理。 调用构造函数 调用 MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition,主要是通过 CommonAnnotationBeanPostProcessor、 AutowiredAnnotationBeanPostProcessor 收集依赖信息。 InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation,这步什么也没做。 调用 InstantiationAwareBeanPostProcessor#postProcessProperties,主要是完成依赖注入。 调用 AutowiredAnnotationBeanPostProcessor#setBeanFactory,注入 BeanFactory 等相关信息。 调用 BeanPostProcessor#postProcessBeforeInitialization,主要是注入 ApplicationContext 等相关信息。 调用 InitializingBean#afterPropertiesSet、 init-method 方法 调用 BeanPostProcessor#postProcessAfterInitialization,主要是生成 AOP 代理类。 Bean 生命周期详解 从 getBean() 方法获取 Bean 时,如果缓存中没有对应的 Bean,则会创建 Bean,整个流程如下: InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation — 目前有如下四个: ImportAwareBeanPostProcessor — 继承父类实现,无所事事。 AnnotationAwareAspectJAutoProxyCreator — 继承父类实现,判断是否属于基础切面类,如果有指定的 Target 则生成代理。
Spring 启动流程概述

Spring 启动流程概述

D瓜哥
对于 Spring 启动流程和 Bean 的生命周期,总有一些小地方搞的不是很清楚,干脆直接通过修改代码增加日志输出,使用断点单步调试,把整个流程捋顺了一点点的。 除了加载配置文件或者基础配置类外,Spring 的启动过程几乎都被封装在 AbstractApplicationContext#refresh 方法中,可以说弄清楚了这个方法的执行过程,就摸清楚了 Spring 启动全流程,下面的流程分析也是以这个方法为骨架来展开的。 流程概要 下面完整流程有些太复杂,所以,提炼一个简要的过程,方便糊弄面试官,哈哈哈😆 创建容器,读取 applicationContext.register(Config.class) 指定的配置。 准备 BeanFactory,注册容器本身和 BeanFactory 实例,以及注册环境配置信息等。 执行 BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry 注册 BeanDefinition。有三点需要注意: 目前只有一个 ConfigurationClassPostProcessor 实现类,Spring 中大量的 Bean 都是在这一步被该类注册到容器中的。 执行顺序是 ① PriorityOrdered ② Ordered ③ 普通的顺序来执行 在执行上一步时,如果发现注册了 BeanDefinitionRegistryPostProcessor 类型的 Bean,就会在循环里继续调用 postProcessBeanDefinitionRegistry 方法。MyBATIS 和 Spring 整合的 MapperScannerConfigurer 类就是在这一步执行的。 执行 BeanFactoryPostProcessor#postProcessBeanFactory 方法。目前只有一个 ConfigurationClassPostProcessor 实现类。 注册 CommonAnnotationBeanPostProcessor 和 AutowiredAnnotationBeanPostProcessor 为 BeanPostProcessor。 注册 ApplicationEventMulticaster,用于广播事件的。 注册 ApplicationListener 预加载以及注册所有非懒加载的 Bean 启动时序图 Spring 启动流程的时序图如下: Figure 1.
Spring 扩展点实践:整合 MyBATIS

Spring 扩展点实践:整合 MyBATIS

D瓜哥
在上一篇文章 Spring 扩展点概览及实践 中介绍了 Spring 内部存在的扩展点。学以致用,现在来分析一下 Spring 与 MyBATIS 的整合流程。 示例程序 为了方便分析源码,先根据官方文档 mybatis-spring – MyBatis-Spring | Getting Started 搭建起一个简单实例。 数据库方面,直接使用功能了 MySQL 示例数据库: MySQL : Employees Sample Database,需要的话,自行下载。 package com.diguage.truman.mybatis; import com.mysql.cj.jdbc.Driver; import com.zaxxer.hikari.HikariDataSource; import org.apache.ibatis.session.Configuration; import org.junit.jupiter.api.Test; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import javax.sql.DataSource; /** * @author D瓜哥, https://www.diguage.com/ * @since 2020-05-29 17:11 */ public class MybatisTest { @Test public void test() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.
Spring 扩展点概览及实践

Spring 扩展点概览及实践

D瓜哥
学习 Spring 代码,最重要的是掌握 Spring 有哪些扩展点,可以利用这些扩展点对 Spring 做什么扩展操作。说得更具体一点,如果自己开发一个框架,如何与 Spring 进行整合,如果对 Spring 的扩展点有一个比较清晰的认识,势必会事半功倍。 @Import 先来看一下 @Import 注解的定义: @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Import { /** * {@link Configuration @Configuration}, {@link ImportSelector}, * {@link ImportBeanDefinitionRegistrar}, or regular component classes to import. */ Class<?>[] value(); } 从声明可以看出,使用时,只需要指定 Class 实例即可;从方法的文档中可以看出,Class 实例可以分为三种:ImportSelector、ImportBeanDefinitionRegistrar 和常规组件类。示例如下: @Configuration @Import(LogImportSelector.class) public static class Config { } 在 org.springframework.context.annotation.ConfigurationClassParser#processImports 方法中,集中了对 @Import 注解的处理。从代码可以非常清晰地看出,分了三种情况进行处理: ImportSelector ImportBeanDefinitionRegistrar 常规组件 Class 下面分别对其进行介绍。 ImportSelector 先来看一下 ImportSelector 接口的定义:

负载均衡算法及实践

D瓜哥
前几天在看一个资料时,看到关于负载均衡算法的介绍。最近也在研究 Spring Cloud 和 Apache Dubbo 等微服务框架。正好负载均衡是微服务框架中一个很重要的知识点。就动手做个整理和总结。方便后续学习。 听朋友建议,这篇文章还可以在算法对比,客户端负载均衡与服务端负载均衡区分等两方面做些补充。这些内容后续再补充加入进来。 常见的负载均衡算法 轮询(Round Robin)法 轮询选择指的是从已有的后端节点列表中按顺序依次选择一个节点出来提供服务。 优点:试图做到请求转移的绝对均衡。实现简单,使用广泛。 加权轮询(Weighted Round Robin)法 实际使用中各个节点往往都带有不同的权重,所以一般都需要实现带权重的轮询选择。 权重高的被选中的次数多,权重低的被选中的次数少。 优点:是 轮询(Round Robin)法 改良版。适用于服务器配置不一致时,可以将配置好的服务器多干活,配置差的服务器少干活以使机器的负载达到相同的水平。 静态轮询(Static Round Robin)法 HAProxy 中实现的一个负载均衡算法。 没有后台服务器的限制,服务器启动时,修改权重也不会生效。增删服务器时,服务器准备就绪后,会立即加入到服务队列中。 随机(Random)法 通过随机函数,根据后端服务器列表的大小值来随机选择其中一台进行访问。由概率统计理论可以得知,随着调用量的增大,其实际效果越来越接近于平均分配流量到每一台后端服务器,也就是轮询的效果。 加权随机(Weighted Random)法 与加权轮询法类似,加权随机法也是根据后端服务器不同的配置和负载情况来配置不同的权重。不同的是,它是按照权重来随机选择服务器的,而不是顺序。 原地址哈希(IP Hashing)法 源地址哈希的思想是获取客户端访问的IP地址值,通过哈希函数计算得到一个数值,用该数值对服务器列表的大小进行取模运算,得到的结果便是要访问的服务器的序号。 优点:保证了相同客户端 IP 地址将会被哈希到同一台后端服务器,直到后端服务器列表变更。根据此特性可以在服务消费者与服务提供者之间建立有状态的 Session 会话。 URI 哈希(URI Hashing)法 HAProxy 中实现的一个负载均衡算法。支持部分 URI(问号之前)和完整 URI 两种模式。 这个算法可以把同一个 URI 的访问发送到同一台服务器上,以最大程度提高缓存命中率。 该算法支持两个可选参数 len 和 depth,后跟一个正整数。仅在需要基于URI的开头来平衡服务器时,这些选项可能会很有用。 len 参数指示算法仅应考虑URI开头的许多字符来计算哈希。请注意,将 len 设置为 1 几乎没有意义,因为大多数URI都以前导 / 开头。 depth 参数指示用于计算哈希的最大目录深度。请求中的每个斜杠都计为一个级别。如果同时指定了两个参数,则在达到任意一个参数时都将停止评估。 哈希算法也有很多中,而且不同算法各有优缺。回头单独开篇整理吧。 URL 参数(URL Parameter)法 HAProxy 中实现的一个负载均衡算法。根据 URL 参数的哈希值来选择服务器。
HikariCP 源码分析 --  ConcurrentBag

HikariCP 源码分析 -- ConcurrentBag

D瓜哥
以前无意间搜资料了解到 HikariCP,一下子就被它的简洁代码和卓越性能吸引住了。以前也有翻过它的代码,但是不是很系统,最近再次翻阅,正好做些笔记,方便以后学习。 D瓜哥最近在学习 Java 并发知识。那就从 HikariCP 自定义的并发集合 ConcurrentBag 开始学习。 在 HikariCP 的 Wiki 中,有 Down the Rabbit Hole · ConcurrentBag 的章节来专门介绍 ConcurrentBag: ConcurrentBag 的灵感借鉴自 C# .NET 的 ConcurrentBag 类。但是实现却是完全不同的。这里的 ConcurrentBag 有如下特性: A lock-free design ThreadLocal caching Queue-stealing Direct hand-off optimizations 下面,通过代码来对此做个说明。 在 ConcurrentBag 类的定义中,声明了集合元素必须是 IConcurrentBagEntry 的子类。先来看看这个接口的定义: public interface IConcurrentBagEntry { int STATE_NOT_IN_USE = 0; int STATE_IN_USE = 1; int STATE_REMOVED = -1; int STATE_RESERVED = -2; boolean compareAndSet(int expectState, int newState); void setState(int newState); int getState(); } 接下来,看一下成员变量:

在世界读书日,推荐书单

今天是世界读书日,各种人都在推荐书单。D瓜哥也凑个热闹,水一篇文章,推荐一些书籍。 在前一段时间,D瓜哥已经写了一个书单: 推荐几本 Java 并发编程的书。为了避免重复,上一个书单中推荐过的书籍,这次就不再重复推荐了。 每年十二个月,D瓜哥就推荐 12 本书,每个月读一本想必压力也不算大。 如何阅读一本书? D瓜哥在年初的时候,刚刚再次重读了这本书。而且,还写了一篇读书笔记: 《如何阅读一本书?》之读书笔记。 如果喜欢读书,那么这本书绝对应该是首先阅读的第一本书。一句话总结一下:用检视阅读的方法来快速筛选出你关注主题的书籍;用分析阅读的方法来吸收一本书的精华;用主题阅读的办法来对多本同一主题的书去伪存真,加工再输出。 远见 D瓜哥在去年年末写的年终总结 “告别 2019,迎接 2020” 中提到了这本书。考虑这本书的实用性和对自身发展的指导意义,所以决定再次推荐这本书。 在这本书中,作者将职业生涯分为:强势开局、聚焦长板和实现持续的影响力三个阶段。 在强势开局阶段,就像要开始一个汽车拉力赛,要努力加添燃料。 在聚焦长板阶段,要努力提高自己的核心竞争力,创造自己的制高点。 在实现持续的影响力阶段,则要优化长尾效应,让自己持续保持领先。 对于职业生涯有追求的小伙伴,尤其是在读大学生,一定要去尽早认真读一读这本书。 思考,快与慢 这是一本有关心理学方面的书籍。作者丹尼尔•卡尼曼因其与阿莫斯•特沃斯基在决策制定上的研究而荣获了 2002 年度的诺贝尔经济学奖。所以,这本书质量上肯定是有保证的。 这本书主要是介绍认知心理学的。作者在书中,把人的认知分为系统一和系统二。系统一是那种不需要思考的,已经固化在我们基因中的反应,比如看见危险会跑路等;而系统二,则是需要深入思考才能有所收获的事情,比如在新税法下,计算个人应该缴纳的个人所得税。两个系统相辅相成,时刻影响着我们的生活,但我们却有些熟视无睹。 穷查理宝典 提起查理·芒格,也许有些人不知道是谁。(看这篇文章的读者估计都了解)但是,他的搭档估计是人尽皆知,那就是世界股神沃伦·巴菲特。 虽然这本书不是查理·芒格书写的,里面的精华部分,却都是查理的演讲稿。通过这些演讲,你可以看到一个睿智的老人,如何在循循善诱地向你传授他的思维方法。查理给我们介绍了他的思维模型:逆向思维,多元思维模型,打造自己的核心圈,避免嫉妒效应,内部积分卡(用我们古人的话说就是反求诸己)等等。 社会性动物 D瓜哥是去年开始读这本书的,非常抱歉目前还没有读完。 这本书是讲述社会心理学的,讲述在这个社会中,人与人之间是如何相互影响的。举一个典型的例子:你思考过吗,什么样的广告最能打动你吗? 事实 比尔·盖茨也推荐了这本书。我也是最近刚刚开始读这本书。还没有读完。就不做过多评价了。用一个问题,勾引一下你的兴趣: 问题:在全世界所有的低收入国家里面,有多少百分比的女孩能够上完小学? 选项:A. 20% B. 40% C. 60% 想知道答案,就快点去读这本书吧。 最近更新:D瓜哥终于把这本书读完了:https://www.diguage.com/post/factfulness/[《事实》之读书笔记^]。 人类简史 坦白讲,这本书D瓜哥才读了一半。但是,作者最近发表的一篇文章: 尤瓦尔·赫拉利《冠状病毒之后的世界》,一个史学家站在历史发展的角度去看待疫情对世界发展的影响。由此可对赫拉利的思想窥得一斑。那么,如果感兴趣,他的成名大作《人类简史》就不得不读了。 最近因为疫情影响,在网上看到各种五毛的无脑言论,怼天怼地,仿佛中国要征服世界,征服宇宙一样,真是让人呵呵… 未来几十年时间里,中国未来寻求自身发展,还需要融入到整个世界经济中,在全世界产业链中,力争上游,占领高附加值的产业,比如芯片,5G,大飞机等等。怼这个,怼那个,只能让自己像二战时期的纳粹德国和日本,让自己四面树敌,最后被全世界群殴。 上帝掷骰子吗? 这是D瓜哥看过的,最好看的科普书,没有之一。 你知道爱因斯坦因为发表相对论,革了物理学的命;但是你知道吗?爱因斯坦也因为自己的顽固守旧阻碍过量子物理的发展。你知道普朗克首次提出量子的概念;但是,你了解吗?普朗克却是非常坚决地反对量子物理的发展。你了解薛定谔的猫到底是怎么回事吗?这些料都可以在这本书中读到。 作者通过各种各样的故事展示了量子物理学从无到有,颠覆整个经典物理,然后又重塑整个现代物理的过程。整个故事可谓波澜壮阔,跌宕起伏。 另外,从这本书中,我希望大家能读出科学的味道。曾经跟一个朋友在讨论,到底什么是科学?我举两个例子来说明: 关于光本质的三次论战 现在,上过高中物理的人都了解,光具有波粒二象性。说得更直白一些,光即是波,也是粒子。围绕光到底是波,还是粒子在历史上,曾经有过三次论战,参与论战的也不乏大家耳熟能详的物理大咖。 第一次的主角是牛顿牛爵爷,通过观察光的直线传播和反射现象,牛顿将光解释为粒子。考虑到牛顿在当时已经是成绩斐然的世界知名物理学家,他的观点在当时得到了广泛的认可。这是第一次论战。 第二次论战的主角是托马斯·杨,这位主角想必学过高中物理的童鞋,想必对双缝干涉实验都有印象。双缝干涉实验是高中物理中非常重要的一个实验,而且操作也很简单。双缝干涉实验就是托马斯·杨发明的。托马斯·杨完成了双缝干涉实验,有力地证明了光的波动性质。然后,大家开始普遍接受光的波动性学说。 你以为这就完了吗?然而并没有,最后踢出临门一脚的是又一个大名鼎鼎的物理学家:爱因斯坦。1905年,爱因斯坦发表论文,对于光电效应给出解释。他把光解释为即是波,也是粒子。至此,这个长达近三百年的争论才得以盖棺论定。爱因斯坦也因为这篇论文,获得了 1921 年度的诺贝尔物理学奖。 牛顿的经典力学 学过初中物理的童鞋,相关对牛顿运动三定理都很了解。从 1687 年,牛顿发表《自然哲学的数学原理》,在书中详细阐述了牛顿运动三定律。至此,两百多年时间里,《自然哲学的数学原理》几乎奠定了经典物理的基础。可谓无往不胜。 但是,在计算水星轨道时,总是出现微小的偏差。物理学家不断尝试来解决这个问题,却一直未能如愿。直道 1915 年爱因斯坦发表广义相对论,才正确地解释了水星近日点的反常进动。后来,经过广义相对论的进一步的发展,人们发现,牛顿运动定律只是在低速情况下,广义相对论的特例。