Spring:自动接线或不自动接线

从使用Spring 2.5开始,我从基于XML的应用程序上下文切换到了注释。 尽管我发现那些非常有用且节省大量时间的人,但我始终觉得在灵活性方面我失去了一些东西。 特别是@Autowired批注-或标准@Inject-在我看来就像新的“新”,增加了类之间的耦合,并使得在需要时更难以更改实现。 我仍然有这种感觉,但是我已经学到了一种有趣的模式来限制测试代码时的问题,即当我想将bean的真实实现替换为模拟时。 让我们用一个例子来说明。 我想构建一个应用程序,以便为我在网络上找到有趣的东西。 我将从一个接受URL的服务开始,如果它是一个有趣的新URL,则将其添加书签。 直到最近,我可能已经编写了如下代码:

@Named
public class AwesomenessFinder {@Injectprivate BlogAnalyzer blogAnalyzer;@Injectprivate BookmarkService bookmarkService;public void checkBlog(String url) {if (!bookmarkService.contains(url) && blogAnalyzer.isInteresting(url)) {bookmarkService.bookmark(url);}}
}

不好,你明白为什么吗? 如果没有,请继续阅读,希望您今天能学到一些有用的东西。 因为我很认真,所以我想为此代码创建单元测试。 希望我的算法很好,但是我想确保它不会为无聊的博客添加书签或将相同的URL添加为书签两次。 那就是问题所在,我想将AwesomenessFinder与它的依赖隔离开来。 如果我使用的是XML配置,则可以在测试上下文中简单地注入模拟实现,是否可以使用批注来实现? 嗯,是! 有一种方法,带有@Primary批注。 让我们尝试为BlogAnalyzer和BookmarkService创建模拟实现。

@Named
@Primary
public class BlogAnalyzerMock implements BlogAnalyzer {public boolean isInteresting(String url) {return true;}
}@Named
@Primary
public class BookmarkServiceMock implements BookmarkService {Set bookmarks = new HashSet();public boolean contains(String url) {return bookmarks.contains(url);}public void bookmark(String url) {bookmarks.add(url);}
}

因为我使用Maven并将这些模拟放置在test / java目录中,所以主应用程序将看不到它们,并将注入实际的实现。 另一方面,单元测试将看到2种实现。 @Primary是必需的,以防止出现类似以下的异常:

org.springframework.beans.factory.NoSuchBeanDefinitionException:
No unique bean of type [service.BlogAnalyzer] is defined: expected single matching bean
but found 2: [blogAnalyzerMock, blogAnalyzerImpl]

现在,我可以测试我的算法了:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:application-context.xml")
public class AwesomenessFinderTest {@Injectprivate AwesomenessFinder awesomenessFinder;@Injectprivate BookmarkService bookmarkService;@Testpublic void checkInterestingBlog_bookmarked() {String url = "http://www.javaspecialists.eu";assertFalse(bookmarkService.contains(url));awesomenessFinder.checkBlog(url);assertTrue(bookmarkService.contains(url));}
}

不错,我测试了幸福的道路,一个有趣的博客被加了书签。 现在我该如何测试其他情况。 当然,我可以在模拟中添加一些逻辑,以查找某些已添加书签或不感兴趣的URL,但这会变得笨拙。 这是一个非常简单的算法,想象一下测试更复杂的东西有多糟糕。 有一种更好的方法需要重新设计我的类以及注入依赖项的方法。 方法如下:

@Named
public class AwesomenessFinder {private BlogAnalyzer blogAnalyzer;private BookmarkService bookmarkService;@Injectpublic AwesomenessFinder(BlogAnalyzer blogAnalyzer, BookmarkService bookmarkService) {this.blogAnalyzer = blogAnalyzer;this.bookmarkService = bookmarkService;}public void checkBlog(String url) {if (!bookmarkService.contains(url) && blogAnalyzer.isInteresting(url)) {bookmarkService.bookmark(url);}}
}

请注意,我仍然使用@Inject注释自动关联我的依赖项,因此AwesomenessFinder的调用者不会受到影响。 例如,客户端类中的以下内容仍然有效:

@Inject
private AwesomenessFinder awesomenessFinder;

但是,最大的不同是我在构造函数级别自动装配,这为我提供了一种注入模拟实现的干净方法。 而且,由于我们是在模拟,所以我们使用一个模拟库。 去年,我写了一篇有关嘲讽的文章,其中我使用了丑陋的二传手来注入嘲讽。 使用这里提到的技术,我不再需要暴露依赖项,我得到了更好的封装。 这是更新后的测试用例的样子:

public class AwesomenessFinderTest {@Testpublic void checkInterestingBlog_bookmarked() {BookmarkService bookmarkService = mock(BookmarkService.class);when(bookmarkService.contains(anyString())).thenReturn(false);BlogAnalyzer blogAnalyzer = mock(BlogAnalyzer.class);when(blogAnalyzer.isInteresting(anyString())).thenReturn(true);AwesomenessFinder awesomenessFinder = new AwesomenessFinder(blogAnalyzer, bookmarkService);String url = "http://www.javaspecialists.eu";awesomenessFinder.checkBlog(url);verify(bookmarkService).bookmark(url);}
}

请注意,现在这是纯Java语言,无需使用Spring注入模拟。 而且,这些模拟的定义与它们的用法位于同一位置,从而简化了维护。 为了更进一步,让我们实现其他测试用例。 为了避免代码重复,我们将重构测试类并引入一些枚举,以使测试用例尽可能地表达。

public class AwesomenessFinderTest {private enum Knowledge {KNOWN, UNKNOWN};private enum Quality {INTERESTING, BORING};private enum ExpectedBookmark {STORED, IGNORED}private enum ExpectedAnalysis {ANALYZED, SKIPPED}@Testpublic void checkInterestingBlog_bookmarked() {checkCase(Knowledge.UNKNOWN, Quality.INTERESTING,ExpectedBookmark.STORED, ExpectedAnalysis.ANALYZED);}@Testpublic void checkBoringBlog_ignored() {checkCase(Knowledge.UNKNOWN, Quality.BORING,ExpectedBookmark.IGNORED, ExpectedAnalysis.ANALYZED);}@Testpublic void checkKnownBlog_ignored() {checkCase(Knowledge.KNOWN, Quality.INTERESTING,ExpectedBookmark.IGNORED, ExpectedAnalysis.SKIPPED);}private void checkCase(Knowledge knowledge, Quality quality,ExpectedBookmark expectedBookmark, ExpectedAnalysis expectedAnalysis) {BookmarkService bookmarkService = mock(BookmarkService.class);boolean alreadyBookmarked = (knowledge == Knowledge.KNOWN) ? true : false;when(bookmarkService.contains(anyString())).thenReturn(alreadyBookmarked);BlogAnalyzer blogAnalyzer = mock(BlogAnalyzer.class);boolean interesting = (quality ==  Quality.INTERESTING) ? true : false;when(blogAnalyzer.isInteresting(anyString())).thenReturn(interesting);AwesomenessFinder awesomenessFinder = new AwesomenessFinder(blogAnalyzer, bookmarkService);String url = "whatever";awesomenessFinder.checkBlog(url);if (expectedBookmark == ExpectedBookmark.STORED) {verify(bookmarkService).bookmark(url);} else {verify(bookmarkService, never()).bookmark(url);}if (expectedAnalysis == ExpectedAnalysis.ANALYZED) {verify(blogAnalyzer).isInteresting(url);} else {verify(blogAnalyzer, never()).isInteresting(url);}}
}

最后但并非最不重要的一点是,构造函数注入的一个不错的好处是能够将类的所有依赖项放在同一位置(构造函数)。 如果依赖项列表超出了控制范围,则构造函数的大小会产生非常明显的代码味道。 这表明您在班级中肯定承担了多个责任,您应该将其划分为多个班级,以便于进行单元测试更容易隔离。

参考: 自动装配或不自动装配从我们JCG的合作伙伴达勒帕热的编程和更多的博客。

翻译自: https://www.javacodegeeks.com/2013/04/spring-to-autowire-or-not-to-autowire.html

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

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

相关文章

刷题总结——Cut the Sequence(POJ 3017 dp+单调队列+set)

题目: Description Given an integer sequence { an } of length N, you are to cut the sequence into several parts every one of which is a consecutive subsequence of the original sequence. Every part must satisfy that the sum of the integers in the …

java crud事件回调_java回调机制 - 神是到着念的个人空间 - OSCHINA - 中文开源技术交流社区...

软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用、回调和异步调用。同步调用是一种阻塞式调用,调用方要等待对方执行完毕才返回,它是一种单向调用;回调是一种双向调用模式&…

CSS盒子的浮动

在标准流中,一个块级元素在水平方向会自动伸展,直到包含它的元素的边界;而在竖直方向和兄弟元素依次排列,不能并排。使用“浮动”方式后,块级元素的表现就会有所不同。 CSS中有一个float属性,默认为no…

servlet中url-pattern之/与/*的区别

转载于:https://www.cnblogs.com/hwgok/p/8835350.html

很少使用“ ControlFlowException”

控制流是命令式编程的“遗留物”,它已泄漏到其他各种编程范例中,包括Java的面向对象范例 。 除了有用的和无处不在的分支和循环结构外,还包括原语(例如GOTO)和非局部变量(例如异常)。 让我们仔细…

java的if里有多个if_代码里写很多if会影响效率吗?

看你怎么写 if.嵌入很多层if的代码叫做“箭头代码”,是一个anti-pattern。 这种代码会增加程序的循环复杂度 (Cyclomatic complexity)具体可以看这里:Flattening Arrow Code这里:总的来说,程序里用if-else是有开销的。每次conditi…

python基础总结(6)

一、模块。 一个模块就是一个python文件,.py文件。 需要注意的是:自己创建模块时要注意命名,不能和Python自带的模块名称冲突。例如,系统自带了sys模块,自己的模块就不可命名为sys.py,否则将无法导入系统自…

[10.10模拟] water

题意: 有一块矩形土地被划分成 n*m 个正方形小块。这些小块高低不平,每一小块都有自己的高度。水流可以由任意一块地流向周围四个方向的四块地中,但是不能直接流入对角相连的小块中。一场大雨后,由于地势高不同,许多地方都积存了不少降水。给定每个小块的高度,求每个…

使用Amazon Web Services(EC2)

正如我上周参加技术动手AWS培训之前所发布的。 这些天的课程当然是我以前使用过的标准EC2和S3服务。 除此之外,我们还使用了RDS , Elastic Load Balancing , SNS和VPC , Elastic Beanstalk并讨论了许多术语和业务案例。 在本文中&…

java整型和浮点型_Java基本的程序结构设计 整形和浮点型

整形:int 4字节short 2字节long 8字节byte 1字节int的大小差不多是20亿。整形计算如果两个int进行加减乘除数学运算的时候,最终的结果仍然是int,有可能出现了溢出,那么结果就不是我们想要的了。如下:System.out.printl…

盒子模型阴影设置,爱奇艺阴影配置

box-shadow的配置阴影 ul li:hover{ border-color: #dfdfdf; border-radius: 10px; -moz-box-shadow: 0 5px 5px rgba(0, 0, 0, 0.1), 0 0 10px 0 rgba(0, 0, 0, 0.2); -webkit-box-shadow: 0 5px 5px rgba(0, 0, 0, 0.1), 0 0 10px 0 rgba(0, 0, 0, 0.2); box-shadow: 0 5px 5…

Linux在线扫描热添加的SCSI/iSCSI设备

SCSI接口具有应用范围广、多任务、带宽大、CPU占用率低,以及热插拔等优点,在服务器中广泛的应用。 当然在虚拟化平台支持下,也能模拟出SCSI设备,方便在虚拟机上热添加SCSI设备(一般是硬盘) 但是在热添加SCS…

第四周PSP

1.本周PSP 2.本周进度条 3.本周累计进度图 代码积累折线图 博文字数积累折线图 4.本周PSP饼状图 转载于:https://www.cnblogs.com/yuanyue-nenu/p/7648565.html

适用于JDeveloper 11gR2的Glassfish插件

众所周知, ADF Essentials是使用Java构建Web应用程序的绝佳框架,它可以自由开发和部署。 您可以在Glassfish(3.1)服务器上部署ADF Essentials应用程序。 但是,JDeveloper并不带有嵌入式Glassfish服务器,而是…

java 李刚 pdf_Java数据库技术详解(李刚) PDF_源雷技术空间

资源名称:Java数据库技术详解(李刚) PDF第一篇 数据库基础篇第1章 Java和数据库 21.1 Java概述 21.1.1 跨平台性 21.1.2 面向对象 21.1.3 安全性 31.1.4 简单性 31.1.5 健壮性和自动内存管理 31.2 Java的开发和运行环境 31.2.1 JDK的安装 31.2.2 配置JDK的环境变量 …

html css3模拟心的跳动

<!DOCTYPE html> <html> <head><meta charset"utf-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><title>模拟心的跳动</title> </head> <style type"text/css">* {m…

-------------------前端技术文章收集-------------------

十个最常见的lodash方法 十个必备的js工具函数 (英文原文) You dont need 系列 underscore常用方法 (长期更新) 转载于:https://www.cnblogs.com/skura23/p/7649405.html

bzoj4152: [AMPPZ2014]The Captain

水。。。 这个建边排序一下从一边连向一边 dij在这种稀疏图果然不够优秀啊。只是学了一发。 #include<cstdio> #include<iostream> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> #include<queue> usin…

使用Hibernate的JPA 2.0标准查询

JPA 2.0中引入了条件查询 。 借助条件查询&#xff0c;您可以以类型安全的方式编写查询。 在进行标准查询之前&#xff0c;开发人员必须通过构建基于对象的查询定义来编写查询。 构建查询时&#xff0c;可能会出现语法错误的情况。 条件查询API提供了创建具有编译时安全性的结构…

response对象的使用

使用response对象提供的sendRedirect()方法可以将网页重定向到另一个页面。重定向操作支持将地址重定向到不同的主机上&#xff0c;这一点与转发是不同的。在客户端浏览器上将会得到跳转地址&#xff0c;并重新发送请求链接。用户可以从浏览器的地址栏中看到跳转后的地址。进行…