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看到的是代理对象,而打印出来的不是代理对象?
- Spring对A对象的B字段赋值的时候,实际上是返回的是一个代理对象。
- 而在打印一个对象的时候,会打印这个对象的toString方法。
- 此时会进入拦截器。而拦截器中,会回调代理对象的getTarget方法。
- getTarget方法中会通过beanFactory获取B,但是B是prototype,不会将创建好的bean保存到singletonObjects中,所以每次都会创建一个新的bean。
相关文章:
SpringBoot扩展篇:@Scope和@Lazy源码解析
SpringBoot扩展篇:Scope和Lazy源码解析 1. 研究主题及Demo2. 注册BeanDefinition3. 初始化属性3.1 解决依赖注入3.2 创建代理 ContextAnnotationAutowireCandidateResolver#getLazyResolutionProxyIfNecessary3.3 代理拦截处理3.4 单例bean与原型bean创建的区别 4. …...
“AI隐患识别系统,安全多了道“智能护盾”
家人们,在生活和工作里,咱们都知道安全那可是头等大事。不管是走在马路上,还是在工厂车间忙碌,又或是住在高楼大厦里,身边都可能藏着一些安全隐患。以前,发现这些隐患大多靠咱们的眼睛和经验,可…...
通向AGI之路:人工通用智能的技术演进与人类未来
文章目录 引言:当机器开始思考一、AGI的本质定义与技术演进1.1 从专用到通用:智能形态的范式转移1.2 AGI发展路线图二、突破AGI的五大技术路径2.1 神经符号整合(Neuro-Symbolic AI)2.2 世界模型架构(World Models)2.3 具身认知理论(Embodied Cognition)三、AGI安全:价…...
论文阅读:InstanceDiffusion: Instance-level Control for Image Generation
CVPR2024文章 摘要: 文本到图像扩散模型产生高质量的图像,但不提供对图像中单个实例的控制。我们引入了InstanceDiffusion,它将精确的实例级控制添加到文本到图像扩散模型中。InstanceDiffusion 支持每个实例的自由形式的语言条件ÿ…...
7.攻防世界 wzsc_文件上传
打开题目页面如下 上传了一张带有木马的图片 返回的页面是空白的,不过路径变了 猜测存在根目录/upload 也可以通过dirsearch扫描根目录 命令: dirsearch -u http://61.147.171.105:65024/ -e* 终于得到了上传的文件的信息 但是测试发现.php文件以及.…...
以为是响应式对象丢失导致数据没有回显
背景:之前ruoyi生成的vue2代码, <el-form ref“form”,后面我改成vue3的写法,没有实例化form, 在vue3中是需要定义const form ref(); 导致点击了修改后,页面弹框显示出来,数据没有回显。 一直…...
来 Gitcode 免费体验 DeepSeek 蒸馏模型,开启 AI 探索新旅程
在 AI 技术飞速发展的时代,你是否也怀揣着对前沿科技的无限好奇与探索欲望?然而,昂贵的模型体验费用和复杂的操作流程,是不是让你一次次望而却步?现在,这些都不再是问题!DeepSeek 蒸馏模型现已强…...
2.Mkdocs配置说明(mkdocs.yml)【最新版】
官方文件:Changing the colors - Material for MkDocs 建议详细学习一下上面的官方网站↑↑↑ 我把我目前的配置文件mkdocs.yml代码写在下面👇🏻 #[Info] site_name: Mkdocs教程 #your site name 显示在左上角 site_url: http://wcowin.wo…...
云轴科技ZStack+海光DCU:率先推出DeepSeek私有化部署方案
针对日益强劲的AI推理需求和企业级AI应用私有化部署场景(Private AI),云轴科技ZStack联合海光信息,共同推动ZStack智塔全面支持DeepSeek V3/R1/Janus Pro系列模型,基于海光DCU实现高性能适配,为企业提供安全…...
扩增子分析|零模型2——基于βNTI的微生物随机性和确定性装配过程(箱线图和柱状图R中实现)
一、引言 我们之前发布的周集中老师团队零模型R中实战案例:扩增子分析|基于零模型的群落确定性和随机性构建过程——R实战_bmntd-CSDN博客。在文末只输出了一个.csv 表格。并没有提供绘图的方法,有小伙伴问如何在R中一键成图呢?还真可以&…...
专题:剑指offer
链表 JZ6 从尾到头打印链表 思路:先顺序输出到栈里面 然后再以此从栈顶弹出即可 /** * struct ListNode { * int val; * struct ListNode *next; * ListNode(int x) : * val(x), next(NULL) { * } * }; */ #include …...
DeepSeek 部署过程中的问题
文章目录 DeepSeek 部署过程中的问题一、部署扩展:docker 部署 DS1.1 部署1.2 可视化 二、问题三、GPU 设置3.1 ollama GPU 的支持情况3.2 更新 GPU 驱动3.3 安装 cuda3.4 下载 cuDNN3.5 配置环境变量 四、测试 DeepSeek 部署过程中的问题 Windows 中 利用 ollama 来…...
DeepSeek R1本地化部署 Ollama + Chatbox 打造最强 AI 工具
🌈 个人主页:Zfox_ 🔥 系列专栏:Linux 目录 一:🔥 Ollama 🦋 下载 Ollama🦋 选择模型🦋 运行模型🦋 使用 && 测试 二:🔥 Chat…...
应急场景中的数据融合与对齐
1. 概述 在应急管理中,快速、准确地掌握现场状况、实时监控灾情并进行决策至关重要。各类数据(如卫星影像、无人机图像、激光雷达点云、地理信息系统(GIS)数据、传感器数据、社交媒体信息、移动终端数据等)具有来源广泛、格式多样、时空特性不同等特点。如何将这些异构数…...
手机上运行AI大模型(Deepseek等)
最近deepseek的大火,让大家掀起新一波的本地部署运行大模型的热潮,特别是deepseek有蒸馏的小参数量版本,电脑上就相当方便了,直接ollamaopen-webui这种类似的组合就可以轻松地实现,只要硬件,如显存…...
Mellanox网卡信息查看
1、查看Mellanox网卡的SN(序列号)和PN mstvpd 04:00.0或者lspci -s 04:00.0 -vvv来自https://enterprise-support.nvidia.com/s/article/MLNX2-117-2532kn 2、查看Mellanox网卡驱动、固件版本 ethtool -i ens6np0...
【漫画机器学习】083.安斯库姆四重奏(Anscombe‘s quartet)
安斯库姆四重奏(Anscombes Quartet) 1. 什么是安斯库姆四重奏? 安斯库姆四重奏(Anscombes Quartet)是一组由统计学家弗朗西斯安斯库姆(Francis Anscombe) 在 1973 年 提出的 四组数据集。它们…...
TCP | RFC793
注:本文为 “ RFC793” 相关文章合辑。 RFC793-TCP 中文翻译 编码那些事儿已于 2022-07-14 16:02:16 修改 简介 翻译自: RFC 793 - Transmission Control Protocol https://datatracker.ietf.org/doc/html/rfc793 TCP 是一个高可靠的主机到主机之间…...
2025蓝桥杯JAVA编程题练习Day2
1.大衣构造字符串 问题描述 已知对于一个由小写字母构成的字符串,每次操作可以选择一个索引,将该索引处的字符用三个相同的字符副本替换。 现有一长度为 NN 的字符串 UU,请帮助大衣构造一个最小长度的字符串 SS,使得经过任意次…...
《解锁GANs黑科技:打造影视游戏的逼真3D模型》
在游戏与影视制作领域,逼真的3D模型是构建沉浸式虚拟世界的关键要素。从游戏中栩栩如生的角色形象,到影视里震撼人心的宏大场景,高品质3D模型的重要性不言而喻。随着人工智能技术的飞速发展,生成对抗网络(GANs…...
Java 语言特性(面试系列1)
一、面向对象编程 1. 封装(Encapsulation) 定义:将数据(属性)和操作数据的方法绑定在一起,通过访问控制符(private、protected、public)隐藏内部实现细节。示例: public …...
ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...
C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...
分布式增量爬虫实现方案
之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面,避免重复抓取,以节省资源和时间。 在分布式环境下,增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路:将增量判…...
Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)
在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马(服务器方面的)的原理,连接,以及各种木马及连接工具的分享 文件木马:https://w…...
用机器学习破解新能源领域的“弃风”难题
音乐发烧友深有体会,玩音乐的本质就是玩电网。火电声音偏暖,水电偏冷,风电偏空旷。至于太阳能发的电,则略显朦胧和单薄。 不知你是否有感觉,近两年家里的音响声音越来越冷,听起来越来越单薄? —…...
【无标题】路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论
路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论 一、传统路径模型的根本缺陷 在经典正方形路径问题中(图1): mermaid graph LR A((A)) --- B((B)) B --- C((C)) C --- D((D)) D --- A A -.- C[无直接路径] B -…...
群晖NAS如何在虚拟机创建飞牛NAS
套件中心下载安装Virtual Machine Manager 创建虚拟机 配置虚拟机 飞牛官网下载 https://iso.liveupdate.fnnas.com/x86_64/trim/fnos-0.9.2-863.iso 群晖NAS如何在虚拟机创建飞牛NAS - 个人信息分享...
HubSpot推出与ChatGPT的深度集成引发兴奋与担忧
上周三,HubSpot宣布已构建与ChatGPT的深度集成,这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋,但同时也存在一些关于数据安全的担忧。 许多网络声音声称,这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...
保姆级【快数学会Android端“动画“】+ 实现补间动画和逐帧动画!!!
目录 补间动画 1.创建资源文件夹 2.设置文件夹类型 3.创建.xml文件 4.样式设计 5.动画设置 6.动画的实现 内容拓展 7.在原基础上继续添加.xml文件 8.xml代码编写 (1)rotate_anim (2)scale_anim (3)translate_anim 9.MainActivity.java代码汇总 10.效果展示 逐帧…...
