Spring 学习源码的基础 核心原理与核心概念

news2025/1/16 17:04:53

文章目录

  • 核心原理
    • AnnotationConfigApplicationContext
    • IoC 容器加载流程
    • Spring 中如何创建一个对象
    • Bean 的创建过程 (生命周期)
    • 单例 原型
    • 推断构造方法
    • 依赖注入
    • AOP 动态代理
    • 判断是否需要 AOP 的大致流程
    • CGLib 做 AOP 的大致流程
    • 事务
    • 事务代理对象执行方法的流程
    • 事务注解排至失效的原因
    • 为何下方加了 @Configuration 事务才能生效
  • 核心概念
    • BeanDefinition
    • BeanDefinitionReader
      • AnnotatedBeanDefinitionReader
      • XmlBeanDefinitionReader
      • ClassPathBeanDefinitionScanner
    • BeanFactory
      • DefaultListableBeanFactory
      • ApplicationContext


核心原理

AnnotationConfigApplicationContext

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) context.getBean("userService");
userService.test();
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService) context.getBean("userService");
userService.test();

我们很少按照上面的方式使用 Spring, 而是使用 Spring MVC, 或者 Spring Boot, 但是它们本质上都是基于这种方式的, 都需要在内部去创建一个 ApplicationContext

  • SpringBoot 创建的是 AnnotationConfigApplicationContext
  • SpringMVC 创建的是 XmlWebApplicationContext, 和 ClassPathXmlApplicationContext 类似都是基于 xml 的

AnnotationConfigApplicationContext 是研究学习的主类

IoC 容器加载流程

IoC容器加载流程可以分成两个步骤

  • 准备, 注册一些底层基础架构工具类, 在容器初始化流程中使用, 然后注册配置类, 然后刷新容器
  • 扫描, 将配置的各种 Bean 解析成为 BeanDefinition, 存入 BeanDefinitionMap
  • 遍历 BeanDefinitionMap, 生产单例, 并缓存到单例池 singletonObjects

Spring 中如何创建一个对象

new AnnotationConfigApplicationContext(Config.class) 的过程就是 IoC 容器的加载流程, 扫描生成 BeanDefinition, 遍历生成单例 Bean. 最后人为调用 getBean(beanName) 去缓存中拿到对应的 Bean

Bean 的创建过程 (生命周期)

class -> 反射newInstance -> 原始对象 -> 依赖注入(属性赋值) -> 一堆Aware -> 初始化前(@PostConstruct) -> 初始化(InitailizedBean) -> 初始化后(AOP) -> 代理对象(代理对象.target=原始对象) -> Bean

  • 实例化, 通过反射调用类的某个构造方法创建对象, 如果有多个构造方法, 会进行选择, 叫做推断构造方法
  • 依赖注入(属性赋值), 遍历对象内的字段, 有 @Autowired 的自动赋值
  • Aware 织入, 判断对象是否是(即类是否实现了) BeanNameAware, ApplicationContextAware 等各种 Aware, 是的话就强转调用结构定义的 set 方法
  • @PostConstruct 处理, 判断是否有方法有该注解, 有的话则执行该方法
  • InitializingBean 处理, 判断对象是否是该接口的实例, 是的话则执行接口定义的 afterPropertiesSet 方法
  • 自定义的 init-method 方法
  • 判断是否需要 AOP, 不需要时返回当前对象, 需要时则生成代理对象, 并返回
  • 如果是单例 Bean 则存入单例池

单例 原型

Bean 的作用域分为单例和原型两种

  • 单例: 每次 getBean 拿到的都是同一个对象, 首次创建后就会缓存到单例池
  • 原型: 每次 getBean 都会重新创建一次, 不会放到单例池

懒加载的 Bean 在 IoC 容器加载时不会被创建和缓存, 在使用时才会创建和缓存

推断构造方法

实例化时默认使用无参构造器(写了有参默认就没有无参了, 除非显式定义)

有无参构造器就使用无参构造器, 没无参构造器则判断有几个有参构造器, 只有一个的话就使用这一个有参构造器, 有多个的话因为不能确认使用哪个, 所以报错找不到默认无参构造器

有多个有参构造器的话, 也可以加 @Autowired 来指定使用哪个有参构造器

有参构造器的参数对象哪里来?

  • 先根据类型到容器中找, 如果只有一个则直接使用, 如果有多个则根据名称来过滤, 如果又匹配名称的则直接使用, 如果没有匹配的, 则报错

依赖注入

先按类型到容器中过滤, 匹配的如果只有一个则直接使用, 多个则再按名称匹配, 有匹配到的直接使用, 没有则报错

AOP 动态代理

在创建 Bean 的最后一步, 会判断是否需要做 AOP, 需要则做动态代理

判断是否需要 AOP 的大致流程

  • 找出所有切面 Bean
  • 遍历其中每个方法, 判断是否写了 @Before, @After 等注解
  • 如果写了, 则判断其所对应的 PointCut 和当前创建的 Bean 是否匹配
  • 如果匹配, 则说明当前 Bean 需要做 AOP

CGLib 做 AOP 的大致流程

  • 生成继承原类的代理类, 代理类持有原类对象 target. 注意, 原类对象是经过 Bean 创建流程的, 包括依赖注入, 初始化等流程
  • 代理类中覆盖原类中的方法, 最终会调用到 target 的对应方法, 但是会在前后把切面逻辑都加上, 大致如下
  • 最后创建 Bean 返回的是持有原始对象的代理对象
public class UserService {
	@Autowired
	private OrderService orderService;
	public void test() {
		sout;
	}
}
class UserServiceProxy extend UserService {
	UserService target;
	public void test() {
		// @Before 的逻辑
		target.test();
		// @After 的逻辑
	}
}

代理类继承自原始类, 所以原始类的字段在代理类中也有, 但是 Spring 并不会为代理类做依赖注入, 因为没有必要

代理对象仅仅用于强化原始对象的某方法, 如果在切面逻辑中需要用到原始对象的依赖注入的字段, 也可以通过 JoinPoint.getTarget() 拿到原始对象来操作, 而原始对象中各字段已经做过依赖注入了

事务

某方法添加了 @Transactional 注解后, 会在 AOP 阶段给本类生成代理类和代理对象

事务代理对象执行方法的流程

  • 判断当前方法上有没有 @Transactional 注解
  • 有的话由事务管理器创建一个数据库连接 (此连接不是来自连接池?)
  • 设置连接的 autoCommit 为 false (无事务注解时, 连接是从连接池获取, 每执行一条 SQL 自动提交一次)
  • 执行方法, 执行方法中的 SQL
  • 调用连接的提交或回滚方法

事务注解排至失效的原因

事务是否会失效, 就看执行 @Transactional 注解方法的是哪个对象

  • 代理对象: 不会失效
  • 原始对象: 失效, 因为执行的就是普通方法, 没有代理对象做的强化了

最常见的例子, 类中有两个事务方法 a 和 b, 而 a 中会调用 b, 单独执行 a 和 b 事务都不会失效, 但是在 a 中执行 b 时, b 的事务注解上的配置会失效

因为执行 a 的流程是这样的, 拿到类的代理对象, 执行其 a, 先走切面逻辑, 创建连接, 设置不自动提交, 然后才执行 target.a 即原始对象的 a 方法, 此时的主体是原始对象而非代理对象. 执行到 b 方法时, 本质是 this.b, 主体还是原始对象, 并没有切面逻辑, 所以在 a 里面的 b 方法的事务注解配置都会失效

当然还有很多其他原因, 需要具体分析

其他的 AOP 失效很多也是一样的原因, 都是自调用导致的

有解决办法, 就是自引用, 类中依赖注入自身 self, 此时的 self 是代理对象, 在 a 中调用 b 的时候, 用 self.b, 这样主体是代理对象, 有切面强化逻辑, b 的事务配置就会生效了

为何下方加了 @Configuration 事务才能生效

@Configuration
public class Config {
	@Bean
	public TransactionManager transationManager() {
		return new DataSourceTransactionManager(dataSource());
	}
	@Bean
	public JdbcTemplate jdbcTemplate() {
		return new JdbcTemplate(dataSource())
	}
	@Bean
	public DataSource dataSource() {
		return new DataSource();
	}
}
  • 不加时, 调用两次 dataSource() 生成两个不同的数据源, 最终事务管理器和模板使用了不同的数据源
  • 加时, 会有特殊的处理, dataSource() 会被认为是一个 Bean, 传入两者的是同一个对象

在 JdbcTemplate 中获取连接时, 会检查当前是否为事务环境, 是的话会从 TransactionSynchronizationManager.getResource(dataSource); 中获取线程绑定的连接, 即事务管理器创建的那个连接, 需要使用同一个数据源对象才能拿到同一个连接, 这样事务管理器的提交和回滚操作才会对 JdbcTemplate 生效

核心概念

Spring 源码里有很多抽象和工具, 需要提前有一定了解, 读源码时能轻松一些

BeanDefinition

BeanDefinition 用来记录 Bean 配置的各种信息

  • class,表示Bean类型
  • scope,表示Bean作用域,单例或原型等
  • lazyInit:表示Bean是否是懒加载
  • initMethodName:表示Bean初始化时要执行的方法
  • destroyMethodName:表示Bean销毁时要执行的方法
  • 还有很多…

定义 Bean 的方式有申明式和编程式两种, 通过各种方式定义的 Bean 最终都会被解析为 BeanDefinition 并缓存起来

  • 申明式:
    • < bean/>
    • @Bean
    • @Component(@Service,@Controller)
  • 编程式
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
    
    // 生成一个BeanDefinition对象,并设置beanClass为User.class,并注册到ApplicationContext中
    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
    beanDefinition.setBeanClass(User.class);
    context.registerBeanDefinition("user", beanDefinition);
    
    System.out.println(context.getBean("user"));
    

BeanDefinitionReader

用于根据某些规则将资源解析成为 BeanDefinition

AnnotatedBeanDefinitionReader

可以将某个类解析成为 BeanDefinition, 包括类上的注解(@Conditional,@Scope、@Lazy、@Primary、@DependsOn、@Role、@Description)

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

AnnotatedBeanDefinitionReader reader = new AnnotatedBeanDefinitionReader(context);

// 将User.class解析为BeanDefinition
reader.register(User.class);

System.out.println(context.getBean("user"));

XmlBeanDefinitionReader

可以解析 < bean/> 标签配置的 Bean

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(context);
int i = reader.loadBeanDefinitions("spring.xml");

System.out.println(context.getBean("user"));

ClassPathBeanDefinitionScanner

扫描器, 但是它的作用和 BeanDefinitionReader 类似, 它可以进行扫描, 扫描某个包路径, 对扫描到的类进行解析

如果扫描到的类上存在 @Component 注解, 那么就会把这个类解析为一个 BeanDefinition

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.refresh();

ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
scanner.scan("com.coder");

System.out.println(context.getBean("userService"));

BeanFactory

Spring 容器的根接口, Bean 工厂, 负责创建 Bean 和获取 Bean, 提供各种 getBean() 方法的定义

DefaultListableBeanFactory

BeanFactory 有一个最核心的实现类 DefaultListableBeanFactory, 可以直接当作 BeanFactory 来使用, 可以替代 AnnotationConfigApplicationContext 来使用, 就是功能会少一点而已

DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(User.class);

beanFactory.registerBeanDefinition("user", beanDefinition);

System.out.println(beanFactory.getBean("user"));

DefaultListableBeanFactory

DefaultListableBeanFactory 的架构体系如上, 有很多接口(能力)和类

  • interface AliasRegistry: 支持别名功能, 定义了 alias 的注册/获取/判断/移除等功能, 在这里支持一个 BeanDefinition / Bean 有多个名称的功能
  • class SimpleAliasRegistry implements AliasRegistry, 维护着 Map<String, String> aliasMap, 在这里是 alias 与 beanName 的多对一关系, 便于从别名找到原名(别名也能起别名), 当然也可以从原名找到所有别名
  • interface BeanDefinitionRegistry: 定义了 BeanDefinition 的注册/获取/存在/移除等功能
  • interface SingletonBeanRegistry: 定义了单例 Bean 的注册/存在/获取/获取数量/获取名称等功能
  • interface BeanFactory: 定义了 Bean 的获取/存在/获取别名/判断作用域等功能
  • interface ListableBeanFactory extends BeanFactory: 扩展了 BeanFactory, 提供了方法用于枚举和检索容器中的 bean 实例, 可以方便地获取所有 bean 的名称、按类型获取 bean、按条件检索 bean 等操作
  • interface HierarchicalBeanFactory extends BeanFactory: 扩展了 BeanFactory, 提供了层次化的容器结构和继承机制, 每个子容器可以独立管理自己的 bean 定义和实例。子容器可以继承父容器中定义的 bean,并可以在子容器中覆盖或扩展父容器中的 bean 定义。可以实现更好的模块化和组织化,并灵活管理和定制 bean 的定义和作用域
  • interface AutowireCapableBeanFactory extends BeanFactory: 扩展了 BeanFactory, 提供了自动装配 bean 的能力, 可以实现依赖注入、自动装配和解耦等功能
  • interface ConfigurableBeanFactory extends HierarchicalBeanFactory, SingletonBeanRegistry, 允许在运行时对 bean 工厂进行配置, 设置各种属性和配置选项, 添加了设置父BeanFactory、类加载器(表示可以指定某个类加载器进行类的加载)、设置Spring EL表达式解析器(表示该BeanFactory可以解析EL表达式)、设置类型转化服务(表示该BeanFactory可以进行类型转化)、可以添加BeanPostProcessor(表示该BeanFactory支持Bean的后置处理器),可以合并BeanDefinition,可以销毁某个Bean等等功能
  1. DefaultSingletonBeanRegistry:它是一个类,实现了SingletonBeanRegistry接口,拥有了直接注册、获取某个单例Bean的功能

  2. FactoryBeanRegistrySupport:支持了FactoryBean的功能

  3. AutowireCapableBeanFactory:是直接继承了BeanFactory,在BeanFactory的基础上,支持在创建Bean的过程中能对Bean进行自动装配

  4. AbstractBeanFactory:实现了ConfigurableBeanFactory接口,继承了FactoryBeanRegistrySupport,这个BeanFactory的功能已经很全面了,但是不能自动装配和获取beanNames

  5. ConfigurableListableBeanFactory:继承了ListableBeanFactory、AutowireCapableBeanFactory、ConfigurableBeanFactory

  6. AbstractAutowireCapableBeanFactory:继承了AbstractBeanFactory,实现了AutowireCapableBeanFactory,拥有了自动装配的功能

  7. DefaultListableBeanFactory:继承了AbstractAutowireCapableBeanFactory,实现了ConfigurableListableBeanFactory接口和BeanDefinitionRegistry接口,所以DefaultListableBeanFactory的功能很强大

ApplicationContext

BeanFactory 有一个最核心的子接口 ApplicationContext, 其定义如下

public interface ApplicationContext 
extends EnvironmentCapable, 
ListableBeanFactory, 
HierarchicalBeanFactory, 
MessageSource, 
ApplicationEventPublisher, 
ResourcePatternResolver

在这里插入图片描述

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

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

相关文章

Docker安装并配置Node-Exporter

Linux下安装Docker请参考&#xff1a;Linux安装Docker Node-Exporter简介 Exporter是Prometheus的指标数据收集组件。它负责从目标Jobs收集数据&#xff0c;并把收集到的数据转换为Prometheus支持的时序数据格式。 和传统的指标数据收集组件不同的是&#xff0c;他只负责收集…

Linux的Man Page知识记录

Man&#xff08;short for manual&#xff09; Page是Unix和Linux操作系统中的一个重要文档&#xff0c;提供命令、函数、系统调用等的详细介绍和使用说明。它是以纯文本的形式出现&#xff0c;通常在终端&#xff08;terminal&#xff09;中使用man命令访问。Man Page按照章节…

语言、习俗与成功:海外网红营销的文化敏感性

随着全球互联网的普及和社交媒体的兴起&#xff0c;海外网红营销正成为越来越多品牌和企业的选择。然而&#xff0c;要在海外市场取得成功&#xff0c;单纯的翻译内容和机械式的推广已经远远不够&#xff0c;文化敏感性成为海外网红营销的关键。本文Nox聚星将深入探讨语言、习俗…

什么是Spring框架?Spring有什么优势?Spring核心模块有哪些?

简介 Spring 是一个以 IoC 和 AOP 为基础的Java EE轻量级开源框架&#xff0c;其目的是用于简化 Java 企业级应用的开发难度和开发周期。 广义上的 Spring 泛指以 Spring Framework 为核心的 Spring 技术栈。例如 Spring Framework、Spring MVC、SpringBoot、Spring Cloud、Spr…

Unity编辑器扩展:提高效率与创造力的关键

Unity编辑器扩展&#xff1a;提高效率与创造力的关键 前言 一、理解Unity编辑器二、扩展Unity编辑器的意义三、扩展Unity编辑器的必要性四、Unity编辑器的扩展方式五、扩展Unity编辑器的步骤六、Unity编辑器扩展的应用案例七、总结 前言 Unity是一款广泛使用的游戏开发引擎&am…

管理三个层级,你是哪一层管理者?

大家好&#xff0c;我是老原。 “什么算是一个好的项目经理&#xff1f;” “我不知道自己现在的水平怎么样&#xff0c;有没有判断标准&#xff1f;” 其实这些问题的本质是&#xff1a;我想提升&#xff0c;该朝着哪个方向去努力&#xff1f; 很多人会说&#xff0c;能力…

Vinted、PoshMark、Carousell这些海外二手跨境电商平台如何运营?

相信大家都知道“闲鱼”&#xff0c;二手交易平台一般来说入驻成本低&#xff0c;运营操作简单&#xff0c;平台流量多&#xff0c;因此也非常适合小型卖家入驻。那么海外的“闲鱼”有哪些呢&#xff1f;如何运营&#xff1f; 小编为大家找到了国外热门这些平台&#xff0c;有…

【芯片设计封装与测试】芯片测试目的、方法、分类及案例

目录 1.芯片测试概述&#xff08;目的、方法&#xff09; 1.1.测试在芯片产业价值链上的位置 2.测试如何体现在设计的过程中 2.1.半导体测试定义与基本工作机制 2.2.半导体测试环节分类及对应设备 2.3.设计验证 3.测试的各种类型 3.1.抽样测试和生产全测 3.2.测试相关…

有什么react进阶的项目推荐的?

前言 整理了一些react相关的项目&#xff0c;可以选择自己需要的练习&#xff0c;希望对你有帮助~ 1.ant-design Star&#xff1a;87.1k 阿里开源的react项目&#xff0c;作为一个UI库&#xff0c;省去重复造轮子的时间 仓库地址&#xff1a;https://github.com/ant-design/…

PMP证书的正确打开方式 get✓

在职场竞争日益激烈的今天&#xff0c;拥有一项专业认证成为了许多人提升自身竞争力的必备条件。而作为项目管理领域的顶级认证&#xff0c;PMP证书备受关注。不过&#xff0c;很多人对于PMP证书的费用颇有顾虑。那么&#xff0c;PMP证书有什么补贴政策呢&#xff1f;下面就为大…

电脑重装+提升网速

https://www.douyin.com/user/self?modal_id7147216653720341767&showTabfavorite_collectionhttps://www.douyin.com/user/self?modal_id7147216653720341767&showTabfavorite_collection 零封有哈数的主页 - 抖音 (douyin.com)https://www.douyin.com/user/self?…

21. 合并两个有序链表(简单系列)

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4] 示例 2&#xff1a; 输入&#xff1a;l1 [], l2 [] 输出&#xff1a;[] 示例 3&#xff1a; …

牛客python练习2

1 解析&#xff1a;赋值操作&#xff08;aXX,ba&#xff09;&#xff0c;a&#xff0c;b指向同一内存空间。当a,b是不可变类型时&#xff0c;a变&#xff0c;a 值变&#xff0c;id变&#xff0c;但是b不变&#xff0c;b的id也不变&#xff1b;当a,b是可变类型时&#xff0c;a变…

中国AIGC产业全景图;设计+AIGC的工业化生产实践;大模型技术应用创新大赛;LangChain+向量数据库的LLM产品应用课程 | ShowMeAI日报

&#x1f440;日报&周刊合集 | &#x1f3a1;生产力工具与行业应用大全 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; &#x1f916; 艾瑞咨询8月最新报告&#xff0c;看透2023年中国AIGC产业全景 ShowMeAI知识星球资源编码&#xff1a;R178 艾瑞咨询在8月份最新发布了…

《合成孔径雷达成像算法与实现》Figure3.13——匹配滤波器的三种实现方式

clc clear close all% 参数设置 TBP 80; % 时间带宽积 T 10e-6; % 脉冲持续时间 N_ZD 60; % 零频点位于中点右侧的距离&#xff0c;P58% 参数计算 B TBP/T; …

windows Etcd的安装与使用

一、简介 etcd是一个分布式一致性键值存储&#xff0c;其主要用于分布式系统的共享配置和服务发现。 etcd由Go语言编写 二、下载并安装 1.下载地址&#xff1a; https://github.com/coreos/etcd/releases 解压后的目录如下&#xff1a;其中etcd.exe是服务端&#xff0c;e…

threejs纹理加载三(视频加载)

threejs中除了能把图片作为纹理进行几何体贴图以外&#xff0c;还可以把视频作为纹理进行贴图设置。纹理的类型有很多&#xff0c;我们可以用不同的加载器来加载&#xff0c;而对于视频作为纹理&#xff0c;我们需要用到今天的主角&#xff1a;VideoTexture。我们先看效果&…

安全工程师(安全产品售后)岗位需求-徐庆臣(黑客洗白者)

职位描述 负责网络安全项目中的产品调试和交付&#xff1b; 负责网络安全项目中的技术方案编写&#xff1b; 负责客户的安全应急和售后驻场。 职位要求 具备扎实的计算机与网络原理&#xff1b; 熟悉各类网络与安全设备&#xff08;路由、交换、防火墙、VPN、漏洞扫描&#…

玩转科技|了解AI平台桌面客户端—ChatBox

目录 前言 特性 ​编辑 为什么需要 ChatBox&#xff1f; ChatGPT Plus 平替&#xff1f; 下载 支持系统 功能图 使用教程 ​感受 展示 前言 今天小编又来了&#xff0c;推荐给大家一款开源的OpenAI API桌面客户端ChatBox&#xff0c;它支持 Windows、Mac 和 Linux。…

数据库导出工具

之前根据数据库升级需求&#xff0c;需要导出旧版本数据&#xff08;sqlserver 6.5&#xff09;&#xff0c;利用c# winfrom写了一个小工具&#xff0c;导出数据。 →→→→→多了不说&#xff0c;少了不唠。进入正题→→→→ 连接数据库&#xff1a;输入数据库信息 连接成功…