Spring之基于注解方式实例化BeanDefinition(1)

news2025/1/18 20:15:00

最近开始读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/beans
	http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/aop
	http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
    http://www.xiangxueedu.com/schema/mytags
    http://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分解过程,首先就是生成一个applicationContent的操作;

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,拿到对应的NameSapceHandler处理器,这个处理器就是我们第1步说的,在spring.handers文件中配置的信息,也就是 ContextNamespaceHandler。这一步我们需要进行详细的分析一下,进入到这个resolve方法中看一下:

 感兴趣的话可以逐个点进去看一下具体。此处我需要再次强调一下初始化init方法,注册component-scan的流程。点进去看一下,我们发现

 我们确认了,它就是初始化各个标签的扫描类的,并且会把这些扫描类放到缓存中,缓存是一个Map, 在它的父类NameSpaceHandle人Support中

8.最后,我们会返回现在已经准备好的context的处理类。进入主流程

 9.  上一步,我们拿到了context的处理类,即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。点进去

 我们发现,只要这个类有与注解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拿到对应的context的处理类,而这个处理类会实例化context下的各个子标签的各个处理器,再根据字标签拿到属于自己的子处理器。 执行context处理类的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;
    }

    @Override
    public 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 {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        //生成bean
        GenericBeanDefinition 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);
    }

    @Override
    public 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 {

    @Autowired
    private ApplicationContext applicationContext;

    @Test
    public void myBean() {
        applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        MyTestBean2 service = (MyTestBean2) applicationContext.getBean("myTestBean2");
        service.system();
    }

    @Test
    public void myBean2() {
        applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        Dao2 dao2 = (Dao2) applicationContext.getBean("dao2");
        System.out.println(dao2.toString());
    }
}

测试结果:

这个甜点的原理,我会在下一章开头就进行分析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/386991.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023年湖北武汉中级工程师怎么申请?申报渠道有哪些?启程别

2023年湖北武汉中级工程师怎么申请?申报渠道有哪些&#xff1f;启程别 武汉市中级工程师怎么报名&#xff1f;很多人不知道中级职称怎么申请&#xff0c;在哪里申请&#xff0c;那么启程别来告诉大家&#xff0c;启程别是谁&#xff0c;进入百度搜索启程别就知道啦 武汉中级工…

【学习Docker(七)】详细讲解Jenkins部署SpringCloud微服务项目,Docker-compose启动

Jenkins部署SpringCloud微服务项目&#xff0c;Docker-compose启动 座右铭&#xff1a;《坚持有效输出&#xff0c;创造价值无限》 本文介绍使用Jenkins部署SpringCloud微服务项目&#xff0c;Docker-compose启动。 之前写过安装Jenkins的过程&#xff0c;这里就不写安装细节了…

[oeasy]python0099_雅达利大崩溃_IBM的开放架构_兼容机_oem

雅达利大崩溃 回忆上次内容 个人计算机浪潮已经来临 苹果公司迅速发展微软公司脱离mits准备做纯软件公司IBM用大型机思路制作的5100惨败 Commodore 64 既做计算机又做游戏机 计算机行业和游戏行业 跟随着底层技术不断迭代已经进入了战乱纷纷的年代最终又会如何呢&#xff1f…

31 openEuler使用LVM管理硬盘-管理物理卷

文章目录31 openEuler使用LVM管理硬盘-管理物理卷31.1 创建物理卷31.2 查看物理卷31.3 修改物理卷属性31.4 删除物理卷31 openEuler使用LVM管理硬盘-管理物理卷 31.1 创建物理卷 可在root权限下通过pvcreate命令创建物理卷。 # pvcreate [option] devname ...其中&#xff1…

【linux工具】Tmux简明教程

A Quick and Easy Guide to tmux (hamvocke.com) 一、说明 我们常用ubuntu&#xff0c;用altctlT实现终端窗口弹出。然而当需要多个终端一起工作&#xff0c;切换的效率就成了问题。 tmux是ubuntu下&#xff0c;终端窗口工具。该工具能实现多窗口分屏显示、多个会话在一个终端…

【13】linux命令每日分享——groupadd建立组

大家好&#xff0c;这里是sdust-vrlab&#xff0c;Linux是一种免费使用和自由传播的类UNIX操作系统&#xff0c;Linux的基本思想有两点&#xff1a;一切都是文件&#xff1b;每个文件都有确定的用途&#xff1b;linux涉及到IT行业的方方面面&#xff0c;在我们日常的学习中&…

时间复杂度的计算

个人主页&#xff1a;平行线也会相交 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 平行线也会相交 原创 收录于专栏【数据结构初阶&#xff08;C实现&#xff09;】 文章目录123456789时间复杂度&#xff08;就是一个函数&#xff09;的计算&#xff0c;…

Flutter开发圆形计时进度条RingProgressBar

演示 先看效果图&#xff1a; 由于无法截取动态图&#xff0c;我就截过程中的两张图片表达了&#xff0c;我想应该能看得懂。 功能1.设置进度条半径 2.设置进度条宽度 3.设置进度条最大值 4.设置进度条背景色以及前景色 5.是否显示进度条文字 6.文字样式设置 7.点击进度条和进…

机器学习100天(三十七):037 朴素贝叶斯-挑个好西瓜!

《机器学习100天》完整目录:目录 机器学习100天,今天讲的是:朴素贝叶斯-挑个好西瓜! 红色石头已经了解了贝叶斯定理和朴素贝叶斯法,接下来已经可以很自信地去买瓜了。买瓜之前,还有一件事情要做,就是搜集样本数据。红色石头通过网上资料和查阅,获得了一组包含 10 组样…

打印名片-课后程序(Python程序开发案例教程-黑马程序员编著-第二章-课后作业)

实例3&#xff1a;文本进度条 进度条以动态方式实时显示计算机处理任务时的进度&#xff0c;它一般由已完成任务量与剩余未完成任务量的大小组成。本实例要求编写程序&#xff0c;实现图1所示的进度条动态显示的效果。 下载中下载完成图1文本进度条 实例分析 在本实例中可以将…

【java】alibaba Fastjson --全解史上最快的JSON解析库

文章目录前序Fastjson 简介Fastjson 的优点速度快使用广泛测试完备使用简单功能完备下载和使用将 Java 对象转换为 JSON 格式JSONField创建 JSON 对象JSON 字符串转换为 Java 对象使用 ContextValueFilter 配置 JSON 转换使用 NameFilter 和 SerializeConfigFastjson 处理日期F…

如何使用SaleSmartly进行Facebook Messenger 营销、销售和支持

如何使用SaleSmartly&#xff08;ss客服&#xff09;进行Facebook Messenger 营销、销售和支持上篇文章我们讲了什么是Facebook Messenger CRM以及获得Facebook Messenger CRM的注意事项&#xff0c;现在你有更多时间与客户聊天&#xff0c;让我们看看你如何使用SaleSmartly&am…

缓存穿透和缓存击穿、缓存雪崩

一、Redis作为一个缓存中间件是如何工作的&#xff1f;架构图如下过程如下 客户端发起一个查询请求的时候&#xff0c;首先去缓存中查询&#xff0c;如果数据在缓存中存在&#xff0c;则直接将缓存中数据返回给客户端&#xff1b;如果数据在缓存中不存在&#xff0c;则继续查询…

嵌入式知识点-SPI通讯

该文原自 &#xff1a; 正点原子 01 SPI概述 SPI (Serial Peripheralinterface),顾名思义就是串行外围设备接口。SPI是一种高速的&#xff0c;全双工&#xff0c;同步的通信总线&#xff0c;并且在芯片的管脚上只占用四根线&#xff0c;节约了芯片的管脚&#xff0c;同…

网络营销培训完能达到什么水平?学完能创业吗?

网络营销本身就是一门创业的技术&#xff0c;很多人学习网络营销&#xff0c;往往担心学完以后技术达不到&#xff0c;再工作几年才可以创业&#xff0c;实际这是错误的理解&#xff0c;那么&#xff0c;网络营销培训完能达到什么水平&#xff1f;新手学员参加网络营销培训&…

MAX 10 10M50 FPGA(10M50DDF256I7G)10M50DDF484C8G/10M50DDF484I7G

MAX 10器件是单芯片、非易失性低成本可编程逻辑器件(pld)&#xff0c;用于集成最优的系统组件集。MAX 10设备的亮点包括&#xff1a;内部存储双配置闪存用户闪存即时支持集成模数转换器(adc)支持Nios II单芯片软核处理器该器件设备是系统管理、I/O扩展、通信控制平面、工业、汽…

Spark BlockManager数据存储与管理机制

BlockManager是整个Spark底层负责数据存储与管理的一个组件&#xff0c;Driver和Executor的所有数据都由对应的BlockManager进行管理。 Driver上有BlockManagerMaster&#xff0c;负责对各个节点上的BlockManager内部管理的数据的元数据进行维护&#xff0c;比如block的增删改等…

SpringBoot如何自定义一个starter

SpringBoot starter&#xff0c;大家应该在平常写项目中应该非常熟悉&#xff0c;很多依赖都会提供集成SpringBoot的依赖&#xff0c;这样我们用起来就非常顺手&#xff0c;开箱就能用&#xff0c;那如何自定义一个starter呢&#xff1f; SpringBoot starter SpringBoot中的一…

【C++初阶】2. 类和对象_1

1. 面向过程和面向对象的初步认识 2. 类的引入 C语言结构体中只能定义变量&#xff0c;在C中&#xff0c;结构体内不仅可以定义变量&#xff0c;也可以定义函数。比如&#xff1a; 之前在数据结构初阶中&#xff0c;用C语言方式实现的栈&#xff0c;结构体中只能定义变量&#…

Matlab深度学习实战二:AlexNet图像分类篇提供花分类这里以分二类演示且matlab提供模型框架详细操作流程

1.花数据集简介下载与准备 2.matlab搭建模型 3.matlab软件的操作过程&#xff1a; &#xff08;1&#xff09;界面操作 &#xff08;2&#xff09;深度学习设计器使用 &#xff08;3&#xff09;图像数据导入 &#xff08;4&#xff09;训练可视化 一、花数据集简介下载与准备…