当前位置: 首页 > news >正文

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源码&#xff0c;读着读着发现里面还是有很多很好玩的东西在里面的&#xff0c;里面涉及到了大量的设计模式以及各种PostProcessor注入的过程&#xff0c;很好玩&#xff0c;也很复杂&#xff0c;本文就是记录一下我学习过程中的主干流程。 在开始我们源码解读…...

【STM32】入门(十四):FreeRTOS-任务

1、简述 FreeRTOS应用程序由一组独立的任务构成。 在任何时间点&#xff0c;应用程序中只能执行一个任务&#xff0c;FreeRTOS调度器负责决定所要执行的任务。 每个任务在自己的上下文中执行&#xff0c;不依赖于系统内的其他任务或 FreeRTOS的调度器本身。 FreeRTOS调度器负责…...

apscheduler 的基本介绍和使用

APScheduler有四大组件&#xff1a; 1、触发器 triggers &#xff1a; 触发器包含调度逻辑。每个作业都有自己的触发器&#xff0c;用于确定下一个任务何时运行。除了初始配置之外&#xff0c;触发器是完全无状态的。 有三种内建的trigger: &#xff08;1&#xff09;date: 特定…...

Oracle中merge Into的用法

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

JDK19下载、安装与测试的完整图文教程

一、下载JDK 1、官网获取&#xff1a;https://www.oracle.com/ 1.1 点击“Products”&#xff1b; 1.2 选择“Java”&#xff1b; 1.3 选择“Download Java”&#xff1b; 1.4 选择“Java downloads”&#xff0c;这里以最新版&#xff08;JDK19&#xff09;为例&#xff…...

Vector - CAPL - 获取相对时间函数

在自动化开发中&#xff0c;无论是CAN通信测试&#xff0c;还是网络管理测试&#xff0c;亦或是休眠唤醒等等存在时间相关的&#xff0c;都可能会使用相关的时间函数&#xff1b;今天主要介绍的就是获取当前时间&#xff0c;我们知道vector工具的最大优势就是稳定和精确度高&am…...

C++编程语言STL之unordered_map介绍

本文主要介绍 C 编程语言的 STL&#xff08;Standard Template Library&#xff09; 中 unordered_map 的相关知识&#xff0c;同时通过示例代码介绍 unordered_map 的常见用法。1 概述C标准库提供了四个无序关联容器&#xff08;unordered associated container&#xff09;&a…...

【独家】华为OD机试 - 最快检测效率-核酸(C 语言解题)

最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧文章目录 最近更新的博客使用说明本期…...

【Redis应用】基于Redis实现共享session登录(一)

&#x1f697;Redis应用学习第一站~ &#x1f6a9;本文已收录至专栏&#xff1a;数据库学习之旅 &#x1f44d;希望您能有所收获 &#x1f449;相关推荐&#xff1a;使用短信服务发送手机验证码进行安全校验 一.引入 ​ 在开发项目过程中&#xff0c;我们常常能碰到需要登录注…...

Android framework系列2 - Init进程

1、源码 入口&#xff1a;system/core/init/main.cpp2 流程图 https://note.youdao.com/s/EtnCswft 3、代码详解 主入口共三步&#xff0c;如流程图所示&#xff0c;我们主要看下最后一步 入口在init.cpp下&#xff0c;这个阶段主要来解析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类完成&#xff0c;部分关键源码&#xff1a;AutoConfiguration(after { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,ValidationAutoConf…...

画图说透 ZooKeeper如何保证数据一致性:选举和ZAB协议

1、zookeeper是什么&#xff1f; zookeeper能被各个牛逼的中间件项目中所依赖&#xff0c;已经说明了他的地位。一出手就是稳定的杀招。zookeeper是什么&#xff1f;官网中所说&#xff0c;zookeeper致力于开发和维护成为一个高度可靠的分布式协调器。 开局一张图&#xff0c;…...

错误异常捕获

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

js垃圾回收机制

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

YApi分析从NoSQL注入到RCE远程命令执行.md

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

【C++】stl_list介绍和实现,list和vector区别,list vector string 迭代器失效

本篇博客详细介绍list的实现&细节讲解&#xff0c;并且在文章末对list和vector&#xff0c;string进行区分和复习 list的基本结构就是双向带头循环链表&#xff0c;链表和顺序表的差别我们在前面数据结构的时候早就学过了&#xff0c;不再赘述 在使用stl库里面list时&…...

linux-kernel-ecmp-ipv4

当使用ip route add/del添加或者删除路由时&#xff0c;通过触发netlink发送信息到各协议路由系统注册的netlink处理函数&#xff0c;如add时调用函数为inet_rtm_newroute。Equal Cost Multi Path,在ip交换网络中存在到达同一目的地址的多条不同的路径&#xff0c;而且每条路径…...

蒙特卡洛树搜索(MTCS)

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

【Verilog】——Verilog简介

目录 1.简介 2.什么是HDL以及HDL的功能 3.Verilog和C语言的比较 4.Verilog的用途 5.数字系统的抽象层次 1.系统级 2.算法级 3.RTL级&#xff08;寄存器变换级&#xff09; 6.数字系统抽象层级 7.自顶向下的结构化设计方法 8.Verilog建模 9.Verilog概述 10.Verilog模块的基本…...

【Python从入门到进阶】10、流程控制语句-循环语句(for-while)

接上篇《9、流程控制语句-条件语句(if-else)》 上一篇我们学习了Python的控制流语句的概念&#xff0c;以及其中的条件语句(if/else)&#xff0c;本篇我们来学习控制流语句中的循环语句(for/while)。 一、Python中的循环 Python的循环结构就是让程序“杀个回马枪”&#xff0…...

超全的命令(代码)执行漏洞无回显的姿势总结(附带详细代码和测试分析过程)

目录 漏洞代码 突破方式 重定向 dnslog外部通信 burpsuite burpcollaborator外部通信 日志监听 netcat监听 反弹shell的各种姿势 漏洞代码 <?php shell_exec($_GET[a]); ?>这里使用了无回显的shell执行函数shell_exec&#xff0c;给html目录的权限是777 突破方…...

STM32MP157-Linux音频应用编程-简易语音助手

文章目录前言STM32MP157简易语音助手alsa-lib简介&#xff1a;移植alsa-lib库&#xff1a;libcurl库简介&#xff1a;移植libcurl库&#xff1a;API调用修改asrmain.c文件修改token.c文件录音文件IO打开音频文件硬件控制sysfs文件系统数据解析和控制多线程主循环实现效果及注意…...

Python-OpenCV图像处理:学习图像算术运算,如加减法、图像混合、按位运算,以及如何实现它们

目录 目标 图像添加 图像混合算法 按位运算 目标 学习对图像的几种算术运算,如加法、减法、位运算等。了解这些功能:cv.add()、...

并发编程——ReentrantLock

如果有兴趣了解更多相关内容&#xff0c;欢迎来我的个人网站看看&#xff1a;耶瞳空间 一&#xff1a;基本介绍 从Java 5开始&#xff0c;引入了一个高级的处理并发的java.util.concurrent包&#xff0c;它提供了大量更高级的并发功能&#xff0c;能大大简化多线程程序的编写…...

English Learning - L2 第 3 次小组纠音 [ʌ] [ɒ] [ʊ] [ɪ] [ə] [e] 2023.3.4 周六

English Learning - L2 第 3 次小组纠音 [ʌ] [ɒ] [ʊ] [ɪ] [ə] [e] 2023.3.4 周六共性问题小元音 [ʌ]小元音 [ɒ]小元音 [ʊ]小元音 [ɪ]小元音 [ə]小元音 [e]我的发音问题纠音过程共性问题 小元音 [ʌ] 口型容易偏大 解决办法&#xff1a;因为嘴角没有放松&#xff0c…...

STM32之关门狗

看门狗介绍在由单片机构成的微型计算机系统中&#xff0c;由于单片机的工作常常会受到来自外界电磁场的干扰&#xff0c;造成程序的跑飞&#xff0c;而陷入死循环&#xff0c;程序的正常运行被打断&#xff0c;由单片机控制的系统无法继续工作&#xff0c;会造成整个系统的陷入…...

Apollo控制部分1-- ControlComponent组件介绍

Apollo控制部分1-- ControlComponent组件介绍摘要一、ControlComponent1、启动文件解析2、ControlComponent()组件函数解析1&#xff09;ControlComponent::ControlComponent() 构造函数2&#xff09;ControlComponent::Init() 初始化函数&#xff08;执行一次&#xff09;3&am…...

0626-0631韩顺平Java Buffered字节处理流 学习笔记

如何去构建字节流package com.hspedu.outputstream_;import java.io.*;/*** author abner* version 1.0*/ public class BufferedCopy02 {public static void main(String[] args) {String srcFilePath "D:\\Users\\Pictures\\Camera Roll\\Pierre-Auguste_Renoir,_Le_Mo…...