Spring 高级依赖注入 —— Bean的延迟依赖查找功能,ObjectFactory 和 ObjectProvider
介绍
首先明确一下什么是延迟查找,一般来说通过@Autowired注解注入一个具体对象的方式是属于实时依赖查找,注入的前提是要保证对象已经被创建。而使用延迟查找的方式是我可以不注入对象的本身,而是通过注入一个代理对象,在需要用到的地方再去取其中真实的对象来使用 ,ObjectFactory提供的就是这样一种能力。
先来看一下ObjectFactory和ObjectProvider的源码
@FunctionalInterface
public interface ObjectFactory<T> {T getObject() throws BeansException;
}
public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {T getObject(Object... args) throws BeansException;@NullableT getIfAvailable() throws BeansException;default T getIfAvailable(Supplier<T> defaultSupplier) throws BeansException {T dependency = getIfAvailable();return (dependency != null ? dependency : defaultSupplier.get());}default void ifAvailable(Consumer<T> dependencyConsumer) throws BeansException {T dependency = getIfAvailable();if (dependency != null) {dependencyConsumer.accept(dependency);}}@NullableT getIfUnique() throws BeansException;default T getIfUnique(Supplier<T> defaultSupplier) throws BeansException {T dependency = getIfUnique();return (dependency != null ? dependency : defaultSupplier.get());}default void ifUnique(Consumer<T> dependencyConsumer) throws BeansException {T dependency = getIfUnique();if (dependency != null) {dependencyConsumer.accept(dependency);}}@Overridedefault Iterator<T> iterator() {return stream().iterator();}default Stream<T> stream() {throw new UnsupportedOperationException("Multi element access not supported");}default Stream<T> orderedStream() {throw new UnsupportedOperationException("Ordered element access not supported");}}
通过源码可以看出ObjectFactory是一个顶层接口,内部只提供了直接获取对象的功能,如果对象在容器中不存则直接抛出NoSuchBeanDefinitionException异常。ObjectProvider提供了更强大的功能,支持迭代,stream 流等特性,通过getIfAvailable方法还可以避免NoSuchBeanDefinitionException 异常
用法演示
下面通过代码来演示ObjectFactory和ObjectProvider的使用方式
public class ObjectFactoryLazyLookupDemo {// DefaultListableBeanFactory$DependencyObjectProvider@Autowiredprivate ObjectFactory<User> objectFactory;// DefaultListableBeanFactory$DependencyObjectProvider@Autowiredprivate ObjectProvider<User> objectProvider;public static void main(String[] args) {// 创建应用上下文AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();// 注册当前类为配置类applicationContext.register(ObjectFactoryLazyLookupDemo.class);// 启动应用上下文applicationContext.refresh();// 获取当前类的实例ObjectFactoryLazyLookupDemo lazyLookupDemo = applicationContext.getBean(ObjectFactoryLazyLookupDemo.class);// 获取通过依赖注入的ObjectFactory和ObjectProvider对象ObjectFactory<User> objectFactory = lazyLookupDemo.objectFactory;ObjectProvider<User> objectProvider = lazyLookupDemo.objectProvider;// trueSystem.out.println(objectFactory.getClass() == objectProvider.getClass());// trueSystem.out.println(objectFactory.getObject() == objectProvider.getObject());// User{id=1, name='lazy lookup'}System.out.println(objectFactory.getObject());}@Beanprivate User user() {User user = new User();user.setId(1L);user.setName("lazy lookup");return user;}
}
在上述代码中,创建了一个User对象,在注入的时候并没有直接注入对象本身,而是分别了注入了ObjectFactory<User>和ObjectProvider<User>对象,在真正使用时才通过objectFactory.getObject()去获取真实对象,在注入ObjectFactory和ObjectProvider时并没有触发依赖查找的动作,这种方式就是典型的延迟依赖查找。通过两种方式获取的User对象也是同一个对象
底层原理
在DefaultListableBeanFactory中有一个resolveDependency(DependencyDescriptor, String, Set<String>, TypeConverter) 方法,通过名称可以看出此方法专门用来解析依赖。在框架内部处理@Autowired注解时会调用此方法,方法内部会通过依赖查找的方式查出需要进行依赖注入的Bean。源码如下
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());// 处理Optional类型的依赖注入if (Optional.class == descriptor.getDependencyType()) {return createOptionalDependency(descriptor, requestingBeanName);}// 处理ObjectFactory和ObjectProvider类型else if (ObjectFactory.class == descriptor.getDependencyType() ||ObjectProvider.class == descriptor.getDependencyType()) {return new DependencyObjectProvider(descriptor, requestingBeanName);}// 处理JSR330 相关的依赖注入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;}
}
在代码中可以看出,如果需要进行依赖注入的Bean类型为ObjectFactory或者ObjectProvider,则直接创建一个类型为DependencyObjectProvider的实例返回。如果注入的是具体类型则代码会走最后的else分支,doResolveDependency()方法本质上就是通过依赖查找的方式去获取对应的Bean
DefaultListableBeanFactory的一个内部类,结构如下
private interface BeanObjectProvider<T> extends ObjectProvider<T>, Serializable {
}private class DependencyObjectProvider implements BeanObjectProvider<Object> {private final DependencyDescriptor descriptor;private final boolean optional;@Nullableprivate final String beanName;public DependencyObjectProvider(DependencyDescriptor descriptor, @Nullable String beanName) {// 需要注入对象的类型描述,在本例中即User类型this.descriptor = new NestedDependencyDescriptor(descriptor);// 是否是Optional类型this.optional = (this.descriptor.getDependencyType() == Optional.class);// 被依赖注入的对象,本例中为objectFactoryLazyLookupDemothis.beanName = beanName;}@Overridepublic Object getObject() throws BeansException {if (this.optional) {return createOptionalDependency(this.descriptor, this.beanName);}else {// 内部实际上就是通过依赖查找的方式查出所需的BeanObject result = doResolveDependency(this.descriptor, this.beanName, null, null);if (result == null) {throw new NoSuchBeanDefinitionException(this.descriptor.getResolvableType());}return result;}}// 省略其他方法.....}
通过代码可以看出DependencyObjectProvider实际上就是ObjectProvider类型,这里我只保留其getObject()方法,通过该方法可以看出,只有当使用者调用ObjectProvider#getObject()方法时,才会通过依赖查找的方式获取对应的Bean
总结和使用场景
通过示例代码和源码分析可以更确定延迟的概念,所谓延迟依赖查找就是等真正用到对象的时候才去获取对象。
那么使用延迟查找的应用场景有哪些呢
-
可以让依赖的资源充分等到初始化完成之后再使用
-
可以和
@Lazy注解配合充分实现延迟初始化在本例的代码中,我们只在
user()方法上面简单标注了@Bean注解,还可以通过标注@Lazy注解实现User对象的延迟初始化,和ObjectFactory配合使用就可以实现真正用到该对象的那一刻才进行初始化操作。 -
可用于解决构造器级别的循环依赖
相关文章:
Spring 高级依赖注入 —— Bean的延迟依赖查找功能,ObjectFactory 和 ObjectProvider
介绍 首先明确一下什么是延迟查找,一般来说通过Autowired注解注入一个具体对象的方式是属于实时依赖查找,注入的前提是要保证对象已经被创建。而使用延迟查找的方式是我可以不注入对象的本身,而是通过注入一个代理对象,在需要用到…...
VSCode--Config
1. basic 1.1 调整字体 1.2 调整 remote login 输入框都在 TERMINAL 中实现 1.3 界面设置成中文 安装插件: 然后配置即可。 2.Linux 2.1 Install 2.1.1 offline Install vscode server 问题描述 内网开发,vscode 自身通过代理安装完 remote 插件后…...
代码随想录刷题第48天|LeetCode198打家劫舍、LeetCode213打家劫舍II、LeetCode337打家劫舍III
1、LeetCode198打家劫舍 题目链接:198、打家劫舍 1、dp[i]:考虑下标i(包括i)以内的房屋,最多可以偷窃的金额为dp[i]。 2、递推公式: 如果偷第i房间,那么dp[i] dp[i - 2] nums[i] …...
C# NTS 获取MuliiLineString中的所有线
/// <summary>/// 获取多段线的所有线/// </summary>/// <param name"ml"></param>/// <returns></returns>public static List<LineString> GetLineStrings(this MultiLineString ml){List<LineString> lineString…...
CodeWhisperer插件使用体验
官方教程点击跳转 使用工具 1.vscode 2.插件(AWS Toolkit),免费使用 安装以后如何使用 1.首先要有一个aws账号 2.插件下载好以后登录aws账号,我们主要用这款插件的CodeWhisperer这个功能,其它的自行看官方教程了解。 注意事项:我们在从vs…...
机器学习笔记 - 多实例学习(MIL)弱监督学习
一、多实例学习概述 多实例学习(MIL)是一种弱监督学习形式,其中训练实例被排列在称为袋的集合中,并为整个袋提供标签。这种方式越来越受到人们的关注,因为它自然适合各种问题,并允许利用弱标记数据。因此,它被应用于计算机视觉和文档分类等不同的应用领域。 多实例学习(…...
SQL Server 2008 定时自动备份和自动删除方法
SQL Server 2008 数据定时自动备份和自动删除方法,同一个计划兼备数据备份数数据删除的操作方法 工具/原料 SQL Server 2008 方法/步骤 1、 点击实例名下的【管理】-【维护计划】-点击鼠标右键,点击【维护计划向导】,填写计划名称&…...
代码生成器实现
代码生成器实现 实现封装元数据的工具类实现代码生成器的代码编写掌握模板创建的 构造数据模型 需求分析 借助Freemarker机制可以方便的根据模板生成文件,同时也是组成代码生成器的核心部分。对于Freemarker而 言,其强调 数据模型 模板 文件 的思…...
【Python基础】Python函数(基本函数)
文章目录 Python函数1、函数多返回值2、函数多种传参方式(1)位置参数(2)关键字参数(3)缺省参数(4)不定长参数位置传递关键字传递 3、小结 Python函数 1、函数多返回值 Q:如果一个函数要有多个返回值,该如何书写代码? # 使用多个变量&#…...
Vue3 + TS + Vite —— 大屏可视化 项目实战
前期回顾 Vue3 Ts Vite pnpm 项目中集成 —— eslint 、prettier、stylelint、husky、commitizen_彩色之外的博客-CSDN博客搭建VIte Ts Vue3项目并集成eslint 、prettier、stylelint、huskyhttps://blog.csdn.net/m0_57904695/article/details/129950163?spm1001.2014…...
EasyExcel 批量导入并校验数据
文章目录 前言一、pom二、使用步骤1.导入对象2.读入数据并保存 前言 EasyExcel 批量导入并校验数据 一、pom <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.2.7</version></depend…...
亚马逊、Allegro卖家建立属于自己的测评系统,实现批量优质账号养成
卖家搭建一套完整的测评系统,卖家自己能够养出批量优质账号,并完全掌控真实买家的浏览、加购、下单和评价等风控数据规律。我们的系统能够自主加速推广,防御反击,同时节省运营成本,实现高效的测评运营。 我们的系统支…...
springboot的目录结构作用
springboot单体项目结构大概如下。 代码都在src/main下, java是后端代码 java下最基本的包 dao(mapper) entity(model) service controller 其他的包根据项目需求扩展。 resources下是配置文件。 如果不是前后端分离,resources下放的是静态文件…...
微信小程序基础使用-请求数据并渲染
小程序基本使用-请求数据并渲染 小程序模板语法-数据绑定 在js中定义数据 Page({data: {isOpen: true,message: hello world!} })小程序的data是一个对象,不同于vue的data是一个函数 在模块中获取使用数据 小程序中使用 {{}} 实现数据与模板的绑定 内容绑定&a…...
代码随想录训练营Day55| 392.判断子序列 ;115.不同的子序列
392.判断子序列 class Solution {public boolean isSubsequence(String s, String t) {int m s.length();int n t.length();if(m>n) return false;int[][] dp new int[m1][n1];dp[0][0]0;for(int i1;i<m;i){for(int j1;j<n;j){if(s.charAt(i-1)t.charAt(j-1)){dp[i…...
网络作业9【计算机网络】
网络作业9【计算机网络】 前言推荐网络作业9一. 单选题(共12题,36分)二. 多选题(共1题,3分)三. 填空题(共2题,10分)四. 阅读理解(共1题,17分&…...
C++ QT 上传图片至mysql数据库
以下是一个简单的C QT上传图片至MySQL数据库的代码示例: #include <QtSql> #include <QFile> #include <QByteArray> int main() { //连接数据库 QSqlDatabase db QSqlDatabase::addDatabase("QMYSQL"); …...
2023去水印小程序saas系统源码修复独立版v1.0.3+uniapp前端
🎈 限时活动领体验会员:可下载程序网创项目短视频素材 🎈 🎉 有需要的朋友记得关赞评,阅读文章底部来交流!!! 🎉 ✨ 源码介绍 一个基于uniapp写的小程序,后端…...
【ChatGPT】数据科学 ChatGPT Cheat Sheet 书籍分享(阿里云盘下载)
封皮 以下为书中部分内容的机器翻译 我们的重要提示指南 1. 以 AI 角色的描述开始提示。 例如,“你是{x}”或“我希望你扮演{x}”。如果您不确定,请尝试“你是一个有帮助的助手”。 例如,您是 OpenAI 的数据科学家,您正在研究大型…...
使用 Docker-compose 搭建lnmp
服务编排: 应用编排: 单机环境下:shell/python脚本多机/集群环境下:ansible、saltstack、pubbet docker容器编排: 单机:docker-compose多机/集群:docker swarm,mesos marathon&a…...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...
Linux应用开发之网络套接字编程(实例篇)
服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...
CTF show Web 红包题第六弹
提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框,很难让人不联想到SQL注入,但提示都说了不是SQL注入,所以就不往这方面想了 先查看一下网页源码,发现一段JavaScript代码,有一个关键类ctfs…...
Spark 之 入门讲解详细版(1)
1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室(Algorithms, Machines, and People Lab)开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目,8个月后成为Apache顶级项目,速度之快足见过人之处&…...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...
Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
vscode(仍待补充)
写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh? debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...
《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》
在注意力分散、内容高度同质化的时代,情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现,消费者对内容的“有感”程度,正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中࿰…...
基于当前项目通过npm包形式暴露公共组件
1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹,并新增内容 3.创建package文件夹...
华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建
华为云FlexusDeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色,华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型,能助力我们轻松驾驭 DeepSeek-V3/R1,本文中将分享如何…...
