当前位置: 首页 > 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值最…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)

说明&#xff1a; 想象一下&#xff0c;你正在用eNSP搭建一个虚拟的网络世界&#xff0c;里面有虚拟的路由器、交换机、电脑&#xff08;PC&#xff09;等等。这些设备都在你的电脑里面“运行”&#xff0c;它们之间可以互相通信&#xff0c;就像一个封闭的小王国。 但是&#…...

云计算——弹性云计算器(ECS)

弹性云服务器&#xff1a;ECS 概述 云计算重构了ICT系统&#xff0c;云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台&#xff0c;包含如下主要概念。 ECS&#xff08;Elastic Cloud Server&#xff09;&#xff1a;即弹性云服务器&#xff0c;是云计算…...

树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法

树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作&#xff0c;无需更改相机配置。但是&#xff0c;一…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)

文章目录 1.什么是Redis&#xff1f;2.为什么要使用redis作为mysql的缓存&#xff1f;3.什么是缓存雪崩、缓存穿透、缓存击穿&#xff1f;3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...

2024年赣州旅游投资集团社会招聘笔试真

2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...

Linux简单的操作

ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...

Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; 目前2025年06月05日更新到&#xff1a; AI炼丹日志-28 - Aud…...

三体问题详解

从物理学角度&#xff0c;三体问题之所以不稳定&#xff0c;是因为三个天体在万有引力作用下相互作用&#xff0c;形成一个非线性耦合系统。我们可以从牛顿经典力学出发&#xff0c;列出具体的运动方程&#xff0c;并说明为何这个系统本质上是混沌的&#xff0c;无法得到一般解…...

[Java恶补day16] 238.除自身以外数组的乘积

给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O(n) 时间复杂度…...

OD 算法题 B卷【正整数到Excel编号之间的转换】

文章目录 正整数到Excel编号之间的转换 正整数到Excel编号之间的转换 excel的列编号是这样的&#xff1a;a b c … z aa ab ac… az ba bb bc…yz za zb zc …zz aaa aab aac…; 分别代表以下的编号1 2 3 … 26 27 28 29… 52 53 54 55… 676 677 678 679 … 702 703 704 705;…...