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

Spring5 中更优雅的第三方 Bean 注入

小伙伴们知道,当我们使用 Spring 容器的时候,如果遇到一些特殊的 Bean,一般来说可以通过如下三种方式进行配置:

  • 静态工厂方法
  • 实例工厂方法
  • FactoryBean

不过从 Spring5 开始,在 AbstractBeandefinition 类中多了一个属性,对于特殊的 Bean 我们有了更多的选择:

/*** Specify a callback for creating an instance of the bean,* as an alternative to a declaratively specified factory method.* <p>If such a callback is set, it will override any other constructor* or factory method metadata. However, bean property population and* potential annotation-driven injection will still apply as usual.* @since 5.0* @see #setConstructorArgumentValues(ConstructorArgumentValues)* @see #setPropertyValues(MutablePropertyValues)*/
public void setInstanceSupplier(@Nullable Supplier<?> instanceSupplier) {this.instanceSupplier = instanceSupplier;
}/*** Return a callback for creating an instance of the bean, if any.* @since 5.0*/
@Nullable
public Supplier<?> getInstanceSupplier() {return this.instanceSupplier;
}

接下来松哥就来和大家简单聊一聊这个话题。

1. 传统解决方案

1.1 问题

不知道各位小伙伴们有没有用过 OkHttp,这是一个专门做网络请求的工具,在微服务的 HTTP 调用组件中,我们可以配置底层使用 OkHttp 这个工具。

一般来说,如果我们想直接使用 OkHttp,代码如下:

OkHttpClient client = new OkHttpClient.Builder().connectTimeout(5, TimeUnit.SECONDS).readTimeout(5, TimeUnit.SECONDS).build();
Request getReq = new Request.Builder().get().url("http://www.javaboy.org").build();
Call call = client.newCall(getReq);
call.enqueue(new Callback() {@Overridepublic void onFailure(@NotNull Call call, @NotNull IOException e) {System.out.println("e.getMessage() = " + e.getMessage());}@Overridepublic void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {System.out.println("response.body().string() = " + response.body().string());}
});

先通过建造者模式创建出来一个 OkHttpClient 对象,然后还是建造者模式创建出来 Request 对象,接下来去发送请求就可以了。那么对于这样的代码,我们可以将 OkHttpClient 对象交由 Spring 容器统一管理,那么该如何将 OkHttpClient 注册到 Spring 容器中呢?

1.2 静态工厂方法

首先可以采用静态工厂方法,也就是工厂方法是一个静态方法,如下:

public class OkHttpStaticFactory {private static OkHttpClient okHttpClient;static {okHttpClient = new OkHttpClient.Builder().connectTimeout(5, TimeUnit.SECONDS).readTimeout(5, TimeUnit.SECONDS).build();}public static OkHttpClient getOkHttpClient() {return okHttpClient;}
}

然后在 Spring 配置文件中进行注入:

<bean class="org.javaboy.bean.OkHttpStaticFactory" factory-method="getOkHttpClient" id="httpClient"/>

静态工厂的特点是静态方法可以直接调用,并必须要获取到工厂类的实例,所以上面配置的时候只需要指定 factory-method 就可以了。

这就可以了,将来我们去 Spring 容器中查找一个名为 httpClient 的对象,拿到手的就是 OkHttpClient 了。

1.3 实例工厂方法

实例工厂方法意思就是说工厂方法是一个实例方法。如下:

public class OkHttpInstanceFactory {private volatile static OkHttpClient okHttpClient;public OkHttpClient getInstance() {if (okHttpClient == null) {synchronized (OkHttpInstanceFactory.class) {if (okHttpClient == null) {okHttpClient = new OkHttpClient.Builder().connectTimeout(5, TimeUnit.SECONDS).readTimeout(5, TimeUnit.SECONDS).build();}}}return okHttpClient;}
}

这是一个简单的单例模式。但是这里的工厂方法是一个实例方法,实例方法的调用必须得先获取到对象然后才能调用实例方法,因此配置方式如下:

<bean class="org.javaboy.bean.OkHttpInstanceFactory" id="httpInstanceFactory"/>
<bean factory-bean="httpInstanceFactory" factory-method="getInstance" id="httpClient"/>

好啦,接下来我们就可以去 Spring 容器中获取一个名为 httpClient 的对象了,拿到手的就是 OkHttpClient 实例。

1.4 FactoryBean

当然,也可以通过 FactoryBean 来解决上述问题,FactoryBean 松哥在之前的文章中刚刚和大家介绍过,我们来看下:

public class OkHttpClientFactoryBean implements FactoryBean<OkHttpClient> {@Overridepublic OkHttpClient getObject() throws Exception {return new OkHttpClient.Builder().connectTimeout(5, TimeUnit.SECONDS).readTimeout(5, TimeUnit.SECONDS).build();}@Overridepublic Class<?> getObjectType() {return OkHttpClient.class;}@Overridepublic boolean isSingleton() {return true;}
}

最后在 Spring 中配置即可:

<bean class="org.javaboy.bean.OkHttpClientFactoryBean" id="httpClient"/>

这个就不做过多解释了,不熟悉的小伙伴可以翻看前面的文章。

上面这三种方案都是传统方案。

特别是前两种,其实我们用的比较少,前两种有一个缺陷,就是我们配置的的 factory-method 都是通过反射来调用的,通过反射调用的话,多多少少性能受点影响。

这种 factory-method 在 Spring 中处理的源码执行时序图如下:

所以最终反射是在 SimpleInstantiationStrategy#instantiate 方法中执行的,就是大家非常熟悉的反射代码了:

@Override
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,@Nullable Object factoryBean, final Method factoryMethod, Object... args) {ReflectionUtils.makeAccessible(factoryMethod);Method priorInvokedFactoryMethod = currentlyInvokedFactoryMethod.get();try {currentlyInvokedFactoryMethod.set(factoryMethod);Object result = factoryMethod.invoke(factoryBean, args);if (result == null) {result = new NullBean();}return result;}finally {if (priorInvokedFactoryMethod != null) {currentlyInvokedFactoryMethod.set(priorInvokedFactoryMethod);}else {currentlyInvokedFactoryMethod.remove();}}
}

好了,这是传统的解决方案。

2. Spring5 解决方案

Spring5 中开始提供了 Supplier,可以通过接口回调获取到一个 Bean 的实例,这种方式显然性能更好一些。

如下:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
GenericBeanDefinition definition = new GenericBeanDefinition();
definition.setBeanClass(Book.class);
definition.setInstanceSupplier((Supplier<Book>) () -> {Book book = new Book();book.setName("深入浅出 Spring Security");book.setAuthor("江南一点雨");return book;
});
ctx.registerBeanDefinition("b1", definition);
ctx.refresh();
Book b = ctx.getBean("b1", Book.class);
System.out.println("b = " + b);

关键就是通过调用 BeanDefinition 的 setInstanceSupplier 方法去设置回调。当然,上面这段代码还可以通过 Lambda 进一步简化:

public class BookSupplier {public Book getBook() {Book book = new Book();book.setName("深入浅出 Spring Security");book.setAuthor("江南一点雨");return book;}
}

然后调用这个方法即可:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
GenericBeanDefinition definition = new GenericBeanDefinition();
definition.setBeanClass(Book.class);
BookSupplier bookSupplier = new BookSupplier();
definition.setInstanceSupplier(bookSupplier::getBook);
ctx.registerBeanDefinition("b1", definition);
ctx.refresh();
Book b = ctx.getBean("b1", Book.class);
System.out.println("b = " + b);

这是不是更有一点 Lambda 的感觉了~

在 Spring 源码中,处理获取 Bean 实例的时候,有如下一个分支,就是处理 Supplier 这种情况的:

AbstractAutowireCapableBeanFactory#createBeanInstance

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {// Make sure bean class is actually resolved at this point.Class<?> beanClass = resolveBeanClass(mbd, beanName);if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,"Bean class isn't public, and non-public access not allowed: " + beanClass.getName());}Supplier<?> instanceSupplier = mbd.getInstanceSupplier();if (instanceSupplier != null) {return obtainFromSupplier(instanceSupplier, beanName);}if (mbd.getFactoryMethodName() != null) {return instantiateUsingFactoryMethod(beanName, mbd, args);}//...return instantiateBean(beanName, mbd);
}@Nullable
private Object obtainInstanceFromSupplier(Supplier<?> supplier, String beanName) {String outerBean = this.currentlyCreatedBean.get();this.currentlyCreatedBean.set(beanName);try {if (supplier instanceof InstanceSupplier<?> instanceSupplier) {return instanceSupplier.get(RegisteredBean.of((ConfigurableListableBeanFactory) this, beanName));}if (supplier instanceof ThrowingSupplier<?> throwableSupplier) {return throwableSupplier.getWithException();}return supplier.get();}
}

上面 obtainFromSupplier 这个方法,最终会调用到第二个方法。第二个方法中的 supplier.get(); 其实最终就调用到我们自己写的 getBook 方法了。

好啦,这是从 Spring5 开始结合 Lamdba 的一种 Bean 注入方式,感兴趣的小伙伴可以试试哦~

相关文章:

Spring5 中更优雅的第三方 Bean 注入

小伙伴们知道&#xff0c;当我们使用 Spring 容器的时候&#xff0c;如果遇到一些特殊的 Bean&#xff0c;一般来说可以通过如下三种方式进行配置&#xff1a; 静态工厂方法实例工厂方法FactoryBean 不过从 Spring5 开始&#xff0c;在 AbstractBeandefinition 类中多了一个属…...

Yolov5-Face 原理解析及算法解析

YOLOv5-Face 文章目录 YOLOv5-Face1. 为什么人脸检测 一般检测&#xff1f;1.1 YOLOv5Face人脸检测1.2 YOLOv5Face Landmark 2.YOLOv5Face的设计目标和主要贡献2.1 设计目标2.2 主要贡献 3. YOLOv5Face架构3.1 模型架构3.1.1 模型示意图3.1.2 CBS模块3.1.3 Head输出3.1.4 stem…...

通俗易懂讲解CPU、GPU、FPGA的特点

1. CPU vs GPU 大家可以简单的将CPU理解为学识渊博的教授&#xff0c;什么都精通&#xff1b;而GPU则是一堆小学生&#xff0c;只会简单的算数运算。可即使教授再神通广大&#xff0c;也不能一秒钟内计算出500次加减法。因此&#xff0c;对简单重复的计算来说&#xff0c;单单一…...

PIC18 DataRAM 笔记

1.疑似最糟糕的英文技术文档段落 Since up to 16 registers may share the same low-order address, the user must always be careful to ensure that the proper bank is selected before performing a data read or write. For example, writing what should be program dat…...

【FFMPEG】AVFilter使用流程

流程图 核心类 AVFilterGraph ⽤于统合这整个滤波过程的结构体 AVFilter 滤波器&#xff0c;滤波器的实现是通过AVFilter以及位于其下的结构体/函数来维护的 AVFilterContext ⼀个滤波器实例&#xff0c;即使是同⼀个滤波器&#xff0c;但是在进⾏实际的滤波时&#xff0c;也…...

爬虫入门06——了解cookie和session

爬虫入门06——了解cookie和session &#xff08;1&#xff09;什么是cookie&#xff0c;有什么作用 http请求是无状态的请求协议&#xff0c;不会记住用户的状态和信息&#xff0c;也不清楚你在这之前访问过什么而当网站需要记录用户是否登录时&#xff0c;就需要在用户登录…...

Ubuntu 的移动梦醒了

老实讲&#xff0c;移动版 Ubuntu 在手机、平板上的发展自始至终可能都没有达到过 Canonical 的期望&#xff0c;既然如此&#xff0c;不再勉为其难地坚持下去&#xff0c;或许才是更加明智的做法。 时至今日&#xff0c;官方显然也意识到了这一点&#xff0c;在早些时候发布的…...

RabbitMQ的集群

新建一个虚拟机,重新安装一个RabbitMQ,不会安装的可以看下面的连接: 在Linux中安装RabbitMQ_流殇꧂的博客-CSDN博客 1.修改/etc/hosts映射文件,两台虚拟机都需要修改 vim /etc/hosts 127.0.0.1 node1 localhost.localdomain localhost4 localhost4.localdomain4 ::1 node1 loca…...

超长上下文处理:基于Transformer上下文处理常见方法梳理

原文链接&#xff1a;芝士AI吃鱼 目前已经采用多种方法来增加Transformer的上下文长度&#xff0c;主要侧重于缓解注意力计算的二次复杂度。 例如&#xff0c;Transformer-XL通过缓存先前的上下文&#xff0c;并允许随着层数的增加线性扩展上下文。Longformer采用了一种注意力…...

ChatGPT爆火 但生成式AI并非全新产物

以ChatGPT、Midjourney 为代表的 AIGC 产品横空出世&#xff0c;在全球掀起新一轮的 AI 技术变革新浪潮。近二十年来&#xff0c;我们见证了从「机器学习」算法到「深度学习」&#xff0c;再到「基础模型」的发展。随着数据量大规模膨胀&#xff0c;可扩展的算力&#xff0c;再…...

深度学习循环神经网络

循环神经网络&#xff08;Recurrent Neural Network&#xff0c;RNN&#xff09;是一种广泛应用于序列数据、自然语言处理等领域的神经网络。与传统的前馈神经网络不同&#xff0c;循环神经网络的输入不仅取决于当前输入&#xff0c;还取决于之前的状态。这使得循环神经网络可以…...

如何规范的设计数据库表

前言对于后端开发同学来说&#xff0c;访问数据库&#xff0c;是代码中必不可少的一个环节。系统中收集到用户的核心数据&#xff0c;为了安全性&#xff0c;我们一般会存储到数据库&#xff0c;比如&#xff1a;mysql&#xff0c;oracle等。后端开发的日常工作&#xff0c;需要…...

【CSS】跳动文字

文章目录 效果展示代码实现 效果展示 代码实现 <!DOCTYPE html> <html><head><meta charset"utf-8" /><title>一颗不甘坠落的流星</title></head><style type"text/css">/* 遮罩盒子样式 */#mask {/* 设…...

arm海思启动udev的错误

近日在配置HI3531D的文件时发现错误 random: udevd: uninitialized urandom read (16 bytes read) random: udevd: uninitialized urandom read (16 bytes read)udev 是一个为你的计算机提供设备事件的 Linux 子系统。通俗来讲就是&#xff0c;当你的计算机上插入了像网卡、外…...

网络协议与攻击模拟-15-DNS协议

DNS 协议 1、了解域名结构 2、 DNS 查询过程 3、在 Windows server 上部署 DNS 4、分析流量 实施 DNS 欺骗 再分析 一、 DNS 1、概念 ● DNS ( domain name system &#xff09;域名系统&#xff0c;作为将域名的 IP 地址的相互映射关系存放在一个分布式的数据库&#xff0…...

ChatGPT将改变教育,而不是摧毁它

01 学校和大学的反应迅速而果断 就在 OpenAI 于 2022 年 11月下旬发布ChatGPT 的几天后&#xff0c;该聊天机器人被广泛谴责为一种免费的论文写作、应试工具&#xff0c;它很容易在作业中作弊。 美国第二大学区洛杉矶联合大学立即阻止了OpenAI网站从其学校网络访问。其他人很…...

springboot在线考试

在线考试系统的开发运用java技术&#xff0c;MIS的总体思想&#xff0c;以及MYSQL等技术的支持下共同完成了该系统的开发&#xff0c;实现了在线考试管理的信息化&#xff0c;使用户体验到更优秀的在线考试管理&#xff0c;管理员管理操作将更加方便&#xff0c;实现目标....

C国演义 [第三章]

第三章 组合分析步骤递归函数的返回值和参数递归结束的条件单层逻辑 组合总和 III 组合 力扣链接 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;n 4, k 2 输出&#xff1…...

数字化时代,企业的数据指标体系

在社会节奏越来越快&#xff0c;处理的信息量越来越大的今天&#xff0c;传统的经营管理模式已经适应不了当下的环境。而由经验、情感组成的业务调整以及决策能力不再能正确指导企业走在正确的方向上&#xff0c;所以数据就成为了企业新的业务优化调整和支撑企业高层管理进行决…...

三分钟了解 RocketMQ消息队列

文章目录 基本概念详细介绍主题&#xff08;Topic&#xff09;消息类型&#xff08;MessageType&#xff09;消息队列&#xff08;MessageQueue&#xff09;消息&#xff08;Message&#xff09;消息视图&#xff08;MessageView&#xff09;消息标签&#xff08;MessageTag&am…...

DockerHub与私有镜像仓库在容器化中的应用与管理

哈喽&#xff0c;大家好&#xff0c;我是左手python&#xff01; Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库&#xff0c;用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...

【位运算】消失的两个数字(hard)

消失的两个数字&#xff08;hard&#xff09; 题⽬描述&#xff1a;解法&#xff08;位运算&#xff09;&#xff1a;Java 算法代码&#xff1a;更简便代码 题⽬链接&#xff1a;⾯试题 17.19. 消失的两个数字 题⽬描述&#xff1a; 给定⼀个数组&#xff0c;包含从 1 到 N 所有…...

dedecms 织梦自定义表单留言增加ajax验证码功能

增加ajax功能模块&#xff0c;用户不点击提交按钮&#xff0c;只要输入框失去焦点&#xff0c;就会提前提示验证码是否正确。 一&#xff0c;模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

Linux云原生安全:零信任架构与机密计算

Linux云原生安全&#xff1a;零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言&#xff1a;云原生安全的范式革命 随着云原生技术的普及&#xff0c;安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测&#xff0c;到2025年&#xff0c;零信任架构将成为超…...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)

上一章用到了V2 的概念&#xff0c;其实 Fiori当中还有 V4&#xff0c;咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务)&#xff0c;代理中间件&#xff08;ui5-middleware-simpleproxy&#xff09;-CSDN博客…...

【Java学习笔记】BigInteger 和 BigDecimal 类

BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点&#xff1a;传参类型必须是类对象 一、BigInteger 1. 作用&#xff1a;适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...

JVM 内存结构 详解

内存结构 运行时数据区&#xff1a; Java虚拟机在运行Java程序过程中管理的内存区域。 程序计数器&#xff1a; ​ 线程私有&#xff0c;程序控制流的指示器&#xff0c;分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。 ​ 每个线程都有一个程序计数…...

Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档&#xff09;&#xff0c;如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下&#xff0c;风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...

JavaScript基础-API 和 Web API

在学习JavaScript的过程中&#xff0c;理解API&#xff08;应用程序接口&#xff09;和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能&#xff0c;使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...

字符串哈希+KMP

P10468 兔子与兔子 #include<bits/stdc.h> using namespace std; typedef unsigned long long ull; const int N 1000010; ull a[N], pw[N]; int n; ull gethash(int l, int r){return a[r] - a[l - 1] * pw[r - l 1]; } signed main(){ios::sync_with_stdio(false), …...