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

SpringBoot扩展篇:@Scope和@Lazy源码解析

SpringBoot扩展篇:@Scope和@Lazy源码解析

    • 1. 研究主题及Demo
    • 2. 注册BeanDefinition
    • 3. 初始化属性
      • 3.1 解决依赖注入
      • 3.2 创建代理 ContextAnnotationAutowireCandidateResolver#getLazyResolutionProxyIfNecessary
      • 3.3 代理拦截处理
      • 3.4 单例bean与原型bean创建的区别
    • 4. 总结

1. 研究主题及Demo

A class

@Component
public class A {@Lazy@Autowiredpublic B b;public B getB() {return b;}
}

B class

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Component
public class B {
}

测试类

@SpringBootApplication
public class WebApplication{public static void main(String[] args) {ConfigurableApplicationContext run = SpringApplication.run(WebApplication.class, args);A a = run.getBean(A.class);System.out.println(a.b);System.out.println(a.b);System.out.println(a.b);}
}

研究问题1:为什么打印三次b对象的地址值不一样,从源码角度分析Spring是如何实现的?

在这里插入图片描述

研究问题2:为什么会debug看到的是代理对象,而打印出来的不是代理对象?

2. 注册BeanDefinition

在Spring对@Component扫描的时候,会调用ClassPathBeanDefinitionScanner#doScan生成beandefinition对象,可参考:
SpringBoot 源码解析5:ConfigurationClassPostProcessor整体流程和@ComponentScan源码分析

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {Assert.notEmpty(basePackages, "At least one base package must be specified");Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();for (String basePackage : basePackages) {Set<BeanDefinition> candidates = findCandidateComponents(basePackage);for (BeanDefinition candidate : candidates) {ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);candidate.setScope(scopeMetadata.getScopeName());String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);if (candidate instanceof AbstractBeanDefinition) {postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);}if (candidate instanceof AnnotatedBeanDefinition) {AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);}if (checkCandidate(beanName, candidate)) {BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);definitionHolder =AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);beanDefinitions.add(definitionHolder);registerBeanDefinition(definitionHolder, this.registry);}}}return beanDefinitions;}

此时,Spring会通过AnnotationScopeMetadataResolver#resolveScopeMetadata扫描class上的@Scope注解,并通过candidate.setScope(scopeMetadata.getScopeName())将scope保存到beanDefinition中。

@Override
public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {ScopeMetadata metadata = new ScopeMetadata();if (definition instanceof AnnotatedBeanDefinition) {AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition;AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(annDef.getMetadata(), this.scopeAnnotationType);if (attributes != null) {metadata.setScopeName(attributes.getString("value"));ScopedProxyMode proxyMode = attributes.getEnum("proxyMode");if (proxyMode == ScopedProxyMode.DEFAULT) {proxyMode = this.defaultProxyMode;}metadata.setScopedProxyMode(proxyMode);}}return metadata;
}

所以此时的B中的scope为prototype。
在这里插入图片描述
Spring默认为单例,所以此时A中的scope为singleton。

3. 初始化属性

3.1 解决依赖注入

想要更加完善的了解Spring属性值注入,可查看 SpringBoot扩展篇:Spring注入 @Autowired & @Resource

此时,A对象已经创建完毕,当对A对象的B字段赋值时,会调用 DefaultListableBeanFactory#resolveDependency 实现依赖注入。

@Override
@Nullable
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());if (Optional.class == descriptor.getDependencyType()) {return createOptionalDependency(descriptor, requestingBeanName);}else if (ObjectFactory.class == descriptor.getDependencyType() ||ObjectProvider.class == descriptor.getDependencyType()) {return new DependencyObjectProvider(descriptor, requestingBeanName);}else if (javaxInjectProviderClass == descriptor.getDependencyType()) {return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName);}else {Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(descriptor, requestingBeanName);if (result == null) {result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);}return result;}
}

当getLazyResolutionProxyIfNecessary方法返回有值时,就会返回当前值,而当前值就是解析@Lazy注解,并对返回值进行了代理。

3.2 创建代理 ContextAnnotationAutowireCandidateResolver#getLazyResolutionProxyIfNecessary

@Override
@Nullable
public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) {return (isLazy(descriptor) ? buildLazyResolutionProxy(descriptor, beanName) : null);
}protected boolean isLazy(DependencyDescriptor descriptor) {for (Annotation ann : descriptor.getAnnotations()) {Lazy lazy = AnnotationUtils.getAnnotation(ann, Lazy.class);if (lazy != null && lazy.value()) {return true;}}MethodParameter methodParam = descriptor.getMethodParameter();if (methodParam != null) {Method method = methodParam.getMethod();if (method == null || void.class == method.getReturnType()) {Lazy lazy = AnnotationUtils.getAnnotation(methodParam.getAnnotatedElement(), Lazy.class);if (lazy != null && lazy.value()) {return true;}}}return false;
}protected Object buildLazyResolutionProxy(final DependencyDescriptor descriptor, final @Nullable String beanName) {Assert.state(getBeanFactory() instanceof DefaultListableBeanFactory,"BeanFactory needs to be a DefaultListableBeanFactory");final DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) getBeanFactory();TargetSource ts = new TargetSource() {@Overridepublic Class<?> getTargetClass() {return descriptor.getDependencyType();}@Overridepublic boolean isStatic() {return false;}@Overridepublic Object getTarget() {Object target = beanFactory.doResolveDependency(descriptor, beanName, null, null);if (target == null) {Class<?> type = getTargetClass();if (Map.class == type) {return Collections.emptyMap();}else if (List.class == type) {return Collections.emptyList();}else if (Set.class == type || Collection.class == type) {return Collections.emptySet();}throw new NoSuchBeanDefinitionException(descriptor.getResolvableType(),"Optional dependency not present for lazy injection point");}return target;}@Overridepublic void releaseTarget(Object target) {}};ProxyFactory pf = new ProxyFactory();pf.setTargetSource(ts);Class<?> dependencyType = descriptor.getDependencyType();if (dependencyType.isInterface()) {pf.addInterface(dependencyType);}return pf.getProxy(beanFactory.getBeanClassLoader());
}

isLazy方法:判断是否需要懒加载。显然,此时A对象的B字段上面有@Lazy注解,返回的是true。
buildLazyResolutionProxy方法:创建ProxyFactory代理对象,并返回该代理对象。当该代理对象调用方法时,会回调getTarget() 方法,从而从beanFactory中获取B对象。但是此时,B对象是prototype类型,不会保存到单例池singletonObjects中,所以每次获取B对象的时候,都是创建,每次都是不同的对象。

3.3 代理拦截处理

private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {private final AdvisedSupport advised;public DynamicAdvisedInterceptor(AdvisedSupport advised) {this.advised = advised;
}@Override
@Nullable
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {Object oldProxy = null;boolean setProxyContext = false;Object target = null;TargetSource targetSource = this.advised.getTargetSource();try {if (this.advised.exposeProxy) {// Make invocation available if necessary.oldProxy = AopContext.setCurrentProxy(proxy);setProxyContext = true;}// Get as late as possible to minimize the time we "own" the target, in case it comes from a pool...target = targetSource.getTarget();Class<?> targetClass = (target != null ? target.getClass() : null);List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);Object retVal;// Check whether we only have one InvokerInterceptor: that is,// no real advice, but just reflective invocation of the target.if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {// We can skip creating a MethodInvocation: just invoke the target directly.// Note that the final invoker must be an InvokerInterceptor, so we know// it does nothing but a reflective operation on the target, and no hot// swapping or fancy proxying.Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);retVal = methodProxy.invoke(target, argsToUse);}else {// We need to create a method invocation...retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();}retVal = processReturnType(proxy, target, method, retVal);return retVal;}finally {if (target != null && !targetSource.isStatic()) {targetSource.releaseTarget(target);}if (setProxyContext) {// Restore old proxy.AopContext.setCurrentProxy(oldProxy);}}
}

在代理拦截器中, 回调了target方法。

3.4 单例bean与原型bean创建的区别

AbstractBeanFactory#doGetBean

在这里插入图片描述
可以看到,单例bean创建调用了getSingleton方法,再从中回调createBean创建bean的,而原型模式是直接调用createBean创建bean的。

DefaultSingletonBeanRegistry#getSingleton

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {Assert.notNull(beanName, "Bean name must not be null");synchronized (this.singletonObjects) {Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {if (this.singletonsCurrentlyInDestruction) {throw new BeanCreationNotAllowedException(beanName,"Singleton bean creation not allowed while singletons of this factory are in destruction " +"(Do not request a bean from a BeanFactory in a destroy method implementation!)");}if (logger.isDebugEnabled()) {logger.debug("Creating shared instance of singleton bean '" + beanName + "'");}beforeSingletonCreation(beanName);boolean newSingleton = false;boolean recordSuppressedExceptions = (this.suppressedExceptions == null);if (recordSuppressedExceptions) {this.suppressedExceptions = new LinkedHashSet<>();}try {singletonObject = singletonFactory.getObject();newSingleton = true;}catch (IllegalStateException ex) {// Has the singleton object implicitly appeared in the meantime ->// if yes, proceed with it since the exception indicates that state.singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {throw ex;}}catch (BeanCreationException ex) {if (recordSuppressedExceptions) {for (Exception suppressedException : this.suppressedExceptions) {ex.addRelatedCause(suppressedException);}}throw ex;}finally {if (recordSuppressedExceptions) {this.suppressedExceptions = null;}afterSingletonCreation(beanName);}if (newSingleton) {addSingleton(beanName, singletonObject);}}return singletonObject;}}

在单例bean创建的最后,会调用addSingleton方法将创建好的bean放入到singletonObjects中,而原型模式创建的bean不会!

4. 总结

研究问题1:为什么打印三次b对象的地址值不一样,从源码角度分析Spring是如何实现的?
研究问题2:为什么会debug看到的是代理对象,而打印出来的不是代理对象?

  1. Spring对A对象的B字段赋值的时候,实际上是返回的是一个代理对象。
  2. 而在打印一个对象的时候,会打印这个对象的toString方法。
  3. 此时会进入拦截器。而拦截器中,会回调代理对象的getTarget方法。
  4. getTarget方法中会通过beanFactory获取B,但是B是prototype,不会将创建好的bean保存到singletonObjects中,所以每次都会创建一个新的bean。

相关文章:

SpringBoot扩展篇:@Scope和@Lazy源码解析

SpringBoot扩展篇&#xff1a;Scope和Lazy源码解析 1. 研究主题及Demo2. 注册BeanDefinition3. 初始化属性3.1 解决依赖注入3.2 创建代理 ContextAnnotationAutowireCandidateResolver#getLazyResolutionProxyIfNecessary3.3 代理拦截处理3.4 单例bean与原型bean创建的区别 4. …...

tkvue 入门,像写html一样写tkinter

介绍 没有官网&#xff0c;只有例子 安装 像写vue 一样写tkinter 代码 pip install tkvue作者博客 修改样式 import tkvue import tkinter.ttk as ttktkvue.configure_tk(theme"clam")class RootDialog(tkvue.Component):template """ <Top…...

c++ stl 遍历算法和查找算法

概述&#xff1a; 算法主要由头文件<algorithm> <functional> <numeric> 提供 <algorithm> 是所有 STL 头文件中最大的一个&#xff0c;提供了超过 90 个支持各种各样算法的函数&#xff0c;包括排序、合并、搜索、去重、分解、遍历、数值交换、拷贝和…...

Hackmyvm Connection

基本信息 难度&#xff1a;简单 靶机&#xff1a;192.168.194.11 kali&#xff1a;192.168.194.9 扫描 常规nmap扫描起手 nmap -sT -sV -A -T4 192.168.194.11 -p- 查看smb服务开启目录 139和445端口的smb服务直接以访客账号登录&#xff0c;无需密码验证成功。对应的ht…...

内置渲染管线和通用渲染管线的区别

内置渲染管线和通用渲染管线&#xff08;URP&#xff09;有以下区别&#xff1a; 功能特性 内置渲染管线&#xff1a;提供了一套较为基础的渲染功能&#xff0c;包括几何渲染、光照计算、阴影生成和后期处理等基本环节。但自定义选项相对有限&#xff0c;渲染次序基本是固…...

Unity 2D实战小游戏开发跳跳鸟 - 记录显示最高分

上一篇文章中我们实现了游戏的开始界面,在开始界面中有一个最高分数的UI,本文将接着实现记录最高分数以及在开始界面中显示最高分数的功能。 添加跳跳鸟死亡事件 要记录最高分,则需要在跳跳鸟死亡时去进行判断当前的分数是否是最高分,如果是最高分则进行记录,如果低于之前…...

算法随笔_40: 爬楼梯

上一篇:算法随笔_39: 最多能完成排序的块_方法2-CSDN博客 题目描述如下: 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 示例 1&#xff1a; 输入&#xff1a;n 2 输出&#xff1a;2 解释&am…...

数据结构(2)——线性表与顺序表实现

目录 前言 一、线性表 二、顺序表 2.1概念 2.2类型的选择 2.3实现 1.初始化 2.检查是否需要扩容 3.尾插 4.尾删 5.头插 6.头删 7.某一个位置添加 8.某一个位置删除 9.基于某一位置的尾插删 10.查找 11.修改 12.销毁 总结 前言 今天对顺序表进行学习&#xf…...

全面解析机器学习优化算法中的进化策略

全面解析机器学习优化算法中的进化策略 全面解析机器学习优化算法中的进化策略引言什么是进化策略?基本概念核心组件算法流程数学基础高斯扰动期望值更新与其他优化方法的比较梯度下降法(Gradient Descent, GD)遗传算法(Genetic Algorithm, GA)Python案例基本实现改进版:…...

【LeetCode】5. 贪心算法:买卖股票时机

太久没更了&#xff0c;抽空学习下。 看一道简单题。 class Solution:def maxProfit(self, prices: List[int]) -> int:cost -1profit 0for i in prices:if cost -1:cost icontinueprofit_ i - costif profit_ > profit:profit profit_if cost > i:cost iret…...

软件测试丨PyTorch 图像目标检测

随着人工智能和机器学习的飞速发展&#xff0c;图像目标检测技术在各个领域扮演着越来越重要的角色。无论是在安防监控、自动驾驶车辆&#xff0c;还是在医疗影像分析和智能家居中&#xff0c;图像目标检测都发挥着不可或缺的作用。今天&#xff0c;我们将深入探讨其中一种热门…...

SpringSecurity密码编码器:使用BCrypt算法加密、自定义密码编码器

1、Spring Security 密码编码器 Spring Security 作为一个功能完备的安全性框架,一方面提供用于完成加密操作的 PasswordEncoder 组件,另一方面提供一个可以在应用程序中独立使用的密码模块。 1.1 PasswordEncoder 抽象接口 在 Spring Security 中,PasswordEncoder 接口代…...

FastReport.NET控件篇之交叉表控件

认识交叉表 上面是交叉表的原型&#xff0c;关键的三个单元格。 单元格①&#xff1a;用于扩展行数据&#xff0c;譬如打印学生成绩表时&#xff0c;每个学生一行&#xff0c;那么这个地方就是以学生姓名列进行打印。 单元格②&#xff1a;用于扩展列数据&#xff0c;譬如打印…...

互联网医院开发|互联网医院成品|互联网医院系统定制

互联网医院开发设计风格需综合考量多方面因素&#xff0c;以确保其专业性、易用性与高效性。在界面设计上&#xff0c;应遵循简洁直观的原则。避免过于繁杂的布局&#xff0c;确保关键功能模块清晰呈现&#xff0c;方便用户快速定位与操作。色彩搭配要注重视觉舒适度与专业性&a…...

17.3.4 颜色矩阵

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 17.3.4.1 矩阵基本概念 矩阵&#xff08;Matrix&#xff09;是一个按照长方阵列排列的复数或实数集合&#xff0c;类似于数组。 由…...

【C++】多态详细讲解

本篇来聊聊C面向对象的第三大特性-多态。 1.多态的概念 多态通俗来说就是多种形态。多态分为编译时多态(静态多态)和运⾏时多态(动态多态)。 编译时多态&#xff1a;主要就是我们前⾯讲的函数重载和函数模板&#xff0c;他们传不同类型的参数就可以调⽤不同的函数&#xff0c;通…...

4. k8s二进制集群之ETCD集群证书生成

安装cfssl工具配置CA证书请求文件创建CA证书创建CA证书策略配置etcd证书请求文件生成etcd证书 继续上一篇文章《负载均衡器高可用部署》下面介绍一下etcd证书生成配置。其中涉及到的ip地址和证书基本信息请替换成你自己的信息。 安装cfssl工具 下载cfssl安装包 https://github…...

Drools规则引擎初体验

前言 假设有这样一个场景&#xff0c;订单管理系统需要根据用户的消费情况&#xff0c;来为每个用户发放不同程度的优惠券&#xff0c;这个发放规则复杂且多变&#xff0c;我们该怎么办&#xff1f;在代码中写死显然是不可取的&#xff0c;规则一变就要修改代码&#xff0c;频…...

Day36【AI思考】-表达式知识体系总览

文章目录 **表达式知识体系总览**回答1&#xff1a;**表达式知识体系****一、三种表达式形式对比****二、表达式转换核心方法****1. 中缀转后缀&#xff08;重点&#xff09;****2. 中缀转前缀** **三、表达式计算方法****1. 后缀表达式计算&#xff08;栈实现&#xff09;****…...

Tailwind CSS v4.0 升级与 Astro 5.2 项目迁移记录

本文博客链接 https://ysx.cosine.ren/tailwind-update-v4-migrate 自用小记。 Tailwind CSS v4.0 - Tailwind CSS 新的高性能引擎 - 完整构建的速度速度快 5 倍&#xff0c;增量构建的速度快于 100 倍以上 —— 以微秒为单位进行测量。为现代 Web 设计 - 建立在前沿的 CSS 特…...

K8S ReplicaSet 控制器

一、理论介绍 今天我们来实验 ReplicaSet 控制器&#xff08;也叫工作负载&#xff09;。官网描述如下&#xff1a; 1、是什么&#xff1f; ReplicaSet 副本集&#xff0c; 维护一组稳定的副本 Pod 集合。 2、为什么需要&#xff1f; 解决 pod 被删除了&#xff0c;不能自我恢…...

基于springboot校园点歌系统

基于Spring Boot的校园点歌系统是一种专为校园场景设计的音乐点播平台&#xff0c;它能够丰富学生的校园生活&#xff0c;提升学生的娱乐体验。以下是对该系统的详细介绍&#xff1a; 一、系统背景与意义 在校园环境中&#xff0c;学生们对于音乐有着浓厚的兴趣&#xff0c;传…...

【R语言】数据操作

一、查看和编辑数据 1、查看数据 直接打印到控制台 x <- data.frame(a1:20, b21:30) x View()函数 此函数可以将数据以电子表格的形式进行展示。 用reshape2包中的tips进行举例&#xff1a; library("reshape2") View(tips) head()函数 查看前几行数据&…...

【C++】2.高并发内存池 -- 如何设计一个定长内存池

博客主题&#xff1a;如何设计一个定长内存池 个人主页&#xff1a;https://blog.csdn.net/sobercq CSDN专栏&#xff1a;https://blog.csdn.net/sobercq/category_12884309.html Gitee链接&#xff1a;https://gitee.com/yunshan-ruo/high-concurrency-memory-pool 文章目录 前…...

Redis --- 使用Feed流实现社交平台的新闻流

要实现一个 Feed 流&#xff08;类似于社交媒体中的新闻流&#xff09;&#xff0c;通常涉及以下几个要素&#xff1a; 内容发布&#xff1a;用户发布内容&#xff08;例如文章、状态更新、图片等&#xff09;。内容订阅&#xff1a;用户可以订阅其他用户的内容&#xff0c;获…...

React中key值的正确使用指南:为什么需要它以及如何选择

React中key值的正确使用指南&#xff1a;为什么需要它以及如何选择 一、key值的基本概念二、如何选择合适的key值1. 数据来源决定key策略2. key值的三大核心要求 三、React为何需要key值&#xff1f;1. 虚拟DOM优化机制2. 状态维护机制 四、常见误区及解决方案1. 索引作为key的…...

在Debian 12上安装VNC服务器

不知道什么标题 可以看到这个文章是通过豆包从国外网站copy的&#xff0c;先这样写着好了&#xff0c;具体的我有时间再补充&#xff0c;基本内容都在这里了。 在Debian 12上安装VNC服务器 简介 VNC&#xff08;Virtual Network Computing&#xff0c;虚拟网络计算&#xf…...

游戏引擎学习第88天

仓库:https://gitee.com/mrxiao_com/2d_game_2 调查碰撞检测器中的可能错误 在今天的目标是解决一个可能存在的碰撞检测器中的错误。之前有人提到在检测器中可能有一个拼写错误&#xff0c;具体来说是在测试某个变量时&#xff0c;由于引入了一个新的变量而没有正确地使用它&…...

c++中priority_queue的应用及模拟实现

1.介绍 priority_queue 是一种数据结构&#xff0c;它允许你以特定的顺序存储和访问元素。在 C 标准模板库&#xff08;STL&#xff09;中&#xff0c;priority_queue 是一个基于容器适配器的类模板&#xff0c;它默认使用 std::vector 作为底层容器&#xff0c;并且默认使用最…...

深度学习篇---计算机视觉任务模型的剪裁、量化、蒸馏

文章目录 前言第一部分&#xff1a;计算机视觉任务图像分类特点 图像识别特点 目标检测特点 图像分割子任务特点 第二部分&#xff1a;模型剪裁、量化、蒸馏模型剪裁1.权重剪裁2.结构剪裁3.迭代剪裁 模型量化1.对称量化2.非对称量化3.动态量化4.静态量化 知识蒸馏1.训练教师网络…...