欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos

本篇概览

  • 前文《五分钟搞懂spring-cloud-square》详细介绍了什么是spring-cloud-square,以及三种实现类型的详细概念,爱动手的您已迫不及待想编码体验spring-cloud-square了,本篇咱们就来畅快实战,体验这个spring官方带给我们的smart client

  • 如标题所述,接下里咱们会将spring-cloud-square提供的三种client都编码体验,总的来说本篇由以下内容构成:

  1. 新建maven工程,名为spring-cloud-square-tutorials,这是本篇所有应用的父工程,库版本在此工程中统一管理;
  2. 创建子工程eureka,作为注册中心
  3. 创建子工程client,放一些公用的数据结构
  4. 创建子工程provider,身份是服务提供者,接下来的三个用到spring-cloud-square的子工程,都调用provider的服务
  5. 创建子工程consumer-okhttp,基于spring-cloud-square的okhttp能力做远程调用
  6. 创建子工程consumer-retrofit-okhttp,基于spring-cloud-square的retrofit + okhttp能力做远程调用
  7. 创建子工程consumer-retrofit-webflux,基于spring-cloud-square的retrofit + webflux能力做远程调用
  • 上述几个服务的关系如下图:

如何验证

  • 代码写完之后,如何验证功能是否符合预期呢?本篇采用单元测试的方式,consumer-okhttp、consumer-retrofit-okhttp、consumer-retrofit-webflux这三个子工程都有自己的单元测试代码,执行通过就意味着代码功能符合预期了

源码下载

名称 链接 备注
项目主页 https://github.com/zq2599/blog_demos 该项目在GitHub上的主页
git仓库地址(https) https://github.com/zq2599/blog_demos.git 该项目源码的仓库地址,https协议
git仓库地址(ssh) git@github.com:zq2599/blog_demos.git 该项目源码的仓库地址,ssh协议
  • 这个git项目中有多个文件夹,本篇的源码在spring-cloud-square-tutorials文件夹下,如下图红框所示:

版本信息

  • 本篇实战涉及到的主要版本情况如下:
  1. JDK:1.8.0_291
  2. IDEA:2021.1.3 (Ultimate Edition)
  3. maven:3.8.1
  4. 操作系统:win10 64位
  5. springboot:2.4.4
  6. spring-cloud:2020.0.2
  7. spring-cloud-square:0.4.0-SNAPSHOT

父工程spring-cloud-square-tutorials

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <parent>
  7. <groupId>org.springframework.boot</groupId>
  8. <artifactId>spring-boot-starter-parent</artifactId>
  9. <version>2.4.4</version>
  10. <relativePath/> <!-- lookup parent from repository -->
  11. </parent>
  12. <groupId>com.bolingcavalry</groupId>
  13. <artifactId>spring-cloud-square-tutorials</artifactId>
  14. <version>1.0-SNAPSHOT</version>
  15. <properties>
  16. <maven.compiler.source>8</maven.compiler.source>
  17. <maven.compiler.target>8</maven.compiler.target>
  18. <java.version>1.8</java.version>
  19. <spring-cloud.version>2020.0.2</spring-cloud.version>
  20. <square.dependency.version>0.4.0-SNAPSHOT</square.dependency.version>
  21. </properties>
  22. <packaging>pom</packaging>
  23. <description>Demo project for Spring Cloud Square Retrofit Web</description>
  24. <modules>
  25. <module>provider</module>
  26. <module>eureka</module>
  27. <module>consumer-okhttp</module>
  28. <module>client</module>
  29. <module>consumer-retrofit-okhttp</module>
  30. <module>consumer-retrofit-webflux</module>
  31. </modules>
  32. <dependencyManagement>
  33. <dependencies>
  34. <dependency>
  35. <groupId>org.springframework.cloud</groupId>
  36. <artifactId>spring-cloud-dependencies</artifactId>
  37. <version>${spring-cloud.version}</version>
  38. <type>pom</type>
  39. <scope>import</scope>
  40. </dependency>
  41. <dependency>
  42. <groupId>org.springframework.cloud</groupId>
  43. <artifactId>spring-cloud-dependencies</artifactId>
  44. <version>${spring-cloud.version}</version>
  45. <type>pom</type>
  46. <scope>import</scope>
  47. </dependency>
  48. <dependency>
  49. <groupId>com.squareup.okhttp3</groupId>
  50. <artifactId>okhttp</artifactId>
  51. <version>3.14.9</version>
  52. <scope>compile</scope>
  53. </dependency>
  54. <dependency>
  55. <groupId>ch.qos.logback</groupId>
  56. <artifactId>logback-classic</artifactId>
  57. <version>1.1.7</version>
  58. </dependency>
  59. <dependency>
  60. <groupId>org.projectlombok</groupId>
  61. <artifactId>lombok</artifactId>
  62. <version>1.16.16</version>
  63. </dependency>
  64. <dependency>
  65. <groupId>org.springframework.cloud</groupId>
  66. <artifactId>spring-cloud-square-okhttp</artifactId>
  67. <version>${square.dependency.version}</version>
  68. </dependency>
  69. <dependency>
  70. <groupId>org.springframework.cloud</groupId>
  71. <artifactId>spring-cloud-square-retrofit</artifactId>
  72. <version>${square.dependency.version}</version>
  73. </dependency>
  74. <dependency>
  75. <groupId>org.springframework.cloud</groupId>
  76. <artifactId>spring-boot-starter-webflux</artifactId>
  77. <version>${square.dependency.version}</version>
  78. </dependency>
  79. <dependency>
  80. <groupId>org.springframework.cloud</groupId>
  81. <artifactId>spring-cloud-square-retrofit-webclient</artifactId>
  82. <version>${square.dependency.version}</version>
  83. </dependency>
  84. </dependencies>
  85. </dependencyManagement>
  86. <build>
  87. <plugins>
  88. <plugin>
  89. <!--skip deploy (this is just a test module) -->
  90. <artifactId>maven-deploy-plugin</artifactId>
  91. <configuration>
  92. <skip>true</skip>
  93. </configuration>
  94. </plugin>
  95. <plugin>
  96. <groupId>org.springframework.boot</groupId>
  97. <artifactId>spring-boot-maven-plugin</artifactId>
  98. </plugin>
  99. </plugins>
  100. </build>
  101. <repositories>
  102. <repository>
  103. <id>spring-snapshots</id>
  104. <name>Spring Snapshots</name>
  105. <url>https://repo.spring.io/snapshot</url>
  106. <snapshots>
  107. <enabled>true</enabled>
  108. </snapshots>
  109. </repository>
  110. <repository>
  111. <id>spring-milestones</id>
  112. <name>Spring Milestones</name>
  113. <url>https://repo.spring.io/milestone</url>
  114. <snapshots>
  115. <enabled>false</enabled>
  116. </snapshots>
  117. </repository>
  118. </repositories>
  119. <pluginRepositories>
  120. <pluginRepository>
  121. <id>spring-snapshots</id>
  122. <name>Spring Snapshots</name>
  123. <url>https://repo.spring.io/snapshot</url>
  124. <snapshots>
  125. <enabled>true</enabled>
  126. </snapshots>
  127. </pluginRepository>
  128. <pluginRepository>
  129. <id>spring-milestones</id>
  130. <name>Spring Milestones</name>
  131. <url>https://repo.spring.io/milestone</url>
  132. <snapshots>
  133. <enabled>false</enabled>
  134. </snapshots>
  135. </pluginRepository>
  136. </pluginRepositories>
  137. </project>

注册中心eureka

  • eureka应用并没有什么特别之处,pom.xml如下:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>spring-cloud-square-tutorials</artifactId>
  7. <groupId>com.bolingcavalry</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>eureka</artifactId>
  12. <properties>
  13. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  14. <start-class>com.bolingcavalry.eureka.EurekaApplication</start-class>
  15. </properties>
  16. <dependencies>
  17. <dependency>
  18. <groupId>org.springframework.cloud</groupId>
  19. <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
  20. </dependency>
  21. </dependencies>
  22. <dependencyManagement>
  23. <dependencies>
  24. <dependency>
  25. <groupId>org.springframework.cloud</groupId>
  26. <artifactId>spring-cloud-dependencies</artifactId>
  27. <!-- <version>Finchley.BUILD-SNAPSHOT</version>-->
  28. <version>${spring-cloud.version}</version>
  29. <type>pom</type>
  30. <scope>import</scope>
  31. </dependency>
  32. </dependencies>
  33. </dependencyManagement>
  34. <build>
  35. <plugins>
  36. <plugin>
  37. <groupId>org.springframework.boot</groupId>
  38. <artifactId>spring-boot-maven-plugin</artifactId>
  39. <!-- defined in spring-cloud-starter-parent pom (as documentation hint),
  40. but needs to be repeated here -->
  41. <configuration>
  42. <requiresUnpack>
  43. <dependency>
  44. <groupId>com.netflix.eureka</groupId>
  45. <artifactId>eureka-core</artifactId>
  46. </dependency>
  47. <dependency>
  48. <groupId>com.netflix.eureka</groupId>
  49. <artifactId>eureka-client</artifactId>
  50. </dependency>
  51. </requiresUnpack>
  52. </configuration>
  53. </plugin>
  54. <plugin>
  55. <!--skip deploy (this is just a test module) -->
  56. <artifactId>maven-deploy-plugin</artifactId>
  57. <configuration>
  58. <skip>true</skip>
  59. </configuration>
  60. </plugin>
  61. </plugins>
  62. </build>
  63. </project>
  • 中规中矩的配置文件application.yml,端口是8761,后面的应用也要保持一致:
  1. server:
  2. port: 8761
  3. spring:
  4. application:
  5. name: eureka
  6. eureka:
  7. client:
  8. registerWithEureka: false
  9. fetchRegistry: false
  10. server:
  11. waitTimeInMsWhenSyncEmpty: 0
  • 启动类EurekaApplication.java,记得用注解EnableEurekaServer开启eureka服务:
  1. package com.bolingcavalry.eureka;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
  5. @SpringBootApplication
  6. @EnableEurekaServer
  7. public class EurekaApplication {
  8. public static void main(String[] args) throws Exception {
  9. SpringApplication.run(EurekaApplication.class, args);
  10. }
  11. }
  • eureka应用已经完成,接下来是服务提供者了

服务提供者provider

  • -新建名为provider的应用,pom.xml如下,可见是个普通的web工程,会将自己注册到eureka上去:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>spring-cloud-square-tutorials</artifactId>
  7. <groupId>com.bolingcavalry</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>provider</artifactId>
  12. <packaging>jar</packaging>
  13. <dependencies>
  14. <dependency>
  15. <groupId>com.bolingcavalry</groupId>
  16. <artifactId>client</artifactId>
  17. <version>${project.version}</version>
  18. </dependency>
  19. <dependency>
  20. <groupId>org.springframework.boot</groupId>
  21. <artifactId>spring-boot-starter-web</artifactId>
  22. </dependency>
  23. <dependency>
  24. <groupId>org.springframework.cloud</groupId>
  25. <artifactId>spring-cloud-context</artifactId>
  26. </dependency>
  27. <dependency>
  28. <groupId>org.springframework.cloud</groupId>
  29. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  30. </dependency>
  31. <dependency>
  32. <groupId>org.springframework.boot</groupId>
  33. <artifactId>spring-boot-starter-test</artifactId>
  34. <scope>test</scope>
  35. </dependency>
  36. </dependencies>
  37. <build>
  38. <plugins>
  39. <!-- 如果父工程不是springboot,就要用以下方式使用插件,才能生成正常的jar -->
  40. <plugin>
  41. <groupId>org.springframework.boot</groupId>
  42. <artifactId>spring-boot-maven-plugin</artifactId>
  43. <configuration>
  44. <mainClass>com.bolingcavalry.provider.ProviderApplication</mainClass>
  45. </configuration>
  46. <executions>
  47. <execution>
  48. <goals>
  49. <goal>repackage</goal>
  50. </goals>
  51. </execution>
  52. </executions>
  53. </plugin>
  54. </plugins>
  55. </build>
  56. </project>
  • 配置文件application.yml:
  1. spring:
  2. application:
  3. name: provider
  4. server:
  5. port: 18080
  6. eureka:
  7. client:
  8. serviceUrl:
  9. defaultZone: http://localhost:8761/eureka/
  • 启动类ProviderApplication .java:
  1. package com.bolingcavalry.provider;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. @SpringBootApplication
  5. public class ProviderApplication {
  6. public static void main(String[] args) {
  7. SpringApplication.run(ProviderApplication.class, args);
  8. }
  9. }
  • web服务类,可见对外提供了两个接口hello-str和hello-obj,前者返回字符串,或者返回对象:
  1. package com.bolingcavalry.provider.controller;
  2. import com.bolingcavalry.client.HelloResponse;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.cloud.client.ServiceInstance;
  5. import org.springframework.cloud.client.discovery.DiscoveryClient;
  6. import org.springframework.web.bind.annotation.GetMapping;
  7. import org.springframework.web.bind.annotation.RequestParam;
  8. import org.springframework.web.bind.annotation.RestController;
  9. import java.text.SimpleDateFormat;
  10. import java.util.Date;
  11. import java.util.List;
  12. import java.util.Random;
  13. @RestController
  14. public class Hello {
  15. public static final String HELLO_PREFIX = "Hello World";
  16. @Autowired
  17. DiscoveryClient client;
  18. /**
  19. * 随机取一个provider实例,返回其描述信息,
  20. * 如果只有一个provider实例时,返回的就是当前服务信息
  21. * @return
  22. */
  23. private String providerDescription() {
  24. List<ServiceInstance> instances = client.getInstances("provider");
  25. ServiceInstance selectedInstance = instances
  26. .get(new Random().nextInt(instances.size()));
  27. return String.format("serviceId [%s], host [%s], port [%d]",
  28. selectedInstance.getServiceId(),
  29. selectedInstance.getHost(),
  30. selectedInstance.getPort());
  31. }
  32. private String dateStr(){
  33. return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date());
  34. }
  35. @GetMapping("/hello-str")
  36. public String helloStr() {
  37. List<ServiceInstance> instances = client.getInstances("provider");
  38. ServiceInstance selectedInstance = instances
  39. .get(new Random().nextInt(instances.size()));
  40. return HELLO_PREFIX
  41. + " : "
  42. + providerDescription()
  43. + ", "
  44. + dateStr();
  45. }
  46. @GetMapping("/hello-obj")
  47. public HelloResponse helloObj(@RequestParam("name") String name) {
  48. return new HelloResponse(name, dateStr(), providerDescription());
  49. }
  50. }
  • 这个provider应用算是个最朴实无华的web服务了

启动服务

  • 现在可以将eureka和provider服务先后启动,这样后面的应用编码完成后可以直接测试

consumer-okhttp,基于spring-cloud-square的okhttp能力

  • 接下来要创建的应用consumer-okhttp,使用的是spring-cloud-square三种能力的第一种:okhttp

  • pom.xml内容如下,重点是spring-cloud-square-okhttp和spring-cloud-starter-loadbalancer这两个库的引入:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>spring-cloud-square-tutorials</artifactId>
  7. <groupId>com.bolingcavalry</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>consumer-okhttp</artifactId>
  12. <packaging>jar</packaging>
  13. <dependencies>
  14. <dependency>
  15. <groupId>com.bolingcavalry</groupId>
  16. <artifactId>client</artifactId>
  17. <version>${project.version}</version>
  18. </dependency>
  19. <dependency>
  20. <groupId>org.springframework.boot</groupId>
  21. <artifactId>spring-boot-starter-web</artifactId>
  22. </dependency>
  23. <dependency>
  24. <groupId>org.springframework.boot</groupId>
  25. <artifactId>spring-boot-starter-test</artifactId>
  26. <scope>test</scope>
  27. </dependency>
  28. <dependency>
  29. <groupId>org.springframework.boot</groupId>
  30. <artifactId>spring-boot-starter-webflux</artifactId>
  31. <scope>test</scope>
  32. </dependency>
  33. <dependency>
  34. <groupId>org.projectlombok</groupId>
  35. <artifactId>lombok</artifactId>
  36. </dependency>
  37. <dependency>
  38. <groupId>com.squareup.okhttp3</groupId>
  39. <artifactId>okhttp</artifactId>
  40. <scope>compile</scope>
  41. </dependency>
  42. <dependency>
  43. <groupId>org.springframework.cloud</groupId>
  44. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  45. </dependency>
  46. <dependency>
  47. <groupId>org.springframework.cloud</groupId>
  48. <artifactId>spring-cloud-square-okhttp</artifactId>
  49. <version>0.4.0-SNAPSHOT</version>
  50. <scope>compile</scope>
  51. </dependency>
  52. <dependency>
  53. <groupId>org.springframework.cloud</groupId>
  54. <artifactId>spring-cloud-starter-loadbalancer</artifactId>
  55. </dependency>
  56. </dependencies>
  57. <build>
  58. <plugins>
  59. <!-- 如果父工程不是springboot,就要用以下方式使用插件,才能生成正常的jar -->
  60. <plugin>
  61. <groupId>org.springframework.boot</groupId>
  62. <artifactId>spring-boot-maven-plugin</artifactId>
  63. <configuration>
  64. <mainClass>com.bolingcavalry.ConsumerApplication</mainClass>
  65. </configuration>
  66. <executions>
  67. <execution>
  68. <goals>
  69. <goal>repackage</goal>
  70. </goals>
  71. </execution>
  72. </executions>
  73. </plugin>
  74. </plugins>
  75. </build>
  76. </project>
  • 配置文件application.yml,还是常见的那几个配置:应用名、端口、eureka:
  1. spring:
  2. application:
  3. name: consumer-okhttp
  4. server:
  5. port: 18081
  6. eureka:
  7. client:
  8. serviceUrl:
  9. defaultZone: http://localhost:8761/eureka/
  • 启动类:
  1. package com.bolingcavalry.consumer;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. @SpringBootApplication
  5. public class OkhttpApplication {
  6. public static void main(String[] args) {
  7. SpringApplication.run(OkhttpApplication.class, args);
  8. }
  9. }
  • 接下来是重要的配置类OkHttpClientConfig.java,用于实例化OkHttpClient.Builder对象并注册到spring环境:
  1. package com.bolingcavalry.consumer;
  2. import okhttp3.OkHttpClient;
  3. import org.springframework.cloud.client.loadbalancer.LoadBalanced;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. @Configuration
  7. class OkHttpClientConfig{
  8. @Bean
  9. @LoadBalanced
  10. public OkHttpClient.Builder okHttpClientBuilder() {
  11. return new OkHttpClient.Builder();
  12. }
  13. }
  • 然后就可以使用这个Builder来创建OkHttpClient实例了,如下所示,可见入参request的url字段里使用了服务名provider,相当于OkHttpClient内如也能通过服务名取得具体的服务地址,至于是如何获取的,会在后面的文章详细分析,整段代码除了url使用服务名,并没有什么值得关注的地方了,普通的OkHttpClient使用而已:
  1. package com.bolingcavalry.consumer.controller;
  2. import okhttp3.OkHttpClient;
  3. import okhttp3.Request;
  4. import okhttp3.Response;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.web.bind.annotation.GetMapping;
  7. import org.springframework.web.bind.annotation.RestController;
  8. import java.io.IOException;
  9. @RestController
  10. public class RemoteHello {
  11. @Autowired
  12. private OkHttpClient.Builder builder;
  13. @GetMapping("/remote-str")
  14. public String hello() throws IOException {
  15. // 直接使用服务名
  16. Request request = new Request.Builder().url("http://provider/hello-str").build();
  17. // 远程访问
  18. Response response = builder.build().newCall(request).execute();
  19. return "get remote response : " + response.body().string();
  20. }
  21. }
  • 接下来看看单元测试代码,使用MockMvcRequestBuilders构造http请求,检查返回码和返回内容:
  1. package com.bolingcavalry.consumer.controller;
  2. import com.bolingcavalry.client.Constants;
  3. import lombok.extern.slf4j.Slf4j;
  4. import org.junit.jupiter.api.Test;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
  7. import org.springframework.boot.test.context.SpringBootTest;
  8. import org.springframework.http.MediaType;
  9. import org.springframework.test.web.servlet.MockMvc;
  10. import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
  11. import static org.hamcrest.Matchers.containsString;
  12. import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
  13. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
  14. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
  15. @SpringBootTest
  16. @AutoConfigureMockMvc
  17. @Slf4j
  18. class RemoteHelloTest {
  19. @Autowired
  20. private MockMvc mvc;
  21. @Test
  22. void hello() throws Exception {
  23. String responseString = mvc.perform(MockMvcRequestBuilders.get("/remote-str").accept(MediaType.APPLICATION_JSON))
  24. .andExpect(status().isOk())
  25. .andExpect(content().string(containsString(Constants.HELLO_PREFIX)))
  26. .andDo(print())
  27. .andReturn()
  28. .getResponse()
  29. .getContentAsString();
  30. log.info("response in junit test :\n" + responseString);
  31. }
  32. }
  • 如果eureka和provider都运行起来了,那么此时可以直接运行单元测试类,顺利通过测试,如下图:

consumer-retrofit-okhttp,基于spring-cloud-square的okhttp能力

  • 接下来的两个应用都使用了当下热门的retrofit,再搭配Spring Cloud LoadBalance实现服务注册发现,当然了retrofit自身无法完成网络请求处理,要依赖其他库,先看okhttp库的

  • 新建应用consumer-retrofit-okhttp,其pom.xml如下,要注意的必须依赖spring-cloud-square-retrofit和spring-cloud-square-okhttp,另外,为了:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>spring-cloud-square-tutorials</artifactId>
  7. <groupId>com.bolingcavalry</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>consumer-retrofit-okhttp</artifactId>
  12. <dependencies>
  13. <dependency>
  14. <groupId>com.bolingcavalry</groupId>
  15. <artifactId>client</artifactId>
  16. <version>${project.version}</version>
  17. </dependency>
  18. <dependency>
  19. <groupId>org.springframework.boot</groupId>
  20. <artifactId>spring-boot-starter-actuator</artifactId>
  21. </dependency>
  22. <dependency>
  23. <groupId>org.springframework.boot</groupId>
  24. <artifactId>spring-boot-starter-web</artifactId>
  25. </dependency>
  26. <dependency>
  27. <groupId>org.springframework.cloud</groupId>
  28. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  29. </dependency>
  30. <dependency>
  31. <groupId>org.springframework.cloud</groupId>
  32. <artifactId>spring-cloud-square-retrofit</artifactId>
  33. <version>0.4.0-SNAPSHOT</version>
  34. </dependency>
  35. <dependency>
  36. <groupId>org.springframework.cloud</groupId>
  37. <artifactId>spring-cloud-square-okhttp</artifactId>
  38. <version>0.4.0-SNAPSHOT</version>
  39. </dependency>
  40. <dependency>
  41. <groupId>org.springframework.boot</groupId>
  42. <artifactId>spring-boot-starter-test</artifactId>
  43. <scope>test</scope>
  44. </dependency>
  45. </dependencies>
  46. <build>
  47. <plugins>
  48. <plugin>
  49. <!--skip deploy (this is just a test module) -->
  50. <artifactId>maven-deploy-plugin</artifactId>
  51. <configuration>
  52. <skip>true</skip>
  53. </configuration>
  54. </plugin>
  55. <plugin>
  56. <groupId>org.springframework.boot</groupId>
  57. <artifactId>spring-boot-maven-plugin</artifactId>
  58. </plugin>
  59. </plugins>
  60. </build>
  61. </project>
  • 配置文件:
  1. spring:
  2. application:
  3. name: consumer-retrofit-okhttp
  4. server:
  5. port: 18082
  6. eureka:
  7. client:
  8. serviceUrl:
  9. defaultZone: http://localhost:8761/eureka/
  • 启动类:
  1. package com.bolingcavalry.consumer;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. @SpringBootApplication
  5. public class RetrofitOkhttpApplication {
  6. public static void main(String[] args) {
  7. SpringApplication.run(RetrofitOkhttpApplication.class, args);
  8. }
  9. }
  • 配置类,和前一个应用的没啥区别,想想也是,底层可不都是okhttp么:
  1. package com.bolingcavalry.consumer;
  2. import okhttp3.OkHttpClient;
  3. import org.springframework.cloud.client.loadbalancer.LoadBalanced;
  4. import org.springframework.cloud.square.retrofit.EnableRetrofitClients;
  5. import org.springframework.context.annotation.Bean;
  6. import org.springframework.context.annotation.Configuration;
  7. @Configuration
  8. @EnableRetrofitClients
  9. class OkHttpClientConfig{
  10. @Bean
  11. @LoadBalanced
  12. public OkHttpClient.Builder okHttpClientBuilder() {
  13. return new OkHttpClient.Builder();
  14. }
  15. }
  • 接下来,有趣的部分出现了,先定义HelloService.java,里面的注解RetrofitClient指定了对应的服务名provider,在hello方法生,用GET注解指定了provider提供的web接口,而且hello方法的返回值Call,和provider服务中hello-obj的返回值HelloResponse也是对应的,还有就是hello的入参对应着provider服务中hello-obj的入参,很熟悉吧,确实,和feign太像了:
  1. package com.bolingcavalry.consumer.service;
  2. import com.bolingcavalry.client.HelloResponse;
  3. import org.springframework.cloud.square.retrofit.core.RetrofitClient;
  4. import retrofit2.Call;
  5. import retrofit2.http.GET;
  6. import retrofit2.http.Query;
  7. @RetrofitClient("provider")
  8. public interface HelloService {
  9. @GET("/hello-obj")
  10. Call<HelloResponse> hello(@Query("name") String name);
  11. }
  • 接下来是调用provider服务中hello-obj接口的代码RemoteHello.java,如下所示,神奇的一幕出现了,刚才咱们只写了HelloService接口,并没有写它的实现,但是通过Autowired注解却能 从spring环境拿到实例直接使用,在hello方法中,并没有见到远程调用的代码,而是执行helloService.hello,就能发起远程调用,拿到provider返回的结果:
  1. package com.bolingcavalry.consumer.controller;
  2. import com.bolingcavalry.client.HelloResponse;
  3. import com.bolingcavalry.consumer.service.HelloService;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.web.bind.annotation.GetMapping;
  6. import org.springframework.web.bind.annotation.RequestParam;
  7. import org.springframework.web.bind.annotation.RestController;
  8. import java.io.IOException;
  9. @RestController
  10. public class RemoteHello {
  11. @Autowired(required = false)
  12. HelloService helloService;
  13. @GetMapping("/remote-obj")
  14. public HelloResponse hello(@RequestParam("name") String name) throws IOException {
  15. return helloService.hello(name).execute().body();
  16. }
  17. }
  • 看到这里,聪明的您一定会觉得欣宸就是个没见过世面的乡巴佬:定义HelloService 接口,无需开发实现类,这玩意在mybatis不就有了嘛,居然敢说"神奇",我觉得您说得对,欣宸确实没见识,大惊小怪的...

  • 单元测试类如下,由于返回的是json对象,因此可以用andExpect方法再配合MockMvcResultMatchers,对json进行检查:

  1. package com.bolingcavalry.consumer.controller;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.junit.jupiter.api.BeforeEach;
  4. import org.junit.jupiter.api.Test;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
  7. import org.springframework.boot.test.context.SpringBootTest;
  8. import org.springframework.http.MediaType;
  9. import org.springframework.test.web.servlet.MockMvc;
  10. import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
  11. import org.springframework.test.web.servlet.setup.MockMvcBuilders;
  12. import org.springframework.web.context.WebApplicationContext;
  13. import static org.hamcrest.Matchers.is;
  14. import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
  15. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
  16. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
  17. @SpringBootTest
  18. @AutoConfigureMockMvc
  19. @Slf4j
  20. class RemoteHelloTest {
  21. private MockMvc mvc;
  22. @Autowired
  23. private WebApplicationContext webApplicationContext;
  24. @BeforeEach
  25. public void setUp() {
  26. // 在单元测试的时候,MockHttpServletResponse实例的characterEncoding默认是ISO-8859-1,
  27. // 得到的字符串打印出来也是乱码,
  28. // 下面的设置可以解决此问题
  29. if (null==mvc) {
  30. mvc = MockMvcBuilders
  31. .webAppContextSetup(webApplicationContext)
  32. .addFilter((request, response, chain) -> {
  33. response.setCharacterEncoding("UTF-8"); // this is crucial
  34. chain.doFilter(request, response);
  35. }, "/*")
  36. .build();
  37. }
  38. }
  39. @Test
  40. void hello() throws Exception {
  41. // 请求参数是用户名,实时生成一个
  42. String name = System.currentTimeMillis() + "程序员A";
  43. // 请求
  44. String responseString = mvc.perform(
  45. MockMvcRequestBuilders
  46. .get("/remote-obj")
  47. .param("name", name)
  48. .accept(MediaType.APPLICATION_JSON)
  49. )
  50. .andExpect(status().isOk()) // 验证状态
  51. .andExpect(jsonPath("$.name", is(name))) // 验证json中返回的字段是否含有name
  52. .andDo(print())
  53. .andReturn()
  54. .getResponse()
  55. .getContentAsString();
  56. log.info("response in junit test :\n" + responseString);
  57. }
  58. }
  • 执行单元测试,如下图,顺利通过:

consumer-retrofit-webflux,基于spring-cloud-square的retrofit + webflux

  • 最后登场的是consumer-retrofit-webflux,pom.xml如下,依赖库是spring-cloud-square-retrofit + spring-boot-starter-webflux的组合:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>spring-cloud-square-tutorials</artifactId>
  7. <groupId>com.bolingcavalry</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>consumer-retrofit-webflux</artifactId>
  12. <dependencies>
  13. <dependency>
  14. <groupId>com.bolingcavalry</groupId>
  15. <artifactId>client</artifactId>
  16. <version>${project.version}</version>
  17. </dependency>
  18. <dependency>
  19. <groupId>org.springframework.boot</groupId>
  20. <artifactId>spring-boot-starter-actuator</artifactId>
  21. </dependency>
  22. <dependency>
  23. <groupId>org.springframework.boot</groupId>
  24. <artifactId>spring-boot-starter-web</artifactId>
  25. </dependency>
  26. <dependency>
  27. <groupId>org.springframework.cloud</groupId>
  28. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  29. </dependency>
  30. <dependency>
  31. <groupId>org.springframework.cloud</groupId>
  32. <artifactId>spring-cloud-square-retrofit</artifactId>
  33. <version>0.4.0-SNAPSHOT</version>
  34. </dependency>
  35. <dependency>
  36. <groupId>org.springframework.cloud</groupId>
  37. <artifactId>spring-cloud-square-retrofit-webclient</artifactId>
  38. </dependency>
  39. <dependency>
  40. <groupId>org.springframework.boot</groupId>
  41. <artifactId>spring-boot-starter-webflux</artifactId>
  42. </dependency>
  43. <dependency>
  44. <groupId>org.springframework.boot</groupId>
  45. <artifactId>spring-boot-starter-test</artifactId>
  46. <scope>test</scope>
  47. </dependency>
  48. </dependencies>
  49. <build>
  50. <plugins>
  51. <plugin>
  52. <!--skip deploy (this is just a test module) -->
  53. <artifactId>maven-deploy-plugin</artifactId>
  54. <configuration>
  55. <skip>true</skip>
  56. </configuration>
  57. </plugin>
  58. <plugin>
  59. <groupId>org.springframework.boot</groupId>
  60. <artifactId>spring-boot-maven-plugin</artifactId>
  61. </plugin>
  62. </plugins>
  63. </build>
  64. </project>
  • 配置文件application.yml:
  1. spring:
  2. application:
  3. name: consumer-retrofit-webflux
  4. server:
  5. port: 18083
  6. eureka:
  7. client:
  8. serviceUrl:
  9. defaultZone: http://localhost:8761/eureka/
  • 启动类RetrofitWebfluxApplication.java
  1. package com.bolingcavalry.consumer;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. @SpringBootApplication
  5. public class RetrofitWebfluxApplication {
  6. public static void main(String[] args) {
  7. SpringApplication.run(RetrofitWebfluxApplication.class, args);
  8. }
  9. }
  • 配置类AppConfiguration.java,使用的注解是EnableRetrofitClients,实例化的Buider对象是WebClient.Builder,和前面的不一样,要格外注意:
  1. package com.bolingcavalry.consumer;
  2. import okhttp3.OkHttpClient;
  3. import org.springframework.cloud.client.loadbalancer.LoadBalanced;
  4. import org.springframework.cloud.square.retrofit.webclient.EnableRetrofitClients;
  5. import org.springframework.context.annotation.Bean;
  6. import org.springframework.context.annotation.Configuration;
  7. import org.springframework.web.reactive.function.client.WebClient;
  8. @Configuration
  9. @EnableRetrofitClients
  10. class AppConfiguration {
  11. @Bean
  12. @LoadBalanced
  13. public WebClient.Builder builder() {
  14. return WebClient.builder();
  15. }
  16. }
  • 接下来是接口定义,注意hello方法的返回值是Mono,这是weflux风格的返回值,代表异步的0个或一个元素:
  1. package com.bolingcavalry.consumer.service;
  2. import com.bolingcavalry.client.HelloResponse;
  3. import org.springframework.cloud.square.retrofit.core.RetrofitClient;
  4. import reactor.core.publisher.Mono;
  5. import retrofit2.http.GET;
  6. import retrofit2.http.Query;
  7. @RetrofitClient("provider")
  8. public interface HelloService {
  9. @GET("/hello-obj")
  10. Mono<HelloResponse> hello(@Query("name") String name);
  11. }
  • 最后是单元测试类,和前面的没啥区别:
  1. package com.bolingcavalry.consumer.controller;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.junit.jupiter.api.BeforeEach;
  4. import org.junit.jupiter.api.Test;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
  7. import org.springframework.boot.test.context.SpringBootTest;
  8. import org.springframework.http.MediaType;
  9. import org.springframework.test.web.servlet.MockMvc;
  10. import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
  11. import org.springframework.test.web.servlet.setup.MockMvcBuilders;
  12. import org.springframework.web.context.WebApplicationContext;
  13. import static org.hamcrest.Matchers.is;
  14. import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
  15. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
  16. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
  17. @SpringBootTest
  18. @AutoConfigureMockMvc
  19. @Slf4j
  20. class RemoteHelloTest {
  21. private MockMvc mvc;
  22. @Autowired
  23. private WebApplicationContext webApplicationContext;
  24. @BeforeEach
  25. public void setUp() {
  26. // 在单元测试的时候,MockHttpServletResponse实例的characterEncoding默认是ISO-8859-1,
  27. // 得到的字符串打印出来也是乱码,
  28. // 下面的设置可以解决此问题
  29. if (null==mvc) {
  30. mvc = MockMvcBuilders
  31. .webAppContextSetup(webApplicationContext)
  32. .addFilter((request, response, chain) -> {
  33. response.setCharacterEncoding("UTF-8"); // this is crucial
  34. chain.doFilter(request, response);
  35. }, "/*")
  36. .build();
  37. }
  38. }
  39. @Test
  40. void hello() throws Exception {
  41. // 请求参数是用户名,实时生成一个
  42. String name = System.currentTimeMillis() + "程序员B";
  43. // 请求
  44. String responseString = mvc.perform(
  45. MockMvcRequestBuilders
  46. .get("/remote-obj")
  47. .param("name", name)
  48. .accept(MediaType.APPLICATION_JSON)
  49. )
  50. .andExpect(status().isOk()) // 验证状态
  51. .andExpect(jsonPath("$.name", is(name))) // 验证json中返回的字段是否含有name
  52. .andDo(print())
  53. .andReturn()
  54. .getResponse()
  55. .getContentAsString();
  56. log.info("response in junit test :\n" + responseString);
  57. }
  58. }
  • 运行单元测试,如下图,顺利通过,并且红框中所示的中文也没有乱码:

  • 至此,spring-cloud-square的三种类型,咱们全部编码体验了一遍,聪明的您当然不会只满足于使用它们,接下来文章,咱们就去深入spring-cloud-square源码,研究其实现的细节,欣宸原创,必不会辜负您的期待!

你不孤单,欣宸原创一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 数据库+中间件系列
  6. DevOps系列

欢迎关注公众号:程序员欣宸

微信搜索「程序员欣宸」,我是欣宸,期待与您一同畅游Java世界...

https://github.com/zq2599/blog_demos

spring-cloud-square开发实战(三种类型全覆盖)的更多相关文章

  1. 【转】Spring学习---Bean配置的三种方式(XML、注解、Java类)介绍与对比

    [原文]https://www.toutiao.com/i6594205115605844493/ Spring学习Bean配置的三种方式(XML.注解.Java类)介绍与对比 本文将详细介绍Spri ...

  2. 云计算服务的三种类型(SaaS、PaaS、IaaS)

    云计算可以帮助企业降低IT方面的成本和复杂性,并获得他们蓬勃发展所需的灵活性与敏捷性.但是,规划出通往云的明确路径并非易事.毕竟用户需要看透与云相关的市场大肆宣传,然后理解并分析不同种类的云计算模式的 ...

  3. 《Spring Cloud》学习(三) 容错保护!

    在微服务架构中,我们将系统拆分成了很多服务单元,各单元的应用间互相依赖.由于每个单元都在不同的进程中运行,依赖通过远程调用的方式执行,这样就有可能因为网络原因或是依赖服务自身间题出现调用故障或延迟,而 ...

  4. Spring线程池开发实战

    Spring线程池开发实战 作者:chszs,转载需注明. 作者博客主页:http://blog.csdn.net/chszs 本文提供了三个Spring多线程开发的例子,由浅入深,由于例子一目了然, ...

  5. 缓慢变化维 (Slowly Changing Dimension) 常见的三种类型及原型设计(转)

    开篇介绍 在从 OLTP 业务数据库向 DW 数据仓库抽取数据的过程中,特别是第一次导入之后的每一次增量抽取往往会遇到这样的问题:业务数据库中的一些数据发生了更改,到底要不要将这些变化也反映到数据仓库 ...

  6. Kubernetes service 三种类型/NodePort端口固定

    Kubernetes service 三种类型 • ClusterIP:默认,分配一个集群内部可以访问的虚拟IP(VIP)• NodePort:在每个Node上分配一个端口作为外部访问入口• Load ...

  7. 留学英文论文写作Abstract三种类型

    所谓Abstract,就是对所写论文主要内容的精炼概括.Abstract是美国人的说法,英国的科技期刊喜欢称之为Summary.在英文中,有资料是这么对其定义的:Abstract is a sketc ...

  8. Spring Cloud Alibaba入门实战之nacos(一)

    Spring Cloud Alibaba入门实战之nacos(一) 前情介绍 ​ Spring Cloud Alibaba 是阿里巴巴提供的新一代的微服务解决方案,相信会有越来越多采用微服务架构的公司 ...

  9. iOS开发UI篇—iOS开发中三种简单的动画设置

    iOS开发UI篇—iOS开发中三种简单的动画设置 [在ios开发中,动画是廉价的] 一.首尾式动画 代码示例: // beginAnimations表示此后的代码要“参与到”动画中 [UIView b ...

随机推荐

  1. 使用私有gitlab发布自动生成版本号和标签(version和tag)(骚)

    设置 semantic ,自动生成版本号和标签 FROM node:14-buster-slim LABEL maintainer="wangyunpeng" COPY sourc ...

  2. TP5更新数据成功,但判断结果不符

    thinkphp的CURD中,使用save方法时会出现一个奇怪的问题,即如果数据没有更新(与原数据相同),返回值判断为false.其实很久之前就发现了这个问题,一度以为是官方代码的问题,但是一直拖延到 ...

  3. 【Azure 应用服务】App Service For Linux 部署PHP Laravel 项目,如何修改首页路径为 wwwroot\public\index.php

    问题描述 参考官方文档部署 PHP Laravel 项目到App Service for Linux环境中,但是访问应用时候遇见了500 Server Error 错误. 从部署的日志中,可以明确看出 ...

  4. 基于pgpool搭建postgressql集群部署

    postgresql集群搭建 基于pgpool中间件实现postgresql一主多从集群部署,这里用两台服务器作一主一从示例 虚拟机名 IP 主从划分 THApps 192.168.1.31 主节点 ...

  5. FormData上传文件 带进度条

    * jQuery ajax  FormData 上传文件 template $.ajax({ url: url, type: 'POST', data: new FormData(form), dat ...

  6. python学习笔记(三)-列表&字典

    列表: 一.列表操作"""Python内置的一种数据类型是列表:list.list是一种有序的集合,可以随时添加和删除其中的元素.比如,列出班里所有同学的名字,就可以用一 ...

  7. CF891E-Lust【EGF】

    正题 题目链接:https://www.luogu.com.cn/problem/CF891E 题目大意 \(n\)个数字的一个序列\(a_i\),每次随机选择一个让它减去一.然后贡献加上所有其他\( ...

  8. Winform配置文件读写操作

    前言 在项目当中为了增加软件的灵活性及可配置性,配置文件在程序当中起着不可替代的作用.下面介绍一下最近用的比较多的方式. config文件的操作 数据库连接字符串 1.获取连接字符串 public s ...

  9. fastjson将json转为Map<String,String>踩坑

    字符串 对于一个json字符串 String str = "{"specItem":"[红, 大]","specName":&qu ...

  10. Semi-supervised semantic segmentation needs strong, varied perturbations

    论文阅读: Semi-supervised semantic segmentation needs strong, varied perturbations 作者声明 版权声明:本文为博主原创文章,遵 ...