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

如果您使用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持有Fifth类的一个实例,该实例引用Enterprisey... ,而后者又又引用了同一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()会静默跳过正在创建的bean(嗯,有TRACEDEBUG日志…),并且仅返回那些已经存在的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

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

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

相关文章

python实现表格分析与建模_python实现数据分析与建模

前言首先我们做数据分析&#xff0c;想要得出最科学&#xff0c;最真实的结论&#xff0c;必须要有好的数据。而实际上我们一般面对的的都是复杂&#xff0c;多变的数据&#xff0c;所以必须要有强大的数据处理能力&#xff0c;接下来&#xff0c;我从我们面临的最真实的情况&a…

Unity 利用Coroutine实现跳动数字效果

纯粹转载&#xff1a;转载注明参考链接&#xff01; 参考链接&#xff1a;http://xataxnova.blog.163.com/blog/static/236620063201451061738122/&#xff0c;作者&#xff1a;网易博客 xataxnova 示例代码&#xff1a;&#xff08;将该脚本绑在一个Lable对象上&#xff0c;即…

java mvc 案例_springmvc经典案例

本想自己写一下总结&#xff0c;但是发现一篇好文&#xff0c;转发一下&#xff0c;日后自己再做补充&#xff1b;感谢Sunnier&#xff0c;引自&#xff1a;https://www.cnblogs.com/sunniest/p/4555801.htmlSpringMVC学习笔记----一、SpringMVC基础入门&#xff0c;创建一个He…

Ajax.ActionLink 辅助方法实现局部刷新

1&#xff0c;需要引用一个JS文件jquery.unobtrusive-ajax.min.js【MVC4 默认存在Scripts文件夹里】引用到母版页 _Layout.cshtml&#xff0c;并且一定要放在JQuery引用文件之后 2&#xff0c;需要添加一个分布视图到指定的目录里&#xff0c;分布视图是用来显示局部刷新的 3&a…

java 命名约定_Java命名约定

java 命名约定我想写这篇简短的文章来帮助某些难以记住Java API类和方法名称的人。 如您所知&#xff0c;Java是区分大小写的语言&#xff0c;要构建Java程序&#xff0c;您需要使用许多内置API类和方法。 而且&#xff0c;初学者发现很难准确地记住方法名称和类名称而不改变大…

Discuz UCenter 修改手记 - 2014.12.19

最近在整JAVA和UCENTER的东西&#xff0c;受限于项目架构需要&#xff0c;无法完全以UCENTER为中心&#xff0c;所以在对接过程中遇到了许多不愉快的事情。经历多番研究&#xff0c;终于解决了其中了两个大问题&#xff0c;现记录下来&#xff0c;以备日后查看。 一、解决email…

java泛型方法无参_从头再来:Java泛型(开发必须知道的)

Java泛型老规矩&#xff0c;测试一下&#xff0c;下面的程序能否正常运行&#xff1f;为什么&#xff1f;开头测试题泛型就是实现了参数化类型&#xff0c;也就是代码可以适用于多种类型。泛型是在编译期检查的&#xff0c;在编译期检查类型是否安全。我认为泛型最有用的就是和…

几种常见模式识别算法整理和总结

这学期选了门模式识别的课。发现最常见的一种情况就是&#xff0c;书上写的老师ppt上写的都看不懂&#xff0c;然后绕了一大圈去自己查资料理解&#xff0c;回头看看发现&#xff0c;Ah-ha&#xff0c;原来本质的原理那么简单&#xff0c;自己一開始仅仅只是被那些看似formidab…

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

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

意外分配– JIT编译抖动

在研究ByteWatcher时 &#xff08;请参阅我的上一篇 文章 &#xff09;&#xff0c;我遇到了一些非常奇怪的事情。 这是实际的代码段&#xff0c;用于找出特定线程上的分配量&#xff1a; return (long) mBeanServer.invoke(name,GET_THREAD_ALLOCATED_BYTES,PARAMS,SIGNATUR…

fastd java下载_FastReport VCL报表控件

FastReport VCL报表控件是著名的 Delphi 打印控件。含全部源码。支持 Delphi 10.2 Tokyo&#xff0c;FastReport可以在大量的方式报表的创建过程中操纵对象。快速发展的报表和随后的打印&#xff0c;这样的优点&#xff0c;正如预览打印文档的外观。专业版除了报表标准版的功能…

#102030:在30天内运行20 10K来庆祝Java的20年

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

View 的 android:visibility属性的讨论

Android VIEW 中的 visibility 属性&#xff0c;在API中的描述为&#xff1a;Controls the initial visibility of the view. [控制VIEW的初始可见性]。 其中包含三个参数&#xff1a; * 默认值为visible&#xff0c;可见。这里需要注意的是 "invisible" 和 "go…

jms java client mq_将Java客户端(JMS)连接到IBM MQ时出现问题

我正在尝试使用基本上通过以下方式构建的Java客户端使用SSL消耗IBM MQ(版本8.0.0.8)&#xff1a;Oracle JKD 8和IBM JRE 7(出于测试目的&#xff0c;我每个都有一个客户端)com.ibm.mq.allclient-9.1.0.0.jarjavax.jms-api-2.0.1.jarspring-jms-4.3.7.RELEASE.jarspring-jms-4.3…

JQuery-Dialog(弹出窗口,遮蔽窗口)

在Ajax中经常用到的弹出窗口和遮蔽窗口。自己写肯定是一个最佳方案&#xff0c;但时间和成本上&#xff0c;还是决定了寻找现成的吧。大概罗列一下。需要我满足我几个条件 一定要简洁方便拥有遮蔽功能&#xff0c;Model Dialog &#xff0c;所谓的模态窗口可以根据HTML弹出窗口…

java 设置系统参数_Java如何设置系统参数和运行参数

系统参数系统级全局变量&#xff0c;该参数在程序中任何位置都可以访问到。优先级最高&#xff0c;覆盖程序中同名配置。系统参数的标准格式为&#xff1a;-Dargnameargvalue&#xff0c;多个参数之间用空格隔开&#xff0c;如果参数值中间有空格&#xff0c;则用引号括起来。其…

HDU 1244 DP

题目大意: 我们需要将一串数字分成多个确定个数的连续段&#xff0c;在得到所有段的和的最大值 定义一个dp[i][j]数组表示在前j个数中取满 i 个段所能得到的最大值 那么也就是说明在这道题目当中每一段都是必须要被取到的 能够取到的前提是 j > cnt[i] //表示前 i 段的数字个…

java怎么生成字母_java自动生成字母

新建一个类SpringContextUtil.java&#xff1a;import org.springframework.beans.BeansException;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;/*** 获取spring容器&#xff0c;以访问容器中定义的其…

一天一块钱第二天翻倍_再把钱翻倍

一天一块钱第二天翻倍总览 很久以前&#xff0c;我写了一篇关于用双倍赚钱的文章。 但是&#xff0c;当解决方案相当简单时&#xff0c;仍然是许多开发人员普遍担心的问题。 用双倍赚钱的问题 double有两种类型的错误。 它存在表示错误。 即它不能精确地表示所有可能的十进制…

移动端回到顶部

PC端回到顶部&#xff0c;很多人都是用js来写的。这样效果比较好一些了&#xff0c;但是在移动端的时候&#xff0c;就没必要搞个animate的动画了&#xff0c;然后实现回到顶部的方法也有&#xff1a;window.scrollTo(0,0)。但是更简单的就是a锚点里面href"#"&#x…