春天猫rtsy_春天:注入列表,地图,可选对象和getBeansOfType()陷阱

春天猫rtsy

如果您使用Spring框架超过一个星期,那么您可能已经知道此功能。 假设您有多个bean实现了给定的接口。 尝试仅自动连接此类接口的一个bean注定会失败,因为Spring不知道您需要哪个特定实例。 您可以通过使用@Primary批注来指定一个优先于其他实现的“ 最重要 ”实现,从而解决此问题。 但是在许多合法的用例中,您想注入所有实现了上述接口的bean。 例如,您有多个验证器,所有验证器都需要在业务逻辑或要同时执行的几种算法实现之前执行。 自动发现所有的实现在运行时是一个奇妙的例证打开/关闭原理 :您可以轻松地添加新的行为,以业务逻辑(验证,算法,策略-对扩展开放 ),无需触摸的业务逻辑本身(修改关闭 )。

万一我有一个快速的介绍,请随时直接跳到后续章节。 因此,让我们举一个具体的例子。 假设您有一个StringCallable接口和多个实现:

interface StringCallable extends Callable<String> { }@Component
class Third implements StringCallable {@Overridepublic String call() {return "3";}}@Component
class Forth implements StringCallable {@Overridepublic String call() {return "4";}}@Component
class Fifth implements StringCallable {@Overridepublic String call() throws Exception {return "5";}
}

现在,我们可以将List<StringCallable>Set<StringCallable>Map<String, StringCallable>String代表bean名称)注入其他任何类。 为了简化,我将注入一个测试用例:

@SpringBootApplication public class Bootstrap { }@ContextConfiguration(classes = Bootstrap)
class BootstrapTest extends Specification {@AutowiredList<StringCallable> list;@AutowiredSet<StringCallable> set;@AutowiredMap<String, StringCallable> map;def 'injecting all instances of StringCallable'() {expect:list.size() == 3set.size() == 3map.keySet() == ['third', 'forth', 'fifth'].toSet()}def 'enforcing order of injected beans in List'() {when:def result = list.collect { it.call() }then:result == ['3', '4', '5']}def 'enforcing order of injected beans in Set'() {when:def result = set.collect { it.call() }then:result == ['3', '4', '5']}def 'enforcing order of injected beans in Map'() {when:def result = map.values().collect { it.call() }then:result == ['3', '4', '5']}}

到目前为止,一切都很好,但是只有第一个测试通过,您能猜出为什么吗?

Condition not satisfied:result == ['3', '4', '5']
|      |
|      false
[3, 5, 4]

毕竟,我们为什么要假设将以与声明bean相同的顺序注入bean? 按字母顺序? 幸运的是,可以使用Ordered接口执行订单:

interface StringCallable extends Callable<String>, Ordered {
}@Component
class Third implements StringCallable {//...@Override public int getOrder() {return Ordered.HIGHEST_PRECEDENCE;}
}@Component
class Forth implements StringCallable {//...@Override public int getOrder() {return Ordered.HIGHEST_PRECEDENCE + 1;}
}@Component
class Fifth implements StringCallable {//...@Override public int getOrder() {return Ordered.HIGHEST_PRECEDENCE + 2;}
}

有趣的是,即使Spring内部注入了LinkedHashMapLinkedHashSet ,也仅对List进行了正确排序。 我猜它没有记录,也就不足为奇了。 为了结束本介绍,您还可以在Java 8中注入Optional<MyService> ,它按预期方式工作:仅在依赖项可用时注入依赖项。 可选依赖项可能会出现,例如,在广泛使用概要文件时,并且某些概要文件中没有引导某些bean。

处理列表非常麻烦。 大多数情况下,您要遍历它们,因此为了避免重复,将这样的列表封装在专用包装器中很有用:

@Component
public class Caller {private final List<StringCallable> callables;@Autowiredpublic Caller(List<StringCallable> callables) {this.callables = callables;}public String doWork() {return callables.stream().map(StringCallable::call).collect(joining("|"));}}

我们的包装器简单地一个接一个地调用所有底层可调用对象,并将它们的结果联接在一起:

@ContextConfiguration(classes = Bootstrap)
class CallerTest extends Specification {@AutowiredCaller callerdef 'Caller should invoke all StringCallbles'() {when:def result = caller.doWork()then:result == '3|4|5'}}

这有点争议,但通常此包装器也实现相同的接口,从而有效地实现复合经典设计模式:

@Component
@Primary
public class Caller implements StringCallable {private final List<StringCallable> callables;@Autowiredpublic Caller(List<StringCallable> callables) {this.callables = callables;}@Overridepublic String call() {return callables.stream().map(StringCallable::call).collect(joining("|"));}}

感谢@Primary我们可以在任何地方简单地自动连接StringCallable ,就好像只有一个bean,而实际上有多个bean一样,我们可以注入Composite。 当重构旧的应用程序时,这很有用,因为它保留了向后兼容性。

为什么我甚至从所有这些基础开始? 如果你仔细关系十分密切,代码片段上面介绍鸡和蛋的问题:实例StringCallable需要的所有实例StringCallable ,所以从技术上来说callables列表应该包括Caller为好。 但是Caller当前正在创建中,所以这是不可能的。 这很有道理,幸运的是,Spring意识到了这种特殊情况。 但是在更高级的情况下,这可能会咬你。 后来,新的开发人员介绍了这一点

@Component
public class EnterpriseyManagerFactoryProxyHelperDispatcher {private final Caller caller;@Autowiredpublic EnterpriseyManagerFactoryProxyHelperDispatcher(Caller caller) {this.caller = caller;}
}

到目前为止,除了类名,其他都没错。 但是,如果其中一个StringCallables对此有依赖关系会怎样?

@Component
class Fifth implements StringCallable {private final EnterpriseyManagerFactoryProxyHelperDispatcher dispatcher;@Autowiredpublic Fifth(EnterpriseyManagerFactoryProxyHelperDispatcher dispatcher) {this.dispatcher = dispatcher;}}

现在,我们创建了一个循环依赖项,并且由于我们通过构造函数进行注入(这一直是我们的本意),因此Spring在启动时会一巴掌:

UnsatisfiedDependencyException:Error creating bean with name 'caller' defined in file ...
UnsatisfiedDependencyException: Error creating bean with name 'fifth' defined in file ...
UnsatisfiedDependencyException: Error creating bean with name 'enterpriseyManagerFactoryProxyHelperDispatcher' defined in file ...
BeanCurrentlyInCreationException: Error creating bean with name 'caller': Requested bean is currently in creation: Is there an unresolvable circular reference?

和我在一起,我在这里建立高潮。 显然,这是一个错误,很遗憾可以通过字段注入(或与此有关的设置)来解决:

@Component
public class Caller {@Autowiredprivate List<StringCallable> callables;public String doWork() {return callables.stream().map(StringCallable::call).collect(joining("|"));}}

现在,通过将注入的bean创建与耦合分离(构造函数注入是不可能的),我们现在可以创建一个循环依赖图,其中Caller持有一个引用Enterprisey...Fifth类的实例,该实例又反过来引用了同一Caller实例。 依存关系图中的循环是一种设计气味,导致无法维持意大利面条关系图。 请避免使用它们,如果构造函数注入可以完全阻止它们,那就更好了。

会议

有趣的是,还有另一种直接适用于Spring guts的解决方案:

ListableBeanFactory.getBeansOfType()

@Component
public class Caller {private final List<StringCallable> callables;@Autowiredpublic Caller(ListableBeanFactory beanFactory) {callables = new ArrayList<>(beanFactory.getBeansOfType(StringCallable.class).values());}public String doWork() {return callables.stream().map(StringCallable::call).collect(joining("|"));}}

问题解决了? 恰恰相反! getBeansOfType()将在创建过程中静默跳过(嗯,有TRACEDEBUG日志…)bean,并且仅返回那些已经存在的bean。 因此,刚刚创建了Caller并成功启动了容器,而它不再引用Fifth bean。 您可能会说我要这样,因为我们有一个循环依赖关系,所以会发生奇怪的事情。 但这是getBeansOfType()的固有功能。 为了了解为什么在容器启动过程中使用getBeansOfType()是一个坏主意 ,请查看以下情形(省略了不重要的代码):

@Component
class Alpha {static { log.info("Class loaded"); }@Autowiredpublic Alpha(ListableBeanFactory beanFactory) {log.info("Constructor");log.info("Constructor (beta?):  {}", beanFactory.getBeansOfType(Beta.class).keySet());log.info("Constructor (gamma?): {}", beanFactory.getBeansOfType(Gamma.class).keySet());}@PostConstructpublic void init() {log.info("@PostConstruct (beta?):  {}", beanFactory.getBeansOfType(Beta.class).keySet());log.info("@PostConstruct (gamma?): {}", beanFactory.getBeansOfType(Gamma.class).keySet());}}@Component
class Beta {static { log.info("Class loaded"); }@Autowiredpublic Beta(ListableBeanFactory beanFactory) {log.info("Constructor");log.info("Constructor (alpha?): {}", beanFactory.getBeansOfType(Alpha.class).keySet());log.info("Constructor (gamma?): {}", beanFactory.getBeansOfType(Gamma.class).keySet());}@PostConstructpublic void init() {log.info("@PostConstruct (alpha?): {}", beanFactory.getBeansOfType(Alpha.class).keySet());log.info("@PostConstruct (gamma?): {}", beanFactory.getBeansOfType(Gamma.class).keySet());}}@Component
class Gamma {static { log.info("Class loaded"); }public Gamma() {log.info("Constructor");}@PostConstructpublic void init() {log.info("@PostConstruct");}
}

日志输出显示了Spring如何在内部加载和解析类:

Alpha: | Class loaded
Alpha: | Constructor
Beta:  | Class loaded
Beta:  | Constructor
Beta:  | Constructor (alpha?): []
Gamma: | Class loaded
Gamma: | Constructor
Gamma: | @PostConstruct
Beta:  | Constructor (gamma?): [gamma]
Beta:  | @PostConstruct (alpha?): []
Beta:  | @PostConstruct (gamma?): [gamma]
Alpha: | Constructor (beta?):  [beta]
Alpha: | Constructor (gamma?): [gamma]
Alpha: | @PostConstruct (beta?):  [beta]
Alpha: | @PostConstruct (gamma?): [gamma]

Spring框架首先加载Alpha并尝试实例化bean。 但是,在运行getBeansOfType(Beta.class)它会发现Beta因此将继续加载和实例化该Beta 。 在Beta内部,我们可以立即发现问题:当Beta询问beanFactory.getBeansOfType(Alpha.class)时,不会得到任何结果( [] )。 Spring将默默地忽略Alpha ,因为它目前正在创建中。 后来一切都按预期进行: Gamma已加载,构造和注入, Beta看到了Gamma ,当我们返回Alpha ,一切就绪。 请注意,即使将getBeansOfType()移至@PostConstruct方法也无济于事–在实例化所有bean时,最终不会执行这些回调–而是在容器启动时。

意见建议

getBeansOfType()很少需要,并且如果您具有循环依赖性,那么结果是不可预测的。 当然,您首先应该避免使用它们,如果您通过集合正确注入了依赖关系,Spring可以预见地处理所有bean的生命周期,并且可以正确地连接它们或在运行时失败。 在bean之间存在循环依赖关系时(有时是偶然的,或者在依赖关系图中的节点和边方面很长), getBeansOfType()会根据我们无法控制的因素(例如CLASSPATH顺序getBeansOfType()产生不同的结果。

PS:对JakubKubryński进行getBeansOfType()故障排除表示getBeansOfType()

翻译自: https://www.javacodegeeks.com/2015/04/spring-injecting-lists-maps-optionals-and-getbeansoftype-pitfalls.html

春天猫rtsy

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

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

相关文章

matlab内维尔差值代码,计算方法上机练习数值积分(包括两次作业).PDF

计算方法上机练习 数值积分(包括两次的作业 )马骢问题&#xff1a; 《计算方法引论》pp.132–133 练习分析&#xff1a;在实际应中基本的数值积分&#xff0c;可 以分为以下种类 &#xff1a;• 牛顿型 &#xff1a;在给定有 限区 间上求等距节 点上 的函数值 。如牛顿-柯茨法则…

用这个C语言骰子代码做选择

点击蓝字关注我们我相信很多人都遇到选择的事情&#xff0c;比较正常的就是&#xff0c;我拿了两个offer要如何选择。用下面这段C代码挺好的&#xff0c;你可以自己加上自己喜欢的判断。#include<stdio.h> #include<stdlib.h> #include<time.h>int main() {i…

java ee的小程序_Java EE应用程序的单片到微服务重构

java ee的小程序您是否曾经想过将现有的Java EE单体应用程序重构为基于微服务的应用程序需要做什么&#xff1f; 该博客说明了一个简单的购物车示例如何转换为基于微服务的应用程序&#xff0c;以及围绕它的一些担忧。 整体和基于微服务的应用程序的完整代码库位于&#xff1a…

matlab工程计算及应用 课程名称,《 MATLAB 工程计算及应用》教学大纲课程名称MATLAB 工程计算及应用.pdf...

《MATLAB 工程计算及应用》教学大纲课程名称 MATLAB 工程计算及应用 课程编号 02T5071课程英文名称 Applications of MATLAB课程性质 选修 学时和学分 32(上机 10)/1 适用专业 工科各专业大纲执笔人 蔡哓君、郭炜 审核人 蔡晓君 先修要求 高等数学、线性代数一、课程基本目的&a…

C语言数据结构:什么是树?什么是二叉树?

点击蓝字关注我们前言在之前的数据结构学习中&#xff0c;我们学习了顺序表、链表、栈、队列这几种结构它们都是用链表或者数组的方式来实现的&#xff0c;主要考察我们对结构体的运用今天让我们来学习一个新的数据结构&#xff0c;也就是下面这副图里面的树啊不好意思&#xf…

git hok json_从战中反弹:将Git提交信息作为JSON返回

git hok json在某些情况下&#xff0c;我们必须知道部署到远程服务器的Web应用程序的确切版本。 例如&#xff0c;客户可能想知道我们是否已经在服务器X上部署了错误修复程序。 当然&#xff0c;我们可以尝试使用“传统”方法找到该问题的答案。 问题是&#xff1a; 没有人不…

oracle plsql异常,【Oracle篇】异常处理和PLSQL

一、所有的PL/SQL异常都具有以下几个基本特征&#xff1a; 错误类型&#xff1a;表示了错误是ORA错误还是PLS错误 错误代号&#xff1a;一串表示错误代号的数字 错误文本&#xff1a;错误消息文本&#xff0c;包括错误代号 二、PL/SQL会产生两种类型的错误 &#xff1a; ORA错误…

如何把CPP源程序改写成C语言?

点击蓝字关注我们曾经参与过公司的bpp项目&#xff0c;就是bluetooth print profile。由于使用了hpijs的开源包&#xff0c;但是是C&#xff0b;&#xff0b;的。由于C&#xff0b;&#xff0b;解释器比C语言解释器占用的存储空间要大500k左右。为了节省有限的存储空间&#xf…

php cdi_使用Fabric8在CDI管理的bean中注入Kubernetes Services

php cdi序幕 我在Kubernetes中最喜欢的是发现服务的方式。 为什么&#xff1f; 主要是因为用户代码不必处理注册&#xff0c;查找服务&#xff0c;也没有网络意外&#xff08;如果您曾经尝试过基于注册表的方法&#xff0c;那么您就会知道我在说什么&#xff09; 。 这篇文章…

oracle日志恢复启用命令,oracle 日志恢复数据详解

1&#xff1a;首先查找redo&#xff0c;如果redo有可供恢复的信息&#xff0c;就那redo中的信息进行恢复&#xff0c;此时一般在恢复时&#xff0c;类似如下:SQL> recover database;Media recovery complete.2&#xff1a;如果在redo中没有找到可供恢复的信息&#xff0c;or…

9 个很酷的 CMD 命令

点击蓝字关注我们ipconfig功能&#xff1a;查询本机IP地址操作方法&#xff1a;只要在在打开的cmd命令界面中输入“ipconfig”就可以了。msg功能&#xff1a;向对方电脑发送一条文本提示操作方法&#xff1a;首先你要知道对方的IP地址&#xff0c;接下来输入命令“msg /server:…

使用java进行婚礼庆祝_#102030:在30天内运行20 10K,庆祝Java 20年

使用java进行婚礼庆祝1995年5月23日是技术史上的重要时刻。 业界似乎并未意识到当天发布的语言会在未来几年内完全改变技术的格局。 Java将在今年的同一天庆祝20岁生日。 Java 20年&#xff0c;哇&#xff01; 回顾20年前的存储器时代&#xff0c;思考一下Java的发明时间/方式…

oracle 插入出错_使用sqlca打印错误原因,ORACLE-Proc:SQLCA

SQL 通信区是用下列语句描述的&#xff1a;EXEC SQL INCLUDE SQLCA&#xff1b;此部分提供了用户运行程序的成败记录和错误处理。SQLCA的组成SQLCA是一个结构类型的变量&#xff0c;它是ORACLE 和应用程序的一个接口。在执行 Pro*C程序时&#xff0c; ORACLE 把每一个嵌入SQL语…

这几行 C++ 代码,真的骚!

点击蓝字关注我们事情是这么一回事&#xff1a;国外有个大佬在StackExchange上发起了一个叫做 Tweetable Mathematical Art 的比赛。参赛者需要用C编写代表三原色的RD、GR、BL三个函数&#xff0c;每个函数都不能超过 140 个字符。每个函数都会接到 i 和 j 两个整型参数&#x…

sts集成jboss_JBoss BPM Travel Agency演示与现代BPM数据集成

sts集成jboss不久前&#xff0c;我们启动了一个规模较大的JBoss Travel Agency演示项目&#xff0c;以展示JBoss BPM Suite的一些更有趣的功能。 我们提供了一系列视频 &#xff0c;不仅向您展示了如何安装它&#xff0c;项目中各种规则和流程工件的含义&#xff0c;还向您介绍…

详解C语言的C#数组

点击蓝字关注我们数组是一种存储相同类型元素的固定大小顺序集合。数组用于存储数据集合&#xff0c;但一般会将数组视为存储在连续存储器位置的相同类型的变量的集合。如果要存储表示100名称学生的分数&#xff0c;需要独立地声明100整数变量。例如&#xff1a;number0.number…

jdk注解_我们正在下注:这个注解很快就会出现在JDK中

jdk注解Yahor最近提出的Stack Overflow问题引起了我的兴趣&#xff1a; 如何在Java 8编译时确保方法签名“实现”功能接口 。 这是一个很好的问题。 让我们假设以下名义类型&#xff1a; FunctionalInterface interface LongHasher {int hash(long x); }该类型强加了清晰的合同…

oracle的连接函数,Oracle各种连接函数总结

1.前言Oracle可用连接函数会介绍以下几个Oracle列转行函数 Listagg()strcat()wmsys.wm_concat()2.Oracle列转行函数 Listagg()2.1最基础的用法&#xff1a;LISTAGG(XXX,XXX) WITHIN GROUP( ORDER BY XXX)用法就像聚合函数一样&#xff0c;通过Group by语句&#xff0c;把每个Gr…

抽象工厂模式设计模式_21世纪的设计模式:抽象工厂模式

抽象工厂模式设计模式这是我的演讲的第二部分&#xff0c;“ 21世纪的设计模式” 。 此模式在Java代码中到处都有使用&#xff0c;尤其是在更多“企业”代码库中。 它涉及一个接口和一个实现。 该界面如下所示&#xff1a; public interface Bakery {Pastry bakePastry(Toppi…

对 C 语言指针最详尽的讲解

点击蓝字关注我们指针对于C来说太重要。然而&#xff0c;想要全面理解指针&#xff0c;除了要对C语言有熟练的掌握外&#xff0c;还要有计算机硬件以及操作系统等方方面面的基本知识。所以本文尽可能的通过一篇文章完全讲解指针。为什么需要指针&#xff1f;指针解决了一些编程…