春天猫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内部注入了LinkedHashMap
和LinkedHashSet
,也仅对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()
将在创建过程中静默跳过(嗯,有TRACE
和DEBUG
日志…)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
春天猫rtsy_春天:注入列表,地图,可选对象和getBeansOfType()陷阱相关推荐
- 春天猫rtsy_春天重试,因为冬天来了
春天猫rtsy 好的,这实际上与冬天无关,众所周知,冬天已经到了 . 它与Spring Retry有关,Spring Retry是一个小的Spring框架库,它使我们可以向应重试的任何任务添加重试功能 ...
- 春天猫rtsy_春天的时代
春天猫rtsy 自从发布第一个版本以来,Spring便获得了成功. 这样一个未知的框架(当时)怎么可能变得如此广泛,以至于公司要求服务员拥有Spring知识? 我认为这是两个主要原因. 首先,使用In ...
- 春天猫rtsy_您最近见过春天吗?
春天猫rtsy 开源应用程序框架Spring昨天在4.0版本中上线 . 被称为"在现代Java开发的最前沿保持Spring活力"的开发,经过改进的平台在设计时牢记了即将到来的Jav ...
- 春天:注入列表,地图,可选对象和getBeansOfType()陷阱
如果您使用Spring框架超过一个星期,那么您可能已经知道此功能. 假设您有多个bean实现了给定的接口. 尝试仅自动接线此类接口的一个bean注定会失败,因为Spring不知道您需要哪个特定实例. ...
- 春不语,春天却能催醒百花。
春不语,春天却能催醒百花.花不语,花却能醉人心田.春天的记忆如白云朵朵,飘在记忆的天空.如河水清清流在记忆的心田.如树木郁郁长在记忆的旅途.如桃花朵朵开在记忆的春天.温暖记忆,永留一份欢乐.我相信在未 ...
- 淘宝/天猫关键词搜索店铺列表 API接口,店铺列表API接口,店铺商品API接口
一.淘宝/天猫关键词搜索店铺列表 API接口,店铺列表API接口,店铺商品API接口参数如下: 1.公共参数: 名称 类型 必须 描述 key String 是 调用key(必须以GET方式拼接在UR ...
- 【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入具体的操作细节 | 获取要注入事件的 View 对象 | 通过反射获取 View 组件的事件设置方法 )
文章目录 前言 一.获取要注入事件的 View 对象 二.通过反射获取 View 组件的事件设置方法并执行 前言 Android 依赖注入的核心就是通过反射获取 类 / 方法 / 字段 上的注解 , ...
- 【Spring实战】注入非Spring Bean对象
2019独角兽企业重金招聘Python工程师标准>>> 大家经常用到Spring IOC去管理对象之间的依赖关系,但一般情况下都有一个前提:这些Bean对象必须是通过Spring容器 ...
- C++经典问题:如果对象A中有对象成员B,对象B没有默认构造函数,那么对象A必须在初始化列表中初始化对象B?
对象成员特点总结: (1)实例化对象A时,如果对象A有对象成员B,那么先执行对象B的构造函数,再执行A的构造函数. (2)如果对象A中有对象成员B,那么销毁对象A时,先执行对象A的析构函数,再执行B的 ...
最新文章
- 关于未捕获异常的处理(WPF)
- vs2012 entity framework mysql_MVC4,MVC3,VS2012+ entity framework Migration from Sqlserver
- CISC与RISC的区别?
- Java知识积累-基础篇
- 扇贝有道180919每日一句
- Navicat Premium 连接 MongoDB 失败: Cannot connect to MongoDB.No suitable servers found: ......
- 大学计算机专业学习哪些课程?
- OpenStack 2015年度总结
- 招聘方眼里的猎聘和Boss直聘直观对比
- matlab纵坐标两边标注,matlab纵坐标标注
- 【物流篇】数商云物流供应链解决方案
- [转载]常用自动挂机APP下载
- Tbase基础积累二之数据迁移工具dbbridge
- spry提示信息设置html,[原]Spry框架:表单验证构件
- python将列表中的偶数变成平方、奇数不变_编写程序,将列表s=[9,7,8,3,2,1,5,6]中的偶数变成它的平方,奇数保持不变,运行效果如书上图所示。_学小易找答案...
- 计算机毕业设计Android宠物领养救助系统app(源码+系统+mysql数据库+Lw文档)
- 博客园博客Wiz测试
- Altium designer18设置原理图尺寸
- 音圈电机工作原理与直线电机的对比
- MIT博士研发绘画机器人Utensil,精通绘画和激光切割