NAEX藏宝网2u支付平台天下是什么平台

&figure&&img src=&https://pic2.zhimg.com/v2-4cf1b993f34d_b.jpg& data-rawwidth=&1280& data-rawheight=&800& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic2.zhimg.com/v2-4cf1b993f34d_r.jpg&&&/figure&&p&之前在《&a href=&http://link.zhihu.com/?target=http%3A//bridgeforyou.cn//How-to-Persuade-Your-Teemmate-to-use-TDD/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&如何说服你的同事使用TDD&/a&》中介绍了为什么要使用TDD(测试驱动开发),以及如何使用TDD写代码。文章发表后,有同学在评论区中表示文章写得不错,但是举得例子太过脱离实际了,能不能举一个在实际工作中的例子呀。这篇文章,就来分享一下在Spring Boot中,如何使用TDD写出&b&功能健壮&/b&、&b&代码整洁&/b&的&b&高质量接口&/b&。&/p&&p&我将用一个简单的案例,向你展示:&/p&&ul&&li&什么是“接口文档-&测试用例-&产品代码”的TDD开发流程&/li&&li&在Spring Boot中,怎样同时使用集成测试和单元测试,保证测试的覆盖面&/li&&li&使用Spring Boot测试框架的一些优秀实践&/li&&li&为什么要使用TDD&/li&&/ul&&h2&接口文档&/h2&&p&我们要实现的接口,功能非常简单,就是能够对敏感字眼进行检查的发帖功能,不允许发带有“shit”、“fxxk”之类字眼的帖子,嗯,我们是一个文明的社区!&/p&&p&接口文档如下:&/p&&p&&b&接口说明&/b&&br&发布帖子,同时对敏感字眼进行校验&/p&&p&&b&URL&/b& &br&/v2.0/posts&/p&&p&&b&HTTP请求方式&/b&&br&POST&/p&&p&&b&请求体&/b&&br&参数: content(帖子的内容,String)&/p&&p&&b&响应&/b&&br&200 创建成功,返回成功创建的帖子信息&br&400 创建失败,帖子中包含敏感字眼&/p&&p&&b&示例1&/b&&br&请求体&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&{
&content&: &hello world!&
&/code&&/pre&&/div&&p&响应&br&200&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&{
&content&: &hello world!&,
&username&: &sexy code&,
&createDate&: 1
&/code&&/pre&&/div&&p&&b&示例2&/b&&br&请求体&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&{
&content&: &hello shit!&
&/code&&/pre&&/div&&p&响应&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&{
&errorCode&: 100001,
&errorInfo&: &post contains sensitive info&
&/code&&/pre&&/div&&h2&测试策略&/h2&&p&如果不采用TDD,那么下一步就是拿着接口文档开发接口了,但是这很不TDD。TDD要求我们先写测试用例。&/p&&blockquote&&i&你或许会认为不写测试用例,同样可以写出实现功能的接口。别急,测试用例带给你的好处远远不止正确性。&/i&&/blockquote&&p&看完上面那份接口文档,我们很自然的想到有下面两个测试用例:&/p&&ol&&li&发布内容合规的帖子,成功发布,返回200和对应的数据&/li&&li&发布含有敏感字眼的帖子,发布失败,返回400和错误提示&/li&&/ol&&p&上面这两个测试用例,都是从模拟客户端请求,到后台业务层和数据库层操作,再到返回响应的端到端测试,因此属于&b&集成测试&/b&。&br&集成测试要求我们启动Spring Boot的容器,因此运行起来会比较慢。通常情况下,集成测试只覆盖基本场景,更细致的测试,可以交给&b&单元测试&/b&。&br&比如在这个场景中,我们可以针对判断内容中是否含有敏感信息的这个功能,进行单元测试,这也就要求我们把这个功能,抽取成一个方法,这样才方便我们写测试用例。由于单元测试不需要启用Spring Boot容器,因此测试用例运行起来将非常迅速。&/p&&blockquote&&i&TDD在不知不觉中提高了我们的代码质量。它让我们从测试用例的角度出发,思考如何写出方便测试的代码,方便测试的代码,往往是符合单一职责的。&/i&&/blockquote&&h2&集成测试&/h2&&p&制定好测试策略之后,下面开始写第一个测试用例。&/p&&p&一个测试用例通常包括以下三个步骤:&/p&&ol&&li&创建环境,初始化数据&/li&&li&执行操作&/li&&li&验证操作结果&/li&&/ol&&p&对于我们这个发帖的接口,那就是:&/p&&ol&&li&创建Spring Boot容器&/li&&li&向发帖的接口,发送Post请求&/li&&li&根据返回的帖子id,去数据库查询,看查到的数据,是不是和发送的数据一致&/li&&/ol&&p&使用Spring Boot提供的测试框架,可以很轻松的将上面这个过程写成代码(&b&本文的所有代码,可到&a href=&http://link.zhihu.com/?target=https%3A//github.com/hzy38324/tiny-facebook& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Github&/a&下载,欢迎加星&/b&):&/p&&div class=&highlight&&&pre&&code class=&language-java&&&span&&/span&&span class=&nd&&@RunWith&/span&&span class=&o&&(&/span&&span class=&n&&SpringRunner&/span&&span class=&o&&.&/span&&span class=&na&&class&/span&&span class=&o&&)&/span&
&span class=&nd&&@SpringBootTest&/span&
&span class=&nd&&@AutoConfigureMockMvc&/span&
&span class=&kd&&public&/span& &span class=&kd&&class&/span& &span class=&nc&&PostControllerV2ITTest&/span& &span class=&o&&{&/span&
&span class=&kd&&public&/span& &span class=&kd&&static&/span& &span class=&kd&&final&/span& &span class=&n&&String&/span& &span class=&n&&POST_CONTENT_VALID&/span& &span class=&o&&=&/span& &span class=&s&&&post content test&&/span&&span class=&o&&;&/span&
&span class=&kd&&public&/span& &span class=&kd&&static&/span& &span class=&kd&&final&/span& &span class=&n&&String&/span& &span class=&n&&POST_URL&/span& &span class=&o&&=&/span& &span class=&s&&&/v2.0/posts&&/span&&span class=&o&&;&/span&
&span class=&nd&&@Autowired&/span&
&span class=&kd&&private&/span& &span class=&n&&MockMvc&/span& &span class=&n&&mockMvc&/span&&span class=&o&&;&/span&
&span class=&nd&&@Autowired&/span&
&span class=&kd&&private&/span& &span class=&n&&ObjectMapper&/span& &span class=&n&&objectMapper&/span&&span class=&o&&;&/span&
&span class=&nd&&@Autowired&/span&
&span class=&kd&&private&/span& &span class=&n&&PostRepository&/span& &span class=&n&&postRepository&/span&&span class=&o&&;&/span&
&span class=&nd&&@Test&/span&
&span class=&kd&&public&/span& &span class=&kt&&void&/span& &span class=&nf&&testCreatePost_returnSuccess&/span&&span class=&o&&()&/span& &span class=&kd&&throws&/span& &span class=&n&&Exception&/span& &span class=&o&&{&/span&
&span class=&n&&ResultActions&/span& &span class=&n&&resultActions&/span& &span class=&o&&=&/span& &span class=&n&&sendCreatePostRequest&/span&&span class=&o&&(&/span&&span class=&n&&POST_CONTENT_VALID&/span&&span class=&o&&);&/span&
&span class=&n&&checkCreateValidPostResult&/span&&span class=&o&&(&/span&&span class=&n&&resultActions&/span&&span class=&o&&,&/span& &span class=&n&&POST_CONTENT_VALID&/span&&span class=&o&&);&/span&
&span class=&o&&}&/span&
&span class=&o&&...&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&p&PostControllerV2ITTest类上的几个注解,@RunWith、@SpringBootTest等,是Spring Boot提供的用于创建集成测试环境的注解,本文重点在于TDD,因此这几个注解的具体用途和原理就不一一赘述了,有兴趣的同学可以查看&a href=&http://link.zhihu.com/?target=https%3A//docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Spring Boot官方文档&/a&中关于测试框架的介绍。 &br&代码中发送请求的函数sendCreatePostRequest和检查请求结果的函数checkCreateValidPostResult分别如下:&br&sendCreatePostRequest:&/p&&div class=&highlight&&&pre&&code class=&language-java&&&span&&/span&
&span class=&kd&&private&/span& &span class=&n&&ResultActions&/span& &span class=&nf&&sendCreatePostRequest&/span&&span class=&o&&(&/span&&span class=&n&&String&/span& &span class=&n&&postContent&/span&&span class=&o&&)&/span& &span class=&kd&&throws&/span& &span class=&n&&Exception&/span& &span class=&o&&{&/span&
&span class=&n&&PostCreateDTO&/span& &span class=&n&&postCreateDTO&/span& &span class=&o&&=&/span& &span class=&k&&new&/span& &span class=&n&&PostCreateDTO&/span&&span class=&o&&(&/span&&span class=&n&&postContent&/span&&span class=&o&&);&/span&
&span class=&k&&return&/span& &span class=&n&&mockMvc&/span&&span class=&o&&.&/span&&span class=&na&&perform&/span&&span class=&o&&(&/span&&span class=&n&&post&/span&&span class=&o&&(&/span&&span class=&n&&POST_URL&/span&&span class=&o&&)&/span&
&span class=&o&&.&/span&&span class=&na&&contentType&/span&&span class=&o&&(&/span&&span class=&n&&MediaType&/span&&span class=&o&&.&/span&&span class=&na&&APPLICATION_JSON&/span&&span class=&o&&)&/span&
&span class=&o&&.&/span&&span class=&na&&content&/span&&span class=&o&&(&/span&&span class=&n&&objectMapper&/span&&span class=&o&&.&/span&&span class=&na&&writeValueAsString&/span&&span class=&o&&(&/span&&span class=&n&&postCreateDTO&/span&&span class=&o&&)));&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&p&checkCreateValidPostResult:&/p&&div class=&highlight&&&pre&&code class=&language-java&&&span&&/span&
&span class=&kd&&private&/span& &span class=&kt&&void&/span& &span class=&nf&&checkCreateValidPostResult&/span&&span class=&o&&(&/span&&span class=&n&&ResultActions&/span& &span class=&n&&resultActions&/span&&span class=&o&&,&/span& &span class=&n&&String&/span& &span class=&n&&expectedContent&/span&&span class=&o&&)&/span& &span class=&kd&&throws&/span& &span class=&n&&Exception&/span& &span class=&o&&{&/span&
&span class=&n&&resultActions&/span&&span class=&o&&.&/span&&span class=&na&&andExpect&/span&&span class=&o&&(&/span&&span class=&n&&status&/span&&span class=&o&&().&/span&&span class=&na&&isCreated&/span&&span class=&o&&());&/span&
&span class=&n&&Post&/span& &span class=&n&&postFromRsp&/span& &span class=&o&&=&/span& &span class=&n&&transferResponse2PostEntity&/span&&span class=&o&&(&/span&&span class=&n&&resultActions&/span&&span class=&o&&);&/span&
&span class=&n&&Post&/span& &span class=&n&&postFromDB&/span& &span class=&o&&=&/span& &span class=&n&&postRepository&/span&&span class=&o&&.&/span&&span class=&na&&findOne&/span&&span class=&o&&(&/span&&span class=&n&&postFromRsp&/span&&span class=&o&&.&/span&&span class=&na&&getId&/span&&span class=&o&&());&/span&
&span class=&n&&assertNotNull&/span&&span class=&o&&(&/span&&span class=&n&&postFromDB&/span&&span class=&o&&);&/span&
&span class=&n&&assertEquals&/span&&span class=&o&&(&/span&&span class=&n&&expectedContent&/span&&span class=&o&&,&/span& &span class=&n&&postFromDB&/span&&span class=&o&&.&/span&&span class=&na&&getContent&/span&&span class=&o&&());&/span&
&span class=&o&&}&/span&
&span class=&kd&&private&/span& &span class=&n&&Post&/span& &span class=&nf&&transferResponse2PostEntity&/span&&span class=&o&&(&/span&&span class=&n&&ResultActions&/span& &span class=&n&&resultActions&/span&&span class=&o&&)&/span& &span class=&kd&&throws&/span& &span class=&n&&java&/span&&span class=&o&&.&/span&&span class=&na&&io&/span&&span class=&o&&.&/span&&span class=&na&&IOException&/span& &span class=&o&&{&/span&
&span class=&n&&String&/span& &span class=&n&&response&/span& &span class=&o&&=&/span& &span class=&n&&resultActions&/span&&span class=&o&&.&/span&&span class=&na&&andReturn&/span&&span class=&o&&().&/span&&span class=&na&&getResponse&/span&&span class=&o&&().&/span&&span class=&na&&getContentAsString&/span&&span class=&o&&();&/span&
&span class=&k&&return&/span& &span class=&n&&objectMapper&/span&&span class=&o&&.&/span&&span class=&na&&readValue&/span&&span class=&o&&(&/span&&span class=&n&&response&/span&&span class=&o&&,&/span& &span class=&n&&Post&/span&&span class=&o&&.&/span&&span class=&na&&class&/span&&span class=&o&&);&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&p&写完测试用例,编辑器会用飘红提醒你,你还没创建PostRepository、Post、PostCreateDTO这些类。嗯,别急,这就创建。&br&PostRepository,使用Spring Data,可以轻松写出一个自带增删改查功能的DAO:&/p&&div class=&highlight&&&pre&&code class=&language-java&&&span&&/span&&span class=&kd&&public&/span& &span class=&kd&&interface&/span& &span class=&nc&&PostRepository&/span& &span class=&kd&&extends&/span& &span class=&n&&CrudRepository&/span&&span class=&o&&&&/span&&span class=&n&&Post&/span&&span class=&o&&,&/span& &span class=&n&&Long&/span&&span class=&o&&&&/span& &span class=&o&&{&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&p&Post,其实就是数据库中的存储结构,用Java Entity的形式表示出来:&/p&&div class=&highlight&&&pre&&code class=&language-java&&&span&&/span&&span class=&nd&&@Entity&/span&
&span class=&kd&&public&/span& &span class=&kd&&class&/span& &span class=&nc&&Post&/span& &span class=&o&&{&/span&
&span class=&nd&&@Id&/span&
&span class=&nd&&@GeneratedValue&/span&&span class=&o&&(&/span&&span class=&n&&strategy&/span& &span class=&o&&=&/span& &span class=&n&&GenerationType&/span&&span class=&o&&.&/span&&span class=&na&&AUTO&/span&&span class=&o&&)&/span&
&span class=&kd&&private&/span& &span class=&kt&&long&/span&
&span class=&n&&id&/span&&span class=&o&&;&/span&
&span class=&kd&&private&/span& &span class=&n&&String&/span& &span class=&n&&content&/span&&span class=&o&&;&/span&
&span class=&kd&&private&/span& &span class=&n&&String&/span& &span class=&n&&username&/span&&span class=&o&&;&/span&
&span class=&kd&&private&/span& &span class=&n&&Date&/span& &span class=&n&&createDate&/span&&span class=&o&&;&/span&
&span class=&kd&&public&/span& &span class=&kt&&long&/span&
&span class=&nf&&getId&/span&&span class=&o&&()&/span& &span class=&o&&{&/span&
&span class=&k&&return&/span& &span class=&n&&id&/span&&span class=&o&&;&/span&
&span class=&o&&}&/span&
&span class=&kd&&public&/span& &span class=&kt&&void&/span& &span class=&nf&&setId&/span&&span class=&o&&(&/span&&span class=&kt&&long&/span& &span class=&n&&id&/span&&span class=&o&&)&/span& &span class=&o&&{&/span&
&span class=&k&&this&/span&&span class=&o&&.&/span&&span class=&na&&id&/span& &span class=&o&&=&/span& &span class=&n&&id&/span&&span class=&o&&;&/span&
&span class=&o&&}&/span&
&span class=&kd&&public&/span& &span class=&n&&String&/span& &span class=&nf&&getContent&/span&&span class=&o&&()&/span& &span class=&o&&{&/span&
&span class=&k&&return&/span& &span class=&n&&content&/span&&span class=&o&&;&/span&
&span class=&o&&}&/span&
&span class=&kd&&public&/span& &span class=&kt&&void&/span& &span class=&nf&&setContent&/span&&span class=&o&&(&/span&&span class=&n&&String&/span& &span class=&n&&content&/span&&span class=&o&&)&/span& &span class=&o&&{&/span&
&span class=&k&&this&/span&&span class=&o&&.&/span&&span class=&na&&content&/span& &span class=&o&&=&/span& &span class=&n&&content&/span&&span class=&o&&;&/span&
&span class=&o&&}&/span&
&span class=&kd&&public&/span& &span class=&n&&String&/span& &span class=&nf&&getUsername&/span&&span class=&o&&()&/span& &span class=&o&&{&/span&
&span class=&k&&return&/span& &span class=&n&&username&/span&&span class=&o&&;&/span&
&span class=&o&&}&/span&
&span class=&kd&&public&/span& &span class=&kt&&void&/span& &span class=&nf&&setUsername&/span&&span class=&o&&(&/span&&span class=&n&&String&/span& &span class=&n&&username&/span&&span class=&o&&)&/span& &span class=&o&&{&/span&
&span class=&k&&this&/span&&span class=&o&&.&/span&&span class=&na&&username&/span& &span class=&o&&=&/span& &span class=&n&&username&/span&&span class=&o&&;&/span&
&span class=&o&&}&/span&
&span class=&kd&&public&/span& &span class=&n&&Date&/span& &span class=&nf&&getCreateDate&/span&&span class=&o&&()&/span& &span class=&o&&{&/span&
&span class=&k&&return&/span& &span class=&n&&createDate&/span&&span class=&o&&;&/span&
&span class=&o&&}&/span&
&span class=&kd&&public&/span& &span class=&kt&&void&/span& &span class=&nf&&setCreateDate&/span&&span class=&o&&(&/span&&span class=&n&&Date&/span& &span class=&n&&createDate&/span&&span class=&o&&)&/span& &span class=&o&&{&/span&
&span class=&k&&this&/span&&span class=&o&&.&/span&&span class=&na&&createDate&/span& &span class=&o&&=&/span& &span class=&n&&createDate&/span&&span class=&o&&;&/span&
&span class=&o&&}&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&p&PostCreateDTO,发帖接口的请求体:&/p&&div class=&highlight&&&pre&&code class=&language-java&&&span&&/span&&span class=&kd&&public&/span& &span class=&kd&&class&/span& &span class=&nc&&PostCreateDTO&/span& &span class=&o&&{&/span&
&span class=&kd&&private&/span& &span class=&n&&String&/span& &span class=&n&&content&/span&&span class=&o&&;&/span&
&span class=&kd&&public&/span& &span class=&n&&String&/span& &span class=&nf&&getContent&/span&&span class=&o&&()&/span& &span class=&o&&{&/span&
&span class=&k&&return&/span& &span class=&n&&content&/span&&span class=&o&&;&/span&
&span class=&o&&}&/span&
&span class=&kd&&public&/span& &span class=&kt&&void&/span& &span class=&nf&&setContent&/span&&span class=&o&&(&/span&&span class=&n&&String&/span& &span class=&n&&content&/span&&span class=&o&&)&/span& &span class=&o&&{&/span&
&span class=&k&&this&/span&&span class=&o&&.&/span&&span class=&na&&content&/span& &span class=&o&&=&/span& &span class=&n&&content&/span&&span class=&o&&;&/span&
&span class=&o&&}&/span&
&span class=&kd&&public&/span& &span class=&nf&&PostCreateDTO&/span&&span class=&o&&(&/span&&span class=&n&&String&/span& &span class=&n&&content&/span&&span class=&o&&)&/span& &span class=&o&&{&/span&
&span class=&k&&this&/span&&span class=&o&&.&/span&&span class=&na&&content&/span& &span class=&o&&=&/span& &span class=&n&&content&/span&&span class=&o&&;&/span&
&span class=&o&&}&/span&
&span class=&kd&&public&/span& &span class=&nf&&PostCreateDTO&/span&&span class=&o&&()&/span& &span class=&o&&{&/span&
&span class=&o&&}&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&p&创建完这三个类之后,测试用例可以编译通过了,执行它,由于我们还没有写接口,嗯,测试用例理所当然、意料之中地失败了:&/p&&figure&&img src=&https://pic3.zhimg.com/v2-1bd8e7ea6_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1177& data-rawheight=&254& class=&origin_image zh-lightbox-thumb& width=&1177& data-original=&https://pic3.zhimg.com/v2-1bd8e7ea6_r.jpg&&&/figure&&p&&br&&/p&&p&预期201,实际404,因为我们还没提供接口。&br&那下面自然就是写接口啦,终于可以写产品代码了! PostController,只负责定义接口路径,逻辑全部交给Service:&/p&&div class=&highlight&&&pre&&code class=&language-java&&&span&&/span&&span class=&nd&&@RestController&/span&
&span class=&nd&&@RequestMapping&/span&&span class=&o&&(&/span&&span class=&s&&&/v2.0/posts&&/span&&span class=&o&&)&/span&
&span class=&kd&&public&/span& &span class=&kd&&class&/span& &span class=&nc&&PostControllerV2&/span& &span class=&o&&{&/span&
&span class=&nd&&@Autowired&/span&
&span class=&kd&&private&/span& &span class=&n&&PostService&/span& &span class=&n&&postService&/span&&span class=&o&&;&/span&
&span class=&nd&&@RequestMapping&/span&&span class=&o&&(&/span&&span class=&n&&value&/span&&span class=&o&&=&/span&&span class=&s&&&&&/span&&span class=&o&&,&/span& &span class=&n&&method&/span&&span class=&o&&=&/span& &span class=&n&&RequestMethod&/span&&span class=&o&&.&/span&&span class=&na&&POST&/span&&span class=&o&&)&/span&
&span class=&kd&&public&/span& &span class=&n&&ResponseEntity&/span& &span class=&nf&&createPost&/span&&span class=&o&&(&/span&&span class=&nd&&@RequestBody&/span& &span class=&n&&PostCreateDTO&/span& &span class=&n&&postCreateDTO&/span&&span class=&o&&)&/span& &span class=&o&&{&/span&
&span class=&k&&return&/span& &span class=&n&&postService&/span&&span class=&o&&.&/span&&span class=&na&&createPost&/span&&span class=&o&&(&/span&&span class=&n&&postCreateDTO&/span&&span class=&o&&);&/span&
&span class=&o&&}&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&p&PostService,业务层操作,将PostCreateDTO转成Post,然后调用postRepository,将数据保存到数据库中:&/p&&div class=&highlight&&&pre&&code class=&language-java&&&span&&/span&&span class=&nd&&@Service&/span&
&span class=&kd&&public&/span& &span class=&kd&&class&/span& &span class=&nc&&PostService&/span& &span class=&o&&{&/span&
&span class=&nd&&@Autowired&/span&
&span class=&kd&&private&/span& &span class=&n&&PostRepository&/span& &span class=&n&&postRepository&/span&&span class=&o&&;&/span&
&span class=&nd&&@Autowired&/span&
&span class=&kd&&private&/span& &span class=&n&&UserService&/span& &span class=&n&&userService&/span&&span class=&o&&;&/span&
&span class=&kd&&public&/span& &span class=&n&&ResponseEntity&/span& &span class=&nf&&createPost&/span&&span class=&o&&(&/span&&span class=&n&&PostCreateDTO&/span& &span class=&n&&postCreateDTO&/span&&span class=&o&&)&/span& &span class=&o&&{&/span&
&span class=&n&&Post&/span& &span class=&n&&postCreateResult&/span& &span class=&o&&=&/span& &span class=&n&&savePost2DB&/span&&span class=&o&&(&/span&&span class=&n&&postCreateDTO&/span&&span class=&o&&);&/span&
&span class=&k&&return&/span& &span class=&n&&ResponseEntity&/span&&span class=&o&&.&/span&&span class=&na&&status&/span&&span class=&o&&(&/span&&span class=&n&&HttpStatus&/span&&span class=&o&&.&/span&&span class=&na&&CREATED&/span&&span class=&o&&).&/span&&span class=&na&&body&/span&&span class=&o&&(&/span&&span class=&n&&postCreateResult&/span&&span class=&o&&);&/span&
&span class=&o&&}&/span&
&span class=&kd&&private&/span& &span class=&n&&Post&/span& &span class=&nf&&savePost2DB&/span&&span class=&o&&(&/span&&span class=&n&&PostCreateDTO&/span& &span class=&n&&postCreateDTO&/span&&span class=&o&&)&/span& &span class=&o&&{&/span&
&span class=&n&&Post&/span& &span class=&n&&post&/span& &span class=&o&&=&/span& &span class=&k&&new&/span& &span class=&n&&Post&/span&&span class=&o&&();&/span&
&span class=&n&&post&/span&&span class=&o&&.&/span&&span class=&na&&setCreateDate&/span&&span class=&o&&(&/span&&span class=&k&&new&/span& &span class=&n&&Date&/span&&span class=&o&&());&/span&
&span class=&n&&post&/span&&span class=&o&&.&/span&&span class=&na&&setContent&/span&&span class=&o&&(&/span&&span class=&n&&postCreateDTO&/span&&span class=&o&&.&/span&&span class=&na&&getContent&/span&&span class=&o&&());&/span&
&span class=&n&&post&/span&&span class=&o&&.&/span&&span class=&na&&setUsername&/span&&span class=&o&&(&/span&&span class=&n&&userService&/span&&span class=&o&&.&/span&&span class=&na&&queryCurrentUserName&/span&&span class=&o&&());&/span&
&span class=&k&&return&/span& &span class=&n&&postRepository&/span&&span class=&o&&.&/span&&span class=&na&&save&/span&&span class=&o&&(&/span&&span class=&n&&post&/span&&span class=&o&&);&/span&
&span class=&o&&}&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&p&PostService中用到了另一个Service,UserService,用于获取当前登录用户,当然这里并没有真的去从session中获取用户信息:&/p&&div class=&highlight&&&pre&&code class=&language-java&&&span&&/span&&span class=&nd&&@Service&/span&
&span class=&kd&&public&/span& &span class=&kd&&class&/span& &span class=&nc&&UserService&/span& &span class=&o&&{&/span&
&span class=&kd&&public&/span& &span class=&n&&String&/span& &span class=&nf&&queryCurrentUserName&/span&&span class=&o&&()&/span& &span class=&o&&{&/span&
&span class=&k&&return&/span& &span class=&s&&&sexy code&&/span&&span class=&o&&;&/span&
&span class=&o&&}&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&p&完工,运行下测试用例,通过后,我们继续写下一个集成测试用例——敏感字段校验。&/p&&p&第二个用例依然遵循测试用例“三部曲”,创建环境-&创建带有敏感信息的帖子-&检查响应是不是400、检查数据库中是不是没有数据。这里只贴上新增的代码。 &br&PostControllerV2ITTest:&/p&&div class=&highlight&&&pre&&code class=&language-java&&&span&&/span&
&span class=&kd&&public&/span& &span class=&kd&&static&/span& &span class=&kd&&final&/span& &span class=&n&&String&/span& &span class=&n&&POST_CONTENT_SENSITIVE&/span& &span class=&o&&=&/span& &span class=&s&&&post content test fuck&&/span&&span class=&o&&;&/span&
&span class=&o&&...&/span&
&span class=&nd&&@Test&/span&
&span class=&kd&&public&/span& &span class=&kt&&void&/span& &span class=&nf&&testCreatePost_withSensitiveInfo_returnBadRequest&/span&&span class=&o&&()&/span& &span class=&kd&&throws&/span& &span class=&n&&Exception&/span& &span class=&o&&{&/span&
&span class=&n&&ResultActions&/span& &span class=&n&&resultActions&/span& &span class=&o&&=&/span& &span class=&n&&sendCreatePostRequest&/span&&span class=&o&&(&/span&&span class=&n&&POST_CONTENT_SENSITIVE&/span&&span class=&o&&);&/span&
&span class=&n&&checkCreateSensitivePostResult&/span&&span class=&o&&(&/span&&span class=&n&&resultActions&/span&&span class=&o&&);&/span&
&span class=&o&&}&/span&
&span class=&o&&...&/span&
&span class=&kd&&private&/span& &span class=&kt&&void&/span& &span class=&nf&&checkCreateSensitivePostResult&/span&&span class=&o&&(&/span&&span class=&n&&ResultActions&/span& &span class=&n&&resultActions&/span&&span class=&o&&)&/span& &span class=&kd&&throws&/span& &span class=&n&&Exception&/span& &span class=&o&&{&/span&
&span class=&n&&resultActions&/span&&span class=&o&&.&/span&&span class=&na&&andExpect&/span&&span class=&o&&(&/span&&span class=&n&&status&/span&&span class=&o&&().&/span&&span class=&na&&isBadRequest&/span&&span class=&o&&());&/span&
&span class=&kt&&long&/span& &span class=&n&&count&/span& &span class=&o&&=&/span& &span class=&n&&postRepository&/span&&span class=&o&&.&/span&&span class=&na&&count&/span&&span class=&o&&();&/span&
&span class=&n&&assertEquals&/span&&span class=&o&&(&/span&&span class=&mi&&0&/span&&span class=&o&&,&/span& &span class=&n&&count&/span&&span class=&o&&);&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&p&运行新的测试用例,自然又是理所当然的失败。继续写产品代码。由于我们遵循良好的分层结构,Controller不需要做任何修改,只需给PostService加上判断敏感字段的逻辑即可,PostService:&/p&&div class=&highlight&&&pre&&code class=&language-java&&&span&&/span&
&span class=&o&&...&/span&
&span class=&kd&&public&/span& &span class=&n&&ResponseEntity&/span& &span class=&nf&&createPost&/span&&span class=&o&&(&/span&&span class=&n&&PostCreateDTO&/span& &span class=&n&&postCreateDTO&/span&&span class=&o&&)&/span& &span class=&o&&{&/span&
&span class=&k&&if&/span&&span class=&o&&(&/span&&span class=&n&&isPostContainsSensitiveInfo&/span&&span class=&o&&(&/span&&span class=&n&&postCreateDTO&/span&&span class=&o&&.&/span&&span class=&na&&getContent&/span&&span class=&o&&()))&/span& &span class=&o&&{&/span&
&span class=&k&&return&/span& &span class=&n&&ResponseEntity&/span&&span class=&o&&.&/span&&span class=&na&&status&/span&&span class=&o&&(&/span&&span class=&n&&HttpStatus&/span&&span class=&o&&.&/span&&span class=&na&&BAD_REQUEST&/span&&span class=&o&&).&/span&&span class=&na&&body&/span&&span class=&o&&(&/span&&span class=&k&&new&/span& &span class=&n&&ErrorInfo&/span&&span class=&o&&(&/span&&span class=&n&&SENSITIVE_INFO_ERROR_CODE&/span&&span class=&o&&,&/span& &span class=&n&&POST_CONTAINS_SENSITIVE_INFO&/span&&span class=&o&&));&/span&
&span class=&o&&}&/span&
&span class=&n&&Post&/span& &span class=&n&&postCreateResult&/span& &span class=&o&&=&/span& &span class=&n&&savePost2DB&/span&&span class=&o&&(&/span&&span class=&n&&postCreateDTO&/span&&span class=&o&&);&/span&
&span class=&k&&return&/span& &span class=&n&&ResponseEntity&/span&&span class=&o&&.&/span&&span class=&na&&status&/span&&span class=&o&&(&/span&&span class=&n&&HttpStatus&/span&&span class=&o&&.&/span&&span class=&na&&CREATED&/span&&span class=&o&&).&/span&&span class=&na&&body&/span&&span class=&o&&(&/span&&span class=&n&&postCreateResult&/span&&span class=&o&&);&/span&
&span class=&o&&}&/span&
&span class=&kd&&private&/span& &span class=&kt&&boolean&/span& &span class=&nf&&isPostContainsSensitiveInfo&/span&&span class=&o&&(&/span&&span class=&n&&String&/span& &span class=&n&&content&/span&&span class=&o&&)&/span& &span class=&o&&{&/span&
&span class=&c1&&// TODO: change to throw exception and use global exception handler to return response&/span&
&span class=&k&&if&/span&&span class=&o&&(&/span&&span class=&n&&content&/span&&span class=&o&&.&/span&&span class=&na&&contains&/span&&span class=&o&&(&/span&&span class=&s&&&shit&&/span&&span class=&o&&)&/span& &span class=&o&&||&/span& &span class=&n&&content&/span&&span class=&o&&.&/span&&span class=&na&&contains&/span&&span class=&o&&(&/span&&span class=&s&&&fuck&&/span&&span class=&o&&))&/span& &span class=&o&&{&/span&
&span class=&k&&return&/span& &span class=&kc&&true&/span&&span class=&o&&;&/span&
&span class=&o&&}&/span&
&span class=&k&&return&/span& &span class=&kc&&false&/span&&span class=&o&&;&/span&
&span class=&o&&}&/span&
&span class=&o&&...&/span&
&/code&&/pre&&/div&&p&这里的isPostContainsSensitiveInfo就是我们用来判断敏感字段的方法,我们将整个判断逻辑抽取出来,方便后面的单元测试。&br&值得注意的是,这个方法更好的做法是在判断为含有敏感信息时,抛出异常,而不是返回true这种标志(参见《Effective Java》第九章异常中提出的原则),不过由于我还没给整个Spring Boot项目加上全局异常处理器,因此这里暂时先使用返回boolean的方式来处理,后面会写一篇文章来分享如何在Spring Boot中把异常转换为http状态码。&/p&&p&写完产品代码,再来运行测试用例,通过。&/p&&h2&单元测试&/h2&&p&现在我们的代码已经可以满足上面两个集成测试,可以说基础场景的功能我们已经实现了。但是我们的&b&测试覆盖率&/b&并不全。&br&举个简单的例子,”shit”和”fxxk”都是敏感信息,但是上面我们只测试了”fxxk”的场景,可是专门给”shit”这个场景写一个集成测试又未免太过兴师动众,这时候我们就可以使用单元测试,来对功能进行&b&更细致&/b&并且&b&更快速&/b&的测试。由于isPostContainsSensitiveInfo是private方法,因此我们在测试时用到了反射。 &br&PostServiceUnitTest:&/p&&div class=&highlight&&&pre&&code class=&language-java&&&span&&/span&&span class=&kd&&public&/span& &span class=&kd&&class&/span& &span class=&nc&&PostServiceUnitTest&/span& &span class=&o&&{&/span&
&span class=&nd&&@Test&/span&
&span class=&kd&&public&/span& &span class=&kt&&void&/span& &span class=&nf&&testMethod_IsPostContainsSensitiveInfo&/span&&span class=&o&&()&/span& &span class=&kd&&throws&/span& &span class=&n&&NoSuchMethodException&/span&&span class=&o&&,&/span& &span class=&n&&InvocationTargetException&/span&&span class=&o&&,&/span& &span class=&n&&IllegalAccessException&/span& &span class=&o&&{&/span&
&span class=&n&&Class&/span&&span class=&o&&&&/span&&span class=&n&&PostService&/span&&span class=&o&&&&/span& &span class=&n&&postServiceClass&/span& &span class=&o&&=&/span& &span class=&n&&PostService&/span&&span class=&o&&.&/span&&span class=&na&&class&/span&&span class=&o&&;&/span&
&span class=&n&&Method&/span& &span class=&n&&method&/span& &span class=&o&&=&/span& &span class=&n&&postServiceClass&/span&&span class=&o&&.&/span&&span class=&na&&getDeclaredMethod&/span&&span class=&o&&(&/span&&span class=&s&&&isPostContainsSensitiveInfo&&/span&&span class=&o&&,&/span& &span class=&n&&String&/span&&span class=&o&&.&/span&&span class=&na&&class&/span&&span class=&o&&);&/span&
&span class=&n&&method&/span&&span class=&o&&.&/span&&span class=&na&&setAccessible&/span&&span class=&o&&(&/span&&span class=&kc&&true&/span&&span class=&o&&);&/span&
&span class=&n&&PostService&/span& &span class=&n&&postService&/span& &span class=&o&&=&/span& &span class=&k&&new&/span& &span class=&n&&PostService&/span&&span class=&o&&();&/span&
&span class=&n&&checkWithContent&/span&&span class=&o&&(&/span&&span class=&n&&method&/span&&span class=&o&&,&/span& &span class=&n&&postService&/span&&span class=&o&&,&/span& &span class=&s&&&hi and fuck&&/span&&span class=&o&&,&/span& &span class=&kc&&true&/span&&span class=&o&&);&/span&
&span class=&n&&checkWithContent&/span&&span class=&o&&(&/span&&span class=&n&&method&/span&&span class=&o&&,&/span& &span class=&n&&postService&/span&&span class=&o&&,&/span& &span class=&s&&&hello world&&/span&&span class=&o&&,&/span& &span class=&kc&&false&/span&&span class=&o&&);&/span&
&span class=&n&&checkWithContent&/span&&span class=&o&&(&/span&&span class=&n&&method&/span&&span class=&o&&,&/span& &span class=&n&&postService&/span&&span class=&o&&,&/span& &span class=&s&&&hello shit&&/span&&span class=&o&&,&/span& &span class=&kc&&true&/span&&span class=&o&&);&/span&
&span class=&o&&}&/span&
&span class=&kd&&private&/span& &span class=&kt&&void&/span& &span class=&nf&&checkWithContent&/span&&span class=&o&&(&/span&&span class=&n&&Method&/span& &span class=&n&&method&/span&&span class=&o&&,&/span& &span class=&n&&PostService&/span& &span class=&n&&postService&/span&&span class=&o&&,&/span& &span class=&n&&String&/span& &span class=&n&&content&/span&&span class=&o&&,&/span& &span class=&kt&&boolean&/span& &span class=&n&&expected&/span&&span class=&o&&)&/span& &span class=&kd&&throws&/span& &span class=&n&&IllegalAccessException&/span&&span class=&o&&,&/span& &span class=&n&&InvocationTargetException&/span& &span class=&o&&{&/span&
&span class=&kt&&boolean&/span& &span class=&n&&isSensitive&/span& &span class=&o&&=&/span& &span class=&o&&(&/span&&span class=&n&&Boolean&/span&&span class=&o&&)&/span&&span class=&n&&method&/span&&span class=&o&&.&/span&&span class=&na&&invoke&/span&&span class=&o&&(&/span&&span class=&n&&postService&/span&&span class=&o&&,&/span& &span class=&n&&content&/span&&span class=&o&&);&/span&
&span class=&n&&assertEquals&/span&&span class=&o&&(&/span&&span class=&n&&expected&/span&&span class=&o&&,&/span& &span class=&n&&isSensitive&/span&&span class=&o&&);&/span&
&span class=&o&&}&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&p&显然,这是一个非常简单的Junit,不需要启用Spring Boot容器,运行起来自然也是相当迅速,在我的机器上,执行一次集成测试要花费&b&15秒&/b&,其中绝大多数时间都是花在初始化容器上,而执行一个单元测试只需要&b&1秒&/b&。&/p&&h2&防止测试用例之间相互影响&/h2&&p&写测试用例有一个原则,那就是各个用例之间不能够相互影响,而我在testCreatePost_returnSuccess用例中给数据库插入了数据,却没有在testCreatePost_withSensitiveInfo_returnBadRequest用例开始之前对数据库进行清空,这样testCreatePost_returnSuccess用例中插入的数据就会带到下一个用例中去,更不幸的是,我们在testCreatePost_withSensitiveInfo_returnBadRequest用例中还加入了如下数据库count的校验:&/p&&div class=&highlight&&&pre&&code class=&language-java&&&span&&/span& &span class=&o&&...&/span&
&span class=&kt&&long&/span& &span class=&n&&count&/span& &span class=&o&&=&/span& &span class=&n&&postRepository&/span&&span class=&o&&.&/span&&span class=&na&&count&/span&&span class=&o&&();&/span&
&span class=&n&&assertEquals&/span&&span class=&o&&(&/span&&span class=&mi&&0&/span&&span class=&o&&,&/span& &span class=&n&&count&/span&&span class=&o&&);&/span&
&span class=&o&&...&/span&
&/code&&/pre&&/div&&p&因此,只要testCreatePost_returnSucces用例在testCreatePost_withSensitiveInfo_returnBadRequest之前执行,那么testCreatePost_withSensitiveInfo_returnBadRequest就会失败。&br&我们来验证一下,为了实现上面所讲的测试用例的执行顺序,我给PostControllerV2ITTest加入了@FixMethodOrder(MethodSorters.NAME_ASCENDING)注解:&/p&&div class=&highlight&&&pre&&code class=&language-java&&&span&&/span&&span class=&nd&&@FixMethodOrder&/span&&span class=&o&&(&/span&&span class=&n&&MethodSorters&/span&&span class=&o&&.&/span&&span class=&na&&NAME_ASCENDING&/span&&span class=&o&&)&/span&
&span class=&kd&&public&/span& &span class=&kd&&class&/span& &span class=&nc&&PostControllerV2ITTest&/span&
&/code&&/pre&&/div&&p&执行测试用例,果然,testCreatePost_withSensitiveInfo_returnBadRequest失败了:&/p&&figure&&img src=&https://pic4.zhimg.com/v2-d8d3bed36787_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1231& data-rawheight=&290& class=&origin_image zh-lightbox-thumb& width=&1231& data-original=&https://pic4.zhimg.com/v2-d8d3bed36787_r.jpg&&&/figure&&p&预期0,结果1,因为我们在用例开始前没有清空数据库,导致用例之间相互影响。要解决这个问题,很简单,只需要写个@Before注解的函数,并在函数中清空表中的数据:&/p&&div class=&highlight&&&pre&&code class=&language-java&&&span&&/span&
&span class=&nd&&@Before&/span&
&span class=&kd&&public&/span& &span class=&kt&&void&/span& &span class=&nf&&setup&/span&&span class=&o&&()&/span& &span class=&o&&{&/span&
&span class=&n&&postRepository&/span&&span class=&o&&.&/span&&span class=&na&&deleteAll&/span&&span class=&o&&();&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&p&@Before是Junit提供的注解,每个测试用例在执行前,都会执行被@Before注解的函数。&/p&&h2&更多&/h2&&p&这篇文章只是举了一个我认为的,足够简单,却又足够说明问题的例子,在实际开发中,自然会遇到更多的场景,比如:&/p&&ol&&li&你们项目加入了鉴权,每个请求过来都会被拦截,导致你在测试用例中发出的请求都会返回401,怎么办?你可以使用&a href=&http://link.zhihu.com/?target=https%3A//stackoverflow.com/questions//are-springs-mockmvc-used-for-unit-testing-or-integration-testing& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&standalonesetup&/a&的方式,只加载你需要的Bean,这样就不会引入鉴权框架;你也可以使用Mock,把鉴权的函数Mock掉;当然你也可以Mock其他的函数,反正只要制造你已经登录的假象就好了;或许你还有其他奇技淫巧…&/li&&li&你不想每次都把请求返回的结果转成Java Bean,然后一个个字段去校验,你希望直接校验json字符串?没问题,Spring Boot支持你这样做: &a href=&http://link.zhihu.com/?target=https%3A//docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html%23boot-features-testing-spring-boot-applications-testing-autoconfigured-json-tests& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Auto-configured JSON tests&/a&&/li&&li&你写了一个很复杂的Dao操作,想要对它进行单元测试?这也没问题: &a href=&http://link.zhihu.com/?target=https%3A//docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html%23boot-features-testing-spring-boot-applications-testing-autoconfigured-jpa-test& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Auto-configured Data JPA tests&/a&&/li&&/ol&&p&Spring Boot为我们写好测试用例、用好TDD提供了非常方便的框架,我们只需尽情去写测试用例,尽情去TDD就好了。&/p&&h2&再谈TDD&/h2&&p&这篇文章虽然是在谈如何在Spring Boot中使用TDD写高质量的接口,但是从这样一个例子中,我们也看到了TDD的很多好处:&/p&&ol&&li&让你开发时充满&b&成就感&/b&:你写代码就是为了让原本fail的测试用例通过,这让你写代码时更加具有目标,同时也让你的代码好坏具有可以&b&量化&/b&的指标。&/li&&li&促进&b&整洁的代码&/b&:正如之前提到的,TDD让我们从测试用例的角度出发,思考如何写出方便测试的代码,方便测试的代码,往往是符合&b&单一职责&/b&的。&/li&&li&&b&提高开发的效率&/b&:我身边很多不写测试用例的同事,每次一修改代码,就把代码编译成class文件放到环境上,然后重启、测试,这对于小项目来说尚可接受,但是对于一个大的项目,重启往往需要花费很多时间,而且在我接触到的一个Docker容器化的项目中,还不支持用单个class文件替换的方式去打补丁,每次替换都需要替换整个服务的代码,嗯,然后每次替换、验证、发现新Bug,再修改、替换、验证… 这样开发的效率自然不高。但是如果你已经在本地环境上写了充分的测试用例,那么代码一把布上去,一把验证通过,也就是家常便饭了的事了。&/li&&li&提高了测试用例的&b&代码覆盖率&/b&:这几乎无需解释,先写测试用例,再写产品代码,和先写产品代码,后来由于某种政治任务的压迫,再来补测试用例,前者写出来的测试用例质量一定更高,测试的覆盖率也一定更大。而代码覆盖率的提高,将带给我们下面两个个超级好处:&/li&&/ol&&ul&&li&&b&方便重构&/b&:有多少次你看到一份写的很烂的代码,却又不敢重构,生怕把原有的功能搞坏?有了高覆盖率的测试用例,你就不再担心这个了,重构后,只需要跑一遍用例,就知道你的重构有没有影响原先的功能。&/li&&li&&b&测试即文档&/b&:测试用例是最好的文档,文档会撒谎、注释会撒谎,但是代码不会。&/li&&/ul&&p&&br&&/p&&p&写完这篇文章,结合之前那篇《&a href=&http://link.zhihu.com/?target=http%3A//bridgeforyou.cn//How-to-Persuade-Your-Teemmate-to-use-TDD/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&如何说服你的同事使用TDD&/a&》,嗯,这下我真的非常有信心,可以说服你们使用TDD,说服你们去说服你们同事,使用TDD了。&/p&&h2&参考&/h2&&ul&&li&&a href=&http://link.zhihu.com/?target=https%3A//docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Spring Boot Testing&/a&&/li&&li&&a href=&http://link.zhihu.com/?target=https%3A//github.com/spring-guides/gs-testing-web.git& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&spring-guides/gs-testing-web&/a&&/li&&li&&a href=&http://link.zhihu.com/?target=https%3A//stackoverflow.com/questions/34571/how-do-i-test-a-private-function-or-a-class-that-has-private-methods-fields-or& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&How do I test a private function or a class that has private methods, fields or inner classes?&/a&&/li&&li&&a href=&http://link.zhihu.com/?target=https%3A//github.com/junit-team/junit4/wiki/test-execution-order& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&junit-team/junit4 - test-execution-order&/a&&/li&&li&&a href=&http://link.zhihu.com/?target=https%3A//stackoverflow.com/questions//are-springs-mockmvc-used-for-unit-testing-or-integration-testing& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Are Spring’s MockMvc used for unit testing or integration testing?&/a&&/li&&li&&a href=&http://link.zhihu.com/?target=https%3A//dzone.com/articles/unit-and-integration-tests-in-spring-boot& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Unit and Integration Tests in Spring Boot - DZone Integration&/a&&/li&&li&&a href=&http://link.zhihu.com/?target=https%3A//stackoverflow.com/questions/2457239/injecting-mockito-mocks-into-a-spring-bean& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Injecting Mockito mocks into a Spring bean&/a&&/li&&li&《Effective Java》&/li&&li&《程序员的职业素养》&/li&&li&《代码整洁之道》&/li&&li&《重构》&/li&&/ul&
之前在《》中介绍了为什么要使用TDD(测试驱动开发),以及如何使用TDD写代码。文章发表后,有同学在评论区中表示文章写得不错,但是举得例子太过脱离实际了,能不能举一个在实际工作中的例子呀。这篇文章,就来分享一下在Spring Bo…
&figure&&img src=&https://pic1.zhimg.com/v2-02aefeac79611_b.jpg& data-rawwidth=&1890& data-rawheight=&1417& class=&origin_image zh-lightbox-thumb& width=&1890& data-original=&https://pic1.zhimg.com/v2-02aefeac79611_r.jpg&&&/figure&&h2&1. 外化配置和自动配置&/h2&&p&Spring Boot 配置,包括自动配置和外化配置。&/p&&p&比如常见的,将属性外化配置在 application.properties 应用配置文件,然后在工程中获取该属性值。Spring Boot 可以将配置外部化,这种模式叫做 “外化配置”。将配置从代码中分离外置,最明显的作用是只要简单地修改下外化配置文件,就可以在不同环境中,可以运行相同的应用代码。具体见《Spring Boot 配置文件 – 在坑中实践》:&br&&a href=&http://link.zhihu.com/?target=http%3A//www.spring4all.com/article/267& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://www.&/span&&span class=&visible&&spring4all.com/article/&/span&&span class=&invisible&&267&/span&&span class=&ellipsis&&&/span&&/a&&/p&&p&那自动配置呢&/p&&p&Spring Boot &code&spring-boot-autoconfigure&/code& 依赖做了很多默认的配置项,即应用默认值。这种模式叫做 “自动配置”。Spring Boot 自动配置会根据添加的依赖,自动加载依赖相关的配置属性并启动依赖。例如默认用的内嵌式容器是 Tomcat ,端口默认设置为 8080。&/p&&p&为什么需要自动配置?顾名思义,自动配置的意义是利用这种模式代替了配置 XML 繁琐模式。以前使用 Spring MVC ,需要进行配置组件扫描、调度器、视图解析器等,使用 Spring Boot 自动配置后,只需要添加 MVC 组件即可自动配置所需要的 Bean。所有自动配置的实现都在 &code&spring-boot-autoconfigure&/code& 依赖中,包括 Spring MVC 、Data 和其它框架的自动配置。&/p&&p&经过和 DD 讨论,DD 原话:&br&1. External Configuration指的不是把配置内容分离到properties文件里,而是配置存储在classpath之外,比如spring cloud config的服务器中&br&2. 自动化配置本身包含了两块内容:@Configuration的定义和properties属性的定义,外部化配置是跟加载过程相关的。&/p&&p&感谢DD~&/p&&h2&2. 自动配置原理浅析&/h2&&h2&spring-boot-autoconfigure 依赖&/h2&&p&&code&spring-boot-autoconfigure&/code& 依赖,是 Spring Boot 实现自动配置的核心 Starter 组件。如图是它的依赖包目录:&/p&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-2959bac59bc8cd19d2f9560ccb6db678_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&559& data-rawheight=&1067& class=&origin_image zh-lightbox-thumb& width=&559& data-original=&https://pic1.zhimg.com/v2-2959bac59bc8cd19d2f9560ccb6db678_r.jpg&&&/figure&&p&可以看出很多常用框架的自动配置包目录:&code&org.springframework.boot.autoconfigure.thymeleaf&/code&、&code&org.springframework.boot.autoconfigure.data.jpa&/code&等&/p&&p&那进一步打开包下对应的自动配置类,可以看到有:Jpa 自动配置类 &code&JpaRepositoriesAutoConfiguration&/code&、Thymeleaf 自动配置类 &code&ThymeleafAutoConfiguration&/code& 等&/p&&p&&code&spring-boot-autoconfigure&/code& 依赖的工作原理很简单,通过 &code&@EnableAutoConfiguration&/code& 核心注解初始化,并扫描 ClassPath 目录中自动配置类对应依赖。比如工程中有木有添加 Thymeleaf 的 Starter 组件依赖。如果有,就按按一定规则获取默认配置并自动初始化所需要的 Bean。&/p&&p&那具体 &code&@EnableAutoConfiguration&/code& 核心注解的工作原理是咋样的呢?&/p&&h2&@EnableAutoConfiguration 注解&/h2&&p&&code&@EnableAutoConfiguration&/code& 注解源码如下:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = &spring.boot.enableautoconfiguration&;
Class&?&[] exclude() default {};
String[] excludeName() default {};
&/code&&/pre&&/div&&p&&code&@EnableAutoConfiguration&/code& 注解核心点是 &code&@Import&/code& 的自动配置导入选择器类 &code&AutoConfigurationImportSelector&/code& 。其代码部分如下:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
protected List&String& getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List&String& configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
Assert.notEmpty(configurations, &No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.&);
&/code&&/pre&&/div&&p&总结可得具体流程如下:&/p&&ul&&li&&code&AutoConfigurationImportSelector&/code& 通过 &code&SpringFactoriesLoader.loadFactoryNames()&/code& 核心方法读取 ClassPath 目录下面的 META-INF/spring.factories 文件。&/li&&li&spring.factories 文件中配置的 Spring Boot 自动配置类,例如常见的Jpa 自动配置类 &code&JpaRepositoriesAutoConfiguration&/code&、Thymeleaf 自动配置类 &code&ThymeleafAutoConfiguration&/code& 、 &code&WebMvcAutoConfiguration&/code& Web MVC 自动配置类和&code&ServletWebServerFactoryAutoConfiguration&/code& 容器自动配置类等 。&/li&&li&spring.factories 文件和 application.properties 文件都属于配置文件,配置的格式均为键值对。里面配置的每个自动配置类都会定义相关 Bean 的实例配置,也会定义什么条件下自动配置和哪些 Bean 被实例化。&/li&&li&当 pom.xml 添加某 Starter 依赖组件的时候,就会自动触发该依赖的默认配置。&/li&&/ul&&p&具体 Starter 组件依赖是如何触发它的默认配置的呢?&/p&&h2&3. Starter 组件浅析&/h2&&h2&Starter 组件&/h2&&p&Spring Boot 提供了很多 “开箱即用” 的 Starter 组件。Starter 组件是可被加载在应用中的 Maven 依赖项。只需要在 Maven 配置中添加对应的依赖配置,即可使用对应的 Starter 组件。例如,添加 &code&spring-boot-starter-web&/code& 依赖,就可用于构建 REST API 服务,其包含了 Spring MVC 和 Tomcat 内嵌容器等。&/p&&p&一个完整的 Starter 组件包括以下两点:&/p&&ul&&li&提供自动配置功能的自动配置模块。&/li&&li&提供依赖关系管理功能的组件模块,即封装了组件所有功能,开箱即用。&/li&&/ul&&h2&spring-boot-starter-web 依赖源码&/h2&&p&当我们添加 &code&spring-boot-starter-web&/code& 依赖,并启动应用会触发容器自动配置类。容器自动配置类 &code&ServletWebServerFactoryAutoConfiguration&/code& 的部分代码如下:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&package org.springframework.boot.autoconfigure.web.
@Configuration
@ConditionalOnClass({ServletRequest.class})
@ConditionalOnWebApplication(
type = Type.SERVLET
@EnableConfigurationProperties({ServerProperties.class})
@Import({ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class})
public class ServletWebServerFactoryAutoConfiguration {
&/code&&/pre&&/div&&p&上面代码源码浅析下:&/p&&ul&&li&&code&@ConditionalOnClass&/code& 注解表示对应的 &code&ServletRequest&/code& 类在 ClassPath 目录下面存在,并且 &code&@ConditionalOnWebApplication&/code& 注解表示该应用是 Servlet Web 应用时,才会去启动容器默认配置&/li&&li&通过 ServerProperties 类默认设置了端口为 8080&/li&&li&Type.SERVLET 枚举代表 Servlet Web 应用,Type.REACTIVE 枚举代表响应式 WebFlux 应用。&/li&&/ul&&p&&code&@ConditionalOnClass&/code& 注解类似功能的还有 &code&@ConditionalOnMissingBean&/code& 、&code&@ConditionalOnProperty&/code&等注解。这里不一一列出解释。&/p&&h2&4. 小结&/h2&&p&自动配置,是一把双刃剑。用好了就像,天下武功唯快不破一样;用不好就需要注意一些自动化配置造成的问题。常见的问题常见有:&/p&&ul&&li&Spring Boot 工程添加某些 Starter 组件依赖,但不想触发组件自动配置&/li&&li&Spring Boot 配置多个不同数据源配置时,比如使用 XML 配置多数据源,但其默认数据源配置会触发自动配置出现问题。&/li&&/ul&&p&类似场景下,解决方式是排除不需要的特定自动配置类。通过 exclude 属性指定并排除自动配置类,代码如下:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
&/code&&/pre&&/div&&p&也等价于配置在 &code&@EnableAutoConfiguration&/code& 注解,代码如下:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&@SpringBootApplication
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
&/code&&/pre&&/div&&p&自动配置会最大的智能化,当配置了 exclude 属性时,Spring Boot 优先初始化用户定义的 Bean ,然后在进行自动化配置。&/p&&p&&br&&/p&&blockquote&本文作者:泥瓦匠BYSocket&br&转载地址:&a href=&http://link.zhihu.com/?target=https%3A//www.bysocket.com/%3Fp%3D2001& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Spring Boot 2.0 自动配置原理浅析&/a&&/blockquote&&p&&/p&
1. 外化配置和自动配置Spring Boot 配置,包括自动配置和外化配置。比如常见的,将属性外化配置在 application.properties 应用配置文件,然后在工程中获取该属性值。Spring Boot 可以将配置外部化,这种模式叫做 “外化配置”。将配置从代码中分离外置,最…
&figure&&img src=&https://pic4.zhimg.com/v2-058da8c91_b.jpg& data-rawwidth=&640& data-rawheight=&328& class=&origin_image zh-lightbox-thumb& width=&640& data-original=&https://pic4.zhimg.com/v2-058da8c91_r.jpg&&&/figure&&blockquote&最近,开源社区发生了一件大事,那个全国 Java 开发者使用最广的开源服务框架 Dubbo 低调重启维护,并且 3 个月连续发布了 4 个维护版本。&/blockquote&&p&我上次在写“&a href=&https://link.zhihu.com/?target=http%3A//mp.weixin.qq.com/s%3F__biz%3DMzI4NDY5Mjc1Mg%3D%3D%26mid%3D%26idx%3D1%26sn%3D7e0eebaf3dc42f14d0ea324%26scene%3D21%23wechat_redirect& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&放弃Dubbo,选择最流行的Spring Cloud微服务架构实践与经验总结&/a&”这篇文章的时候,就有很多的网友给我留言说,Dubbo 又开始更新了。&/p&&p&我当然是清楚的,我也一直在关注着 Dubbo 的走向,在几个月前技术圈里面就有一个消息说是 Dubbo 又开始更新了,大家议论纷纷不知真伪。&/p&&p&我还专门跑到 GitHub 上面进行了留言询问,最后在 Dubbo 的 gitter 聊天室里面找到了确信的答案,说是正在组建团队。&/p&&p&虽然稍稍有所期待,但也不知道阿里这次拿出了多少的诚意来做这件事,于是我昨天又到 GitHub 逛了一下,发现从 9 月开始,阿里三个月连着发布了四个版本,还是非常有诚意的,值得关注。&/p&&h2&Dubbo 简介&/h2&&p&Dubbo 是阿里巴巴公司一个开源的高性能服务框架,致力于提供高性能和透明化的 RPC 远程服务调用方案,以及 SOA 服务治理方案,使得应用可通过高性能 RPC 实现服务的输出、输入功能和 Spring 框架无缝集成。&/p&&p&Dubbo 包含远程通讯、集群容错和自动发现三个核心部分。它提供透明化的远程方法调用,实现像调用本地方法一样调用远程方法,只需简单配置,没有任何 API 侵入。&/p&&p&同时它具备软负载均衡及容错机制,可在内网替代 F5 等硬件负载均衡器,降低成本,减少单点。&/p&&p&它可以实现服务自动注册与发现,不再需要写死服务提供方地址,注册中心基于接口名查询服务提供者的 IP 地址,并且能够平滑添加或删除服务提供者。&/p&&p&2011 年末,阿里巴巴在 GitHub 上开源了基于 Java 的分布式服务治理框架 Dubbo,之后它成为了国内该类开源项目的佼佼者,许多开发者对其表示青睐。&/p&&p&同时,先后有不少公司在实践中基于 Dubbo 进行分布式系统架构。目前在 GitHub 上,它的 fork、star 数均已破万。&/p&&p&&b&Dubbo 核心功能:&/b&&/p&&ul&&li&&b&远程通讯,&/b&提供对多种基于长连接的 NIO 框架抽象封装,包括多种线程模型,序列化,以及“请求-响应”模式的信息交换方式。&/li&&li&&b&集群容错,&/b&提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持。&/li&&li&&b&自动发现,&/b&基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器。&/li&&/ul&&figure&&img src=&https://pic4.zhimg.com/v2-ebef615ba37d634cd4a234_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&640& data-rawheight=&399& class=&origin_image zh-lightbox-thumb& width=&640& data-original=&https://pic4.zhimg.com/v2-ebef615ba37d634cd4a234_r.jpg&&&/figure&&h2&Dubbo 发展史&/h2&&p&&b&发展到开源&/b&&/p&&p&2008 年底在阿里内部开始规划调用,2009 年初开发 1.0 版本;2010 年 04 月在 1.0 的版本之上进行了重构,发布了 2.0 版本;2011 年 10 月阿里宣布将 Dubbo 开源,开源的第一个版本为版本 dubbo-2.0.7。&/p&&p&&b&开源成长&/b&&/p&&p&Dubbo 开源之后,框架发展比较迅速,几乎两三个月会发布一个版本,于 2012 年 3 月 14 号发布版本 dubbo-2.1.0。 &/p&&p&随后又进入另一个快速发展期,版本发布频繁,几乎每一个月会发布好几次。直到 2013 年 3 月 17 号发布了 dubbo-2.4.10,版本陷入停滞;2014 年 10 月 30 号发布版本 dubbo-2.4.11,修复了一个小 Bug,版本又陷入漫长的停滞到现在。&/p&&p&&b&阿里之外的发展&/b&&/p&&p&2014 年的 10 月 20 号,当当网 Fork 了阿里的一个 Dubbo 版本开始维护,并命名为 dubbox-2.8.0。 &/p&&p&值得注意的是,当当网扩展 Dubbo 服务框架支持 REST 风格远程调用,并且跟随着 ZooKeepe 和 Spring 升级了对应的版本。之后 Dubbox 一直在小版本维护,2015 年 3 月 31 号发布了最后一个版本 dubbox-2.8.4。&/p&&figure&&img src=&https://pic3.zhimg.com/v2-7ee7d0eaf8d019c35727_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&720& data-rawheight=&540& class=&origin_image zh-lightbox-thumb& width=&720& data-original=&https://pic3.zhimg.com/v2-7ee7d0eaf8d019c35727_r.jpg&&&/figure&&h2&Dubbo 团队这三个月都做了什么?&/h2&&p&目前 Dubbo 的主力开发以阿里巴巴中间件团队为主,同时在阿里内部也招募了一些对 Dubbo 感兴趣的同事。&/p&&p&大家要知道,Dubbo 距离今年开始维护的上一个版本是什么时间发布的吗?是 2014 年 10 月 30 号,差了整整将近 3 年,Dubbo 所依赖的 Jdk、Spring、Zookeeper、Zkclient 等等不知道都更新了多少个版本。&/p&&p&因此阿里恢复更新的第一步就是适配所依赖的各组件版本,让 Dubbo 所依赖的基础环境不要太落伍,另外也 Fixed 掉了一些严重的 Bug。&/p&&p&&b&dubbo-2.5.4/5 版本&/b&&/p&&p&2017 年 9 月,阿里发布了 dubbo-2.5.4/5 版本,更新内容如下:&/p&&figure&&img src=&https://pic1.zhimg.com/v2-0bb37cc5a7_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&640& data-rawheight=&410& class=&origin_image zh-lightbox-thumb& width=&640& data-original=&https://pic1.zhimg.com/v2-0bb37cc5a7_r.jpg&&&/figure&&p&&i&
依赖升级&/i&&/p&&p&这版在升级相关依赖版本的同时,以问题反馈频率和影响面排定优先级,优先解决了几个反馈最多、影响较大的一些缺陷,包括优雅停机、异步调用、动态配置、MonitorFilter 监控统计等问题。&/p&&p&&b&dubbo-2.5.6 版本&/b&&/p&&p&2017 年 10 月,阿里发布了 dubbo-2.5.6 版本,又 Fixed 掉了一大批严重的 Bug。&/p&&p&发布内容主要包括:&/p&&ul&&li&泛化调用 PojoUtils 工具类不能正确处理枚举类型、私有字段等问题。&/li&&li&provider 业务线程池满后,拒绝请求的异常无法通知到 consumer 端。&/li&&li&业务返回值 payload 超阈值时,payload 被重复发送回 consumer。&/li&&li&slf4jLogger 正确输出 log 调用实际所在行号。&/li&&li&延迟(delay)暴露存在潜在并发问题,导致不同服务占用多个端口。&/li&&li&无 provider 时,consumer 端 mock 逻辑不能生效。&/li&&li&一些小优化:OverrideListener 监听逻辑、provider 端关闭心跳请求、Main 启动类停机逻辑等。&/li&&li&一些小 Bug 修复:动态配置不能删除、telnet 支持泛型 json 调用、monitor 统计错误等。&/li&&/ul&&p&&b&dubbo-2.5.7 版本&/b&&/p&&p&2017 年 11 月,也就是 12 天前,阿里发布了 dubbo-2.5.7。这次不但修复了一批主要的 Bug,还做了一处小功能的完善。&/p&&p&发布内容主要包括:&/p&&ul&&li&完善注解配置方式,修复社区反馈的旧版注解 Bug。&/li&&li&支持启动时从环境变量读取注册 ip port、绑定 ip port,支持社区反馈的容器化部署场景等。&/li&&li&调整、开放一些不完善的 xml 配置项,如 dump.directory 等。&/li&&li&解决启动阶段 ZK 无法连接导致应用无限阻塞的问题。&/li&&li&解决 ZK 无法连接时,MonitorService 初次访问会导致 rpc 请求阻塞问题 #672。&/li&&li&内部 json 实现标记 deprecate,转为依赖开源 fastjson 实现。&/li&&li&RMI 协议支持传递 attachments。&/li&&li&Hessian 支持 EnumSet 类型序列化。&/li&&li&社区反馈的一些小 Bug 修复及优化。&/li&&/ul&&p&这次版本发布内容较多,因此还给出了升级建议:&/p&&ul&&li&此次升级存在以下不兼容或需要注意点,但对核心功能均无影响,且只需添加依赖或遵守配置规则即可避免。这里只是把潜在的注意点列出来,如果你没用到这些功能无需额外关注。&/li&&li&AccesslogFilter、telnet、mock 等部分功能依赖了老版 JSON 实现,如开启以上功能,升级后请添加 fastjson 作为第三方依赖。&/li&&li&此次升级完善了注解配置方式,同时保留了老的注解配置代码,如工程从之前的老版本注解配置转到 2.5.7 版本,请确保删除老的注解扫描配置,使用新的配置形式。&/li&&li&在工程启动阶段,如遇到 ZK 不可达,当前版本的行为是使用注册中心缓存继续启动。具体由 check 参数决定,MonitorService 初次调用,如遇 ZK 不可达,当前版本会忽略 monitor 数据上传,以避免阻塞 rpc 主流程。&/li&&/ul&&p&在 2.5.7 版本更新的同时还给出了下一步的预告,近期即将提供 dubbo-spring-boot-starter 启动配置模块。&/p&&p&这个提示说明了两个事情:&/p&&ul&&li&Dubbo 还会继续完善,后续会开发很多的新的功能,所以希望大家关注。&/li&&li&说明 Spring Boot 的影响力也越来越大,各种牛逼的开源软件纷纷给出了支持,现在也将包括 Dubbo。&/li&&/ul&&h2&Dubbo 下一步会做什么?&/h2&&p&根据阿里技术的信息,最近三个版本会做的事情如下: &/p&&ul&&li&优先解决社区使用过程中的问题和框架的缺陷,吸收社区贡献的新特性,解决文档访问和不全面的问题。&/li&&li&提供服务延迟暴乱、优雅停机 API 接口支持 RESTFULE 风格服务调用,提供 netty http 的支持,集成高性能序列化协议。&/li&&li&路由功能优化、消费端异步功能优化、提供端异步调用支持注册中心推送通知异步、合并处理改造等。&/li&&/ul&&p&&b&未来计划&/b&&/p&&p&重构动态配置模块,动态配置和注册中心分离,集成流行的开源分布式配置管理框架,服务元数据注册与注册中心分离,丰富元数据内容,适配流行的 consul etcd 等注册中心方案。&/p&&p&考虑提供 opentrace、oauth2、metrics、health、gateway 等部分服务化基础组件的支持,服务治理平台 OPS 重做,除代码、UI 重构外,期望能提供更强的服务测试、健康检查、服务动态治理等特性。&/p&&p&Dubbo 模块化,各个模块可单独打包、单独依赖,集群熔断和自动故障检测能力。&/p&&p&继续在 Dubbo 框架现代化、国际化这两个大的方向上进行探索。现代化方面主要是考虑到目前微服务架构以及容器化日渐流行的大趋势,Dubbo 作为 RPC 框架如何很好地融入其中,成为其生态体系中不可或缺的一个组件。&/p&&p&强调的是 Dubbo 未来的定位并不是要成为一个微服务的全面解决方案,而是专注在 RPC 领域,成为微服务生态体系中的一个重要组件。&/p&&p&至于大家关注的微服务化衍生出的服务治理需求, Dubbo 将积极适配开源解决方案,甚至启动独立的开源项目予以支持。&/p&&h2&Dubbo 和 Spring Cloud 有何不同?&/h2&&p&首先做一个简单的功能对比:&/p&&figure&&img src=&https://pic1.zhimg.com/v2-ed428d0d3af734a9b3f444_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&487& data-rawheight=&463& class=&origin_image zh-lightbox-thumb& width=&487& data-original=&https://pic1.zhimg.com/v2-ed428d0d3af734a9b3f444_r.jpg&&&/figure&&p&从上图可以看出 Dubbo 的功能只是 Spring Cloud 体系的一部分。&/p&&p&这样对比是不够公平的,首先 Dubbo 是 SOA 时代的产物,它的关注点主要在于服务的调用,流量分发、流量监控和熔断。&/p&&p&而 Spring Cloud 诞生于微服务架构时代,考虑的是微服务治理的方方面面,另外由于依托了 Spirng、Spirng Boot 的优势之上,两个框架在开始目标就不一致,Dubbo 定位服务治理、Spirng Cloud 是一个生态。&/p&&p&如果仅仅关注于服务治理的这个层面,Dubbo 还优于 Spring Cloud 很多:&/p&&ul&&li&Dubbo 支持更多的协议,如:rmi、hessian、http、webservice、thrift、memcached、redis 等。&/li&&li&Dubbo 使用 RPC 协议效率更高,在极端压力测试下,Dubbo 的效率会高于 Spring Cloud 效率一倍多。&/li&&li&Dubbo 有更强大的后台管理,Dubbo 提供的后台管理 Dubbo Admin 功能强大,提供了路由规则、动态配置、访问控制、权重调节、均衡负载等诸多强大的功能。&/li&&li&可以限制某个 IP 流量的访问权限,设置不同服务器分发不同的流量权重,并且支持多种算法,利用这些功能我们可以在线上做灰度发布、故障转移等,Spring Cloud 到现在还不支持灰度发布、流量权重等功能。&/li&&/ul&&p&下图为 Dubbo Admin 后台截图:&/p&&figure&&img src=&https://pic1.zhimg.com/v2-8cf66fbc49e_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&640& data-rawheight=&248& class=&origin_image zh-lightbox-thumb& width=&640& data-original=&https://pic1.zhimg.com/v2-8cf66fbc49e_r.jpg&&&/figure&&p&所以 Dubbo 专注于服务治理,Spring Cloud 关注于微服务架构生态。&/p&&p&Dubbo 发布对 Spring Cloud 有影响吗?&/p&&p&国内技术人喜欢拿 Dubbo 和 Spring Cloud 进行对比,是因为两者都是服务治理非常优秀的开源框架。&/p&&p&但它们两者的出发点是不一样的,Dubbo 关注于服务治理这块并且以后也会继续往这个方向去发展。&/p&&p&Spring Cloud 关注的是微服务治理的生态。因为微服务治理的方方面面都是它所关注的内容,服务治理也只是微服务生态的一部分而已。&/p&&p&因此可以大胆的断定,Dubbo 未来会在服务治理方面更为出色,而 Spring Cloud 在微服务治理上面无人能敌。&/p&&p&同时根据 Dubbo 最新的更新技术来看,Dubbo 也会积极的拥抱开源,拥抱新技术。&/p&&p&Dubbo 接下来的版本将会很快的支持 Spring Boot,方便我们享受高效开发的同时,也可以支持高效的服务调用。&/p&&p&Dubbo 被广泛应用于中国各互联网公司,如今阿里又重新重视起来并且发布了新版本和一系列的计划,对于正在使用 Dubbo 的公司来说是一个喜讯,对于中国广大的开发者来说更是一件非常喜悦的事情。&/p&&p&我们非常乐于看到中国有一款非常优秀的开源框架,让我们有更多的选择,有更好的支持。&/p&&p&所以说两者其实不一定有竞争关系,如果使用得当甚至可以互补;另外两个关注的领域也不一致,因此对 Spring Cloud 的影响甚微。&/p&&h2&Dubbo 和 Spring Cloud 该如何选择?&/h2&&p&可能很多人正在犹豫,在服务治理的时候应该选择那个框架呢?&/p&&p&如果公司对效率有极高的要求建议使用 Dubbo,相对比 RPC 的效率会比 HTTP 高很多;如果团队不想对技术架构做大的改造建议使用 Dubbo,Dubbo 仅仅需要少量的修改就可以融入到内部系统的架构中。&/p&&p&但如果技术团队喜欢挑战新技术,建议选择 Spring Cloud,Spring Cloud 架构体系有有趣很酷的技术。&/p&&p&如果公司选择微服务架构去重构整个技术体系,那么 Spring Cloud 是当仁不让之选,它可以说是目前最好的微服务框架没有之一。&/p&&p&最后,技术选型是一个综合的问题,需要考虑团队的情况、业务的发展以及公司的产品特征。&/p&&p&最炫最酷的技术并不一定是最好的,选择适合自己团队、符合公司业务的框架才是最佳方案。技术的发展永远没有尽头,因此我们对技术也要保持空杯、保持饥饿、保持敬畏!&/p&&blockquote&原文出处:&a href=&https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s%3F__biz%3DMzI4NDY5Mjc1Mg%3D%3D%26mid%3D%26idx%3D1%26sn%3Da3eccbbbaf8e%26chksm%3Debf6db1fdc6d290f703e9e458e4aa1a9319e0aff60f737cce86855%23rd& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&阿里Dubbo疯狂更新,关Spring Cloud什么事?&/a&&/blockquote&&p&&/p&
最近,开源社区发生了一件大事,那个全国 Java 开发者使用最广的开源服务框架 Dubbo 低调重启维护,并且 3 个月连续发布了 4 个维护版本。我上次在写“”这篇文章的时候,就有很多的网友给我留…
&figure&&img src=&https://pic1.zhimg.com/v2-9d46e62f465a4e3a1b295460bfcf3753_b.jpg& data-rawwidth=&2560& data-rawheight=&1600& class=&origin_image zh-lightbox-thumb& width=&2560& data-original=&https://pic1.zhimg.com/v2-9d46e62f465a4e3a1b295460bfcf3753_r.jpg&&&/figure&&p&老婆经常喜欢翻看我订阅的技术杂志,她总能从她的视角提出很多有趣的问题。&/p&&p&一个悠闲的周日下午,她午觉醒来,又习惯性的抓起这个月的杂志,饶有兴趣地看了起来。&/p&&p&果不其然,看着看着,她又对我发难了,“Restful是什么呀,老公?是restaurant的形容词吗,突然就觉得好饿了啊......”&/p&&p&作为一个合格的程序员,我一直把能够将一项技术讲给老婆听,并且能给她讲懂,作为我已经掌握了这项技术的标准。&/p&&p&如果我直接回答说,“REST就是Representational State Transfer的缩写呀,翻译为中文就是‘表述性状态转移’”,那她今晚肯定得罚我跪键盘。我必须找个合适的机会,把Restful的&b&来龙去脉&/b&给她形象的描述一遍。&/p&&p&“走,咱们去楼下咖啡厅吃个下午茶吧”,我对老婆说。&/p&&p&&br&&/p&&p&“一个芝士蛋糕,一杯拿铁,两条吸管,谢谢”,我对前台的服务员说,然后我们找了个角落坐了下来。&/p&&p&&br&&/p&&h2&Level 0 - 面向前台&/h2&&p&“刚才我们向前台点了一杯拿铁,这个过程可以用这段文字来描述”,说着,我在纸上写下了这段JSON,虽然她不知道什么叫JSON,但理解这段文字对于英语专业8级的她,实在再简单不过。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&{
&addOrder&: {
&orderName&: &latte&
&/code&&/pre&&/div&&p&“我们通过这段文字,告诉前台,新增一笔订单,订单是一杯拿铁咖啡”,接着,前台给我们返回这么一串回复:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&{
&orderId&: &123456&
&/code&&/pre&&/div&&p&“订单ID?还是订单编号?”&/p&&p&“恩恩,就是订单编号”&/p&&p&“那我们就等着前台喊‘订单123456的客户可以取餐了’,然后就可以开吃了!”&/p&&p&“哈哈,你真聪明,不过,在这之前,假设我们有一张会员卡,我们想查询一下这张会员卡的余额,这时候,要向前台发起另一个询问”,我继续在纸上写着:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&{
&queryBalance&: {
&cardId&: &886333&
&/code&&/pre&&/div&&p&“查询卡号为886333的卡的余额?”&/p&&p&“真棒!接着,查询的结果返回来了”&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&{
&balance&: &0&
&/code&&/pre&&/div&&p&“切,没钱......”&/p&&p&“哈哈,没钱,现在我们要跟前台说,这杯咖啡不要了”,我在纸上写到:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&{
&deleteOrder&: {
&orderId&: &123456&
&/code&&/pre&&/div&&p&“哼,这就把订单取消啦?”&/p&&p&&br&&/p&&h2&Level 1 - 面向资源&/h2&&p&“现在这家咖啡店越做越大,来喝咖啡的人越来越多,单靠前台显然是不行的,店主决定进行分工,每个&b&资源&/b&都有专人负责,我们可以直接&b&面向资源&/b&操作。”&/p&&p&&面向资源?”&/p&&p&“是的,比如还是下单,请求的内容不变,但是我们多了一条消息”,我在纸上画出这次的模型:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&/orders
&addOrder&: {
&orderName&: &latte&
&/code&&/pre&&/div&&p&“多了一个斜杠和orders?这是什么意思?”&/p&&p&“这个表示我们这个请求是发给哪个资源的,订单是一种资源,我们可以理解为是咖啡厅专门管理订单的人,他可以帮我们处理所有有关订单的操作,包括新增订单、修改订单、取消订单等操作”&/p&&p&“Soga...”&/p&&p&“接着还是会返回订单的编号给我们”&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&{
&orderId&: &123456&
&/code&&/pre&&/div&&p&“下面,我们还是要查询会员卡余额,这次请求的资源变成了cards”&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&/cards
&queryBalance&: {
&cardId&: &886333&
&/code&&/pre&&/div&&p&“接下来是取消订单”&/p&&p&“这个我会”,说着,她抢走我手上的笔,}

我要回帖

更多关于 藏宝网2u交易平台 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信