Spring之基于注解方式实例化BeanDefinition(1)
最近开始读Spring源码,读着读着发现里面还是有很多很好玩的东西在里面的,里面涉及到了大量的设计模式以及各种PostProcessor注入的过程,很好玩,也很复杂,本文就是记录一下我学习过程中的主干流程。
在开始我们源码解读之前,我们先大致的归纳一下解析的全过程。
1. 首先就是每个spring的jar包都会提供各自的配置文件,包括注解,标签,处理类。
2. 然后就是根据我们的配置信息去解读我们自己的配置文件spring.xml,主要分为自定义标签和默认标签(<bean> 就是默认标签)
3. 最后就是解读这些信息,封装成 BeanDefinition对象,进行注册。
4. 最后介绍一下此阶段注册的几个PostProcessor接口,这部分内容是为我们实例化bean和DI注入做准备的,下一章我们会重点分析。
在我们逐步分解实例化beanDefinition之前,我先贴出时大体流程图,后面解读的过程会参照这张图进行讲解。
进入主题:
1. 每一个Spring的jar包都会配置好各种各样的处理类,这些类都提供了各种各样的处理器功能。比如说,spring想要管理maybatis,那么在spring管理mabatis的jar包中肯定会定义一些处理类,如下图:
而本篇我们是将spring基于注解方式实例化BeanDefinition的,那么我们都会基于扫描的方式进行
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:p="http://www.springframework.org/schema/p"xmlns:c="http://www.springframework.org/schema/c"xmlns:jack="http://www.xiangxueedu.com/schema/mytags"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-3.2.xsdhttp://www.xiangxueedu.com/schema/mytagshttp://www.xiangxueedu.com/schema/mytags.xsd"default-lazy-init="false"><context:component-scan base-package="com.xiangxue.jack" /></beans>
我们知道,扫描是基于 http://www.springframework.org/schema/context进行的,那么我们就打开spring的context相关 jar包,找到对应的处理类。
点进去以后,我们可以看到这个处理类只有一个方法,就是注册基于context标签下的各个标签的处理类,有点拗口,看一下代码就明白了
2. 上面步骤是一个基本的了解过程,下面我们就开始自己逐步debug分解过程,首先就是生成一个applicationContext的操作;
3. 进入ClassPathXMLApplicationContext的refresh()方法,这个方式是spring的核心
4. 我们关注一下obtainFreshBeanFactory()方法,就在refresh()内部调用。这个方法做了很多的事情,下方第一张图片有详细的comments进行解释
总结一下,其实就是把spring.xml文件生成一个document对象,然后进行逐步解析。
5. 解析document对象的时候,我们会判断是默认标签,还是自定义标签。比如在spring.xml中配置<bean id="" class=""></bean>, 这就是默认标签。 而<context:component-scan base-package="com.xiangxue.jack" />就是自定义标签。 本文针对的是基于注解的方式分析,所以关注的是自定义标签。
6. 我们首先会根据component-scan 标签,在spring..xml 中找到对应的URI
7. 基于上一步获取到的URI,拿到对应的上下文处理器,这个处理器就是我们第1步说的,在spring.handers文件中配置的信息,也就是 ContextNamespaceHandler。这一步我们需要进行详细的分析一下,进入到这个resolve方法中看一下:
感兴趣的话可以逐个点进去看一下具体。此处我需要再次强调一下初始化init方法,注册component-scan的流程。点进去看一下,我们发现
我们确认了,它就是初始化各个标签的扫描类的,并且会把这些扫描类放到缓存中,缓存是一个Map, 在它的父类NameSpaceHandlerSupport中
8.最后,我们会返回现在已经准备好的上下文处理器类,即ContextNamespaceHandler。进入主流程
9. 上一步,我们拿到了上下文处理器,即ContextNamespaceHandler。并且通过这个处理器实例化了各个子标签的处理类放在Map中。接下来会调用这个ContextNamespaceHandler的parase方法,进行解析操作。 这个方法是重点方法,我们需要点进去看
a. 首先会根据<context:component-scan base-package="com.xiangxue.jack" />配置,拿到base-package配置的目录。
b. 创建注解扫描器 ClassPathBeanDefinitionScanner,来扫描base-package配置的路径。 扫描到对应的class文件,生成beanDefinition进行注册。这一步是核心
b-1: 扫描到有注解的类并封装成BeanDefinition对象
b-1-1: 进入findCandidateComponents方法:它就是递归的方法,根据配置的路径找到所有的class文件,不管是有注解的,还是没有注解的。只要是这个路径下的class文件,统统找出来
b-1-2: 然后逐个遍历,判断当前的class文件是否有符合要求的注解。如果是符合要求的class, 就生成BeanDefinition对象进行收集
b-1-3: 这里有必要看一下这个判断方法 isCandidateComponent。了解一下它是如何去判断那个类是符合spring管理的。点进去
我们发现,只要这个类有与注解Component相关的信息,就是符合条件的class对象,就会被搜集起来。
最后,我们解释一下,什么是和@Component相关的呢? 其实,@Controller, @Service, @Repository都是与@Component相关的. 如何判断呢?只要找到对应的注解,打开发现接口上方有@Component相关信息即可。
b-1-4: 最后返回到所有搜集起来的符合条件的BeanDefinition的set集合
b2: 拿到这些有注解的,符合条件的beanDefinition,那么接下来就是处理这些BeanDefinition的事情了。
b2-1: 判断beanDefinition是否支持懒加载@Lazy @DependOn @Description @Primary等注解
其实,就是在BeanDefinition中,给对应的属性设置个值而已。当我们实例化对象的时候,我们在根据BeanDefinition中设置的这些值,做不同的逻辑判断而已。下一章章实例化的时候,我会分析一下这些注解
b2-2: 最后就是注册这些BeanDefinition了
b2-3: 进去看一下最终是如何注册的。
最后,我们发现,所谓的注册BeanDefinition,其实就是把所有的name都放在一个list集合中,把BeanDefinition按照 name -->BeanDefinition放在一个Map中。这个垃圾,搞了半天,就搞了2个集合,这就完了。
上面就是基于注解的方式,实例化BeanDefinition的全部过程,概括就是根据自定义标签,拿到URI,再根据URI拿到对应的上下文处理器ContextNameSpaceHandler,而这个处理类会实例化context下的各个子标签的各个处理器,再根据字标签拿到属于自己的子处理器。
执行ContextNameSpaceHandler处理类的resolve方法和parse方法。resolve方法就是负责初始化子标签的处理器的。而parse就是根据子标签处理器找满足条件的class文件,然后把这些符合条件的类生成beanDefinition,最后进行注册。就是放在2个集合中。最后打完收工,回家睡觉。
最后补充一下:我们在parse的最后一行,有个registerComponents方法。它是负责注册一些PostProcessor接口的。
那么为啥要注册这些接口呢? 因为在我们实例化对象的时候,我们会使用很多注解进行依赖注入,比如说@Autowired @Resource等等。而这些注解,都是由这些PostProceseor接口提供的。具体如何提供的,我们在下一章会针对@Autowired @Resource这两个注解进行重点分析。
此处, 我们只需要知道注册了一些PostProceseor即可,那具体注册了哪些PostProceseor呢?它们又具体负责哪些事呢?
每个PostTProcessor 作用,我们都可以点进去查看。如何进行查看呢?
下面我列出一些常用的注解支持类:
AutowiredAnnotationBeanPostProcessor 支持@Autowired @Value
CommonAnnotationBeanPostProcessor 支持 @PostConstruct @PreDestroy @Resource
ConfigurationClassPostProcessor 支持@Configuration, @Bean
我们都知道@PostConstruct相当于init-method,@PreDestroy相当于destory-method,其实,它们在beanDefinition中都是设置到相同的属性中的,这证明了它们就是一个东西
最后,分享一个甜点 BeanDefinitionRegistryPostProcessor
在第9步的b2-3中,我们谈到了注册BeanDefinition的过程
其实,如果一些对象,它们没有注解,也没有配置在spring.xml中,它们只是一个普通的java文件,或者是第三方jar提供的文件, 我们是否可以也交给spring进行管理呢?答案是可以的。
假设一个普通的java文件
package com.xiangxue.jack.postProcessor;public class Dao2 {private String name;private String id;public String getName() {return name;}public void setName(String name) {this.name = name;}public String getId() {return id;}public void setId(String id) {this.id = id;}@Overridepublic String toString() {return "name :" + name + " id :" + id;}
}
实现了BeanDefinitionRegistryPostProcessor的实现类,这个类需要交给spring管理
package com.xiangxue.jack.postProcessor;import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.stereotype.Component;@Component
public class MyBeanPostProcessor implements BeanDefinitionRegistryPostProcessor {@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {//生成beanGenericBeanDefinition beanDefinition = new GenericBeanDefinition();beanDefinition.setBeanClass(Dao2.class);//给Dao2属性值赋值,这样实例化完成以后就会有值了.MutablePropertyValues有很多方法,//也就意味着我们即使配置的类有错误,只要实现这个接口,我们依旧可以在类实例化之前,通过//对beanDefinition进行修改,从而达到修改类的目的MutablePropertyValues pValues = beanDefinition.getPropertyValues();pValues.addPropertyValue("name", "yyds");pValues.addPropertyValue("id", "测试001");//我们给beanDefinition起了个 名字叫 dao2, 然后注册到spring容器中registry.registerBeanDefinition("dao2", beanDefinition);}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}
}
测试类:
package com.xiangxue.jack;import com.xiangxue.jack.bean.MyTestBean2;
import com.xiangxue.jack.postProcessor.Dao2;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;//@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring.xml"})
public class MyTest {@Autowiredprivate ApplicationContext applicationContext;@Testpublic void myBean() {applicationContext = new ClassPathXmlApplicationContext("spring.xml");MyTestBean2 service = (MyTestBean2) applicationContext.getBean("myTestBean2");service.system();}@Testpublic void myBean2() {applicationContext = new ClassPathXmlApplicationContext("spring.xml");Dao2 dao2 = (Dao2) applicationContext.getBean("dao2");System.out.println(dao2.toString());}
}
测试结果:
这个甜点的原理,我会在下一章进行分析
相关文章:

Spring之基于注解方式实例化BeanDefinition(1)
最近开始读Spring源码,读着读着发现里面还是有很多很好玩的东西在里面的,里面涉及到了大量的设计模式以及各种PostProcessor注入的过程,很好玩,也很复杂,本文就是记录一下我学习过程中的主干流程。 在开始我们源码解读…...

【STM32】入门(十四):FreeRTOS-任务
1、简述 FreeRTOS应用程序由一组独立的任务构成。 在任何时间点,应用程序中只能执行一个任务,FreeRTOS调度器负责决定所要执行的任务。 每个任务在自己的上下文中执行,不依赖于系统内的其他任务或 FreeRTOS的调度器本身。 FreeRTOS调度器负责…...
apscheduler 的基本介绍和使用
APScheduler有四大组件: 1、触发器 triggers : 触发器包含调度逻辑。每个作业都有自己的触发器,用于确定下一个任务何时运行。除了初始配置之外,触发器是完全无状态的。 有三种内建的trigger: (1)date: 特定…...

Oracle中merge Into的用法
Oracle中merge Into的用法 使用场景 在操作数据库时,数据存在的情况下,进行update操作;不存在的情况下,进行insert操作;在Oracle数据库中,能够使用merge into来实现。 基本语法 merge into table_name …...

JDK19下载、安装与测试的完整图文教程
一、下载JDK 1、官网获取:https://www.oracle.com/ 1.1 点击“Products”; 1.2 选择“Java”; 1.3 选择“Download Java”; 1.4 选择“Java downloads”,这里以最新版(JDK19)为例ÿ…...

Vector - CAPL - 获取相对时间函数
在自动化开发中,无论是CAN通信测试,还是网络管理测试,亦或是休眠唤醒等等存在时间相关的,都可能会使用相关的时间函数;今天主要介绍的就是获取当前时间,我们知道vector工具的最大优势就是稳定和精确度高&am…...
C++编程语言STL之unordered_map介绍
本文主要介绍 C 编程语言的 STL(Standard Template Library) 中 unordered_map 的相关知识,同时通过示例代码介绍 unordered_map 的常见用法。1 概述C标准库提供了四个无序关联容器(unordered associated container)&a…...
【独家】华为OD机试 - 最快检测效率-核酸(C 语言解题)
最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧文章目录 最近更新的博客使用说明本期…...

【Redis应用】基于Redis实现共享session登录(一)
🚗Redis应用学习第一站~ 🚩本文已收录至专栏:数据库学习之旅 👍希望您能有所收获 👉相关推荐:使用短信服务发送手机验证码进行安全校验 一.引入 在开发项目过程中,我们常常能碰到需要登录注…...

Android framework系列2 - Init进程
1、源码 入口:system/core/init/main.cpp2 流程图 https://note.youdao.com/s/EtnCswft 3、代码详解 主入口共三步,如流程图所示,我们主要看下最后一步 入口在init.cpp下,这个阶段主要来解析init.rc并执行此文件下的命令 看到…...

2023年“网络安全”赛项江苏省淮安市选拔赛 任务书
任务书 一、竞赛时间 共计3小时。 二、竞赛阶段 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 第一阶段单兵模式系统渗透测试 任务一 服务器内部信息获取 任务二 网站渗透测试 任务三 Linux系统渗透提权 任务四 Web渗透测试 第二阶段分组对抗 备战阶段 攻防对抗准备工作 系统加…...
2023年Wireshark数据包分析——wireshark0051.pcap
Wireshark数据包分析 任务环境说明: 服务器场景:FTPServer220223服务器场景操作系统:未知(关闭连接)FTP用户名:wireshark0051密码:wireshark0051从靶机服务器的FTP上下载wireshark0051.pcap数据包文件,找出黑客获取到的可成功登录目标服务器FTP的账号密码,并将黑客获…...
SpringMVC的自定义配置和自动化配置
SpringBoot的自动配置MVC处理加载逻辑基于Spring Boot的MVC自动化配置由WebMvcAutoConfiguration类完成,部分关键源码:AutoConfiguration(after { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,ValidationAutoConf…...

画图说透 ZooKeeper如何保证数据一致性:选举和ZAB协议
1、zookeeper是什么? zookeeper能被各个牛逼的中间件项目中所依赖,已经说明了他的地位。一出手就是稳定的杀招。zookeeper是什么?官网中所说,zookeeper致力于开发和维护成为一个高度可靠的分布式协调器。 开局一张图,…...

错误异常捕获
1、React中错误异常捕获 在 React 中,可以通过 Error Boundaries(错误边界)来捕获错误异常。Error Boundaries 是一种 React 组件,它可以在其子组件树的渲染期间捕获 JavaScript 异常,并且可以渲染出备用 UI。React 提…...

js垃圾回收机制
内存的生命周期 ]S环境中分配的内存,一般有如下生命周期 1.内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存 2.内存使用:即读写内存,也就是使用变量、函数等 3.内存回收: 使用完毕,由垃圾回收器自动回收不再…...

YApi分析从NoSQL注入到RCE远程命令执行.md
0x00 前提 这个是前几个月的漏洞,之前爆出来发现没人分析就看了一下,也写了一片 Nosql注入的文章,最近生病在家,把这个写一半的完善一下发出来吧。 0x01 介绍 YApi是一个可本地部署的、打通前后端及QA的、可视化的接口管理平台…...

【C++】stl_list介绍和实现,list和vector区别,list vector string 迭代器失效
本篇博客详细介绍list的实现&细节讲解,并且在文章末对list和vector,string进行区分和复习 list的基本结构就是双向带头循环链表,链表和顺序表的差别我们在前面数据结构的时候早就学过了,不再赘述 在使用stl库里面list时&…...
linux-kernel-ecmp-ipv4
当使用ip route add/del添加或者删除路由时,通过触发netlink发送信息到各协议路由系统注册的netlink处理函数,如add时调用函数为inet_rtm_newroute。Equal Cost Multi Path,在ip交换网络中存在到达同一目的地址的多条不同的路径,而且每条路径…...

蒙特卡洛树搜索(MTCS)
一、目标 一种启发式的搜索算法,在搜索空间巨大的场景下比较有效 算法完成后得到一棵树,这棵树可以实现:给定一个游戏状态,直接选择最佳的下一步 二、算法四阶段 1、选择(Selection) 父节点选择UCB值最…...

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造,完美适配AGV和无人叉车。同时,集成以太网与语音合成技术,为各类高级系统(如MES、调度系统、库位管理、立库等)提供高效便捷的语音交互体验。 L…...

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...
【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15
缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下: struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...

STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...

select、poll、epoll 与 Reactor 模式
在高并发网络编程领域,高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表,以及基于它们实现的 Reactor 模式,为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。 一、I…...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...
Java毕业设计:WML信息查询与后端信息发布系统开发
JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发,实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构,服务器端使用Java Servlet处理请求,数据库采用MySQL存储信息࿰…...
LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》
这段 Python 代码是一个完整的 知识库数据库操作模块,用于对本地知识库系统中的知识库进行增删改查(CRUD)操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 📘 一、整体功能概述 该模块…...