Welcome to my GitHub
Here is a classification and summary of all original (including supporting source code): 16183228c93406 https://github.com/zq2599/blog_demos
Overview of this article
- The previous article "Understanding spring-cloud-square in five minutes" introduces in detail what is spring-cloud-square and the detailed concepts of the three implementation types. If you love hands-on, you can’t wait to code and experience spring-cloud-square. In this article, let’s have fun and experience the smart client that spring officially brings us.
- As mentioned in the title, in the next section, we will have coding experience for all three clients provided by spring-cloud-square. In general, this article consists of the following contents:
- Create a new maven project named <font color="blue">spring-cloud-square-tutorials</font>, which is the parent project of all applications in this article, and the library version is managed in this project;
- Create a subproject <font color="blue">eureka</font> as the registration center
- Create a subproject <font color="blue">client</font> and put some common data structures
- Create a sub-project <font color="blue">provider</font>, the identity is the service provider, the next three sub-projects that use spring-cloud-square, all call the service of the provider
- Create sub-project <font color="blue">consumer-okhttp</font>, based on spring-cloud-square's <font color="red">okhttp</font> ability to make remote calls
- Create a sub-project <font color="blue">consumer-retrofit-okhttp</font>, based on spring-cloud-square's <font color="red">retrofit + okhttp</font> ability to make remote calls
- Create sub-project <font color="blue">consumer-retrofit-webflux</font>, based on spring-cloud-square's <font color="red">retrofit + webflux</font> ability to make remote calls
- The relationship between the above services is as follows:
How to verify
- After the code is written, how to verify that the function meets expectations? This article uses unit testing. Consumer-okhttp, consumer-retrofit-okhttp, and consumer-retrofit-webflux three sub-projects have their own unit test code. Passing execution means that the code function meets expectations.
Source download
- The complete source code in this actual combat can be downloaded on GitHub, the address and link information are shown in the following table ( https://github.com/zq2599/blog_demos):
name | Link | Remark |
---|---|---|
Project homepage | https://github.com/zq2599/blog_demos | The project's homepage on GitHub |
git warehouse address (https) | https://github.com/zq2599/blog_demos.git | The warehouse address of the source code of the project, https protocol |
git warehouse address (ssh) | git@github.com:zq2599/blog_demos.git | The warehouse address of the source code of the project, ssh protocol |
- There are multiple folders in this git project. The source code of this article is under the <font color="blue">spring-cloud-square-tutorials</font> folder, as shown in the red box in the following figure:
Version Information
- The main versions involved in the actual combat in this article are as follows:
- JDK:1.8.0_291
- IDEA:2021.1.3 (Ultimate Edition)
- maven:3.8.1
- Operating system: win10 64 bit
- springboot:2.4.4
- spring-cloud:2020.0.2
- spring-cloud-square:0.4.0-SNAPSHOT
Parent project spring-cloud-square-tutorials
- The parent project is named <font color="blue">spring-cloud-square-tutorials</font>, and its pom.xml is as follows, except that the version of the dependent library is managed here, but also the introduction of the two repositories ( https://repo.spring.io/snapshot and https://repo.spring.io/milestone), they were introduced because spring-cloud-square is still in the incubation stage and has not been released to the maven central warehouse:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.bolingcavalry</groupId>
<artifactId>spring-cloud-square-tutorials</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<java.version>1.8</java.version>
<spring-cloud.version>2020.0.2</spring-cloud.version>
<square.dependency.version>0.4.0-SNAPSHOT</square.dependency.version>
</properties>
<packaging>pom</packaging>
<description>Demo project for Spring Cloud Square Retrofit Web</description>
<modules>
<module>provider</module>
<module>eureka</module>
<module>consumer-okhttp</module>
<module>client</module>
<module>consumer-retrofit-okhttp</module>
<module>consumer-retrofit-webflux</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.14.9</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.7</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.16</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-square-okhttp</artifactId>
<version>${square.dependency.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-square-retrofit</artifactId>
<version>${square.dependency.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<version>${square.dependency.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-square-retrofit-webclient</artifactId>
<version>${square.dependency.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<!--skip deploy (this is just a test module) -->
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>
Registration center eureka
- There is nothing special about the eureka application. The pom.xml is as follows:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-square-tutorials</artifactId>
<groupId>com.bolingcavalry</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>eureka</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<start-class>com.bolingcavalry.eureka.EurekaApplication</start-class>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<!-- <version>Finchley.BUILD-SNAPSHOT</version>-->
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<!-- defined in spring-cloud-starter-parent pom (as documentation hint),
but needs to be repeated here -->
<configuration>
<requiresUnpack>
<dependency>
<groupId>com.netflix.eureka</groupId>
<artifactId>eureka-core</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.eureka</groupId>
<artifactId>eureka-client</artifactId>
</dependency>
</requiresUnpack>
</configuration>
</plugin>
<plugin>
<!--skip deploy (this is just a test module) -->
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>
- The satisfactory configuration file application.yml, the port is 8761, and the following applications should be consistent:
server:
port: 8761
spring:
application:
name: eureka
eureka:
client:
registerWithEureka: false
fetchRegistry: false
server:
waitTimeInMsWhenSyncEmpty: 0
- Start the class EurekaApplication.java, remember to enable the eureka service with the annotation EnableEurekaServer:
package com.bolingcavalry.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(EurekaApplication.class, args);
}
}
- The eureka application is complete, and the service provider is next
Service provider
- -Create a new application named <font color="blue">provider</font>, pom.xml is as follows, it can be seen that it is an ordinary web project, and it will register itself to eureka:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-square-tutorials</artifactId>
<groupId>com.bolingcavalry</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>provider</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.bolingcavalry</groupId>
<artifactId>client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 如果父工程不是springboot,就要用以下方式使用插件,才能生成正常的jar -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.bolingcavalry.provider.ProviderApplication</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
- Configuration file application.yml:
spring:
application:
name: provider
server:
port: 18080
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
- Start the class ProviderApplication.java:
package com.bolingcavalry.provider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
- web service class, it can be seen that there are two external interfaces <font color="blue">hello-str</font> and <font color="blue">hello-obj</font>. The former returns a string or returns Object:
package com.bolingcavalry.provider.controller;
import com.bolingcavalry.client.HelloResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Random;
@RestController
public class Hello {
public static final String HELLO_PREFIX = "Hello World";
@Autowired
DiscoveryClient client;
/**
* 随机取一个provider实例,返回其描述信息,
* 如果只有一个provider实例时,返回的就是当前服务信息
* @return
*/
private String providerDescription() {
List<ServiceInstance> instances = client.getInstances("provider");
ServiceInstance selectedInstance = instances
.get(new Random().nextInt(instances.size()));
return String.format("serviceId [%s], host [%s], port [%d]",
selectedInstance.getServiceId(),
selectedInstance.getHost(),
selectedInstance.getPort());
}
private String dateStr(){
return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date());
}
@GetMapping("/hello-str")
public String helloStr() {
List<ServiceInstance> instances = client.getInstances("provider");
ServiceInstance selectedInstance = instances
.get(new Random().nextInt(instances.size()));
return HELLO_PREFIX
+ " : "
+ providerDescription()
+ ", "
+ dateStr();
}
@GetMapping("/hello-obj")
public HelloResponse helloObj(@RequestParam("name") String name) {
return new HelloResponse(name, dateStr(), providerDescription());
}
}
- This provider application is the most unpretentious web service
Start service
- Now you can start the eureka and provider services one after another, so that the subsequent application coding can be tested directly
consumer-okhttp, okhttp capability based on spring-cloud-square
- The application <font color="blue">consumer-okhttp</font> to be created next uses the first of the three capabilities of spring-cloud-square: okhttp
- The content of pom.xml is as follows, focusing on the introduction of the two libraries spring-cloud-square-okhttp and spring-cloud-starter-loadbalancer:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-square-tutorials</artifactId>
<groupId>com.bolingcavalry</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>consumer-okhttp</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.bolingcavalry</groupId>
<artifactId>client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-square-okhttp</artifactId>
<version>0.4.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 如果父工程不是springboot,就要用以下方式使用插件,才能生成正常的jar -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.bolingcavalry.ConsumerApplication</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
- The configuration file application.yml is still the common configuration: application name, port, eureka:
spring:
application:
name: consumer-okhttp
server:
port: 18081
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
- Startup class:
package com.bolingcavalry.consumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class OkhttpApplication {
public static void main(String[] args) {
SpringApplication.run(OkhttpApplication.class, args);
}
}
- Next is the important configuration class OkHttpClientConfig.java, which is used to instantiate the OkHttpClient.Builder object and register it to the spring environment:
package com.bolingcavalry.consumer;
import okhttp3.OkHttpClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
class OkHttpClientConfig{
@Bean
@LoadBalanced
public OkHttpClient.Builder okHttpClientBuilder() {
return new OkHttpClient.Builder();
}
}
- Then you can use this Builder to create an OkHttpClient instance, as shown below, you can see that the service name <font color="blue">provider</font> is used in the url field of the input request, which is equivalent to OkHttpClient. The service name obtains the specific service address. As for how to obtain it, I will analyze it in detail in the following article. The whole code has nothing to pay attention to except that the url uses the service name. The ordinary OkHttpClient uses it:
package com.bolingcavalry.consumer.controller;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
@RestController
public class RemoteHello {
@Autowired
private OkHttpClient.Builder builder;
@GetMapping("/remote-str")
public String hello() throws IOException {
// 直接使用服务名
Request request = new Request.Builder().url("http://provider/hello-str").build();
// 远程访问
Response response = builder.build().newCall(request).execute();
return "get remote response : " + response.body().string();
}
}
- Next, take a look at the unit test code, use MockMvcRequestBuilders to construct an http request, check the return code and return content:
package com.bolingcavalry.consumer.controller;
import com.bolingcavalry.client.Constants;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
@Slf4j
class RemoteHelloTest {
@Autowired
private MockMvc mvc;
@Test
void hello() throws Exception {
String responseString = mvc.perform(MockMvcRequestBuilders.get("/remote-str").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().string(containsString(Constants.HELLO_PREFIX)))
.andDo(print())
.andReturn()
.getResponse()
.getContentAsString();
log.info("response in junit test :\n" + responseString);
}
}
- If both eureka and provider are up and running, then you can directly run the unit test class and pass the test successfully, as shown in the following figure:
consumer-retrofit-okhttp, based on spring-cloud-square's okhttp capability
- The next two applications both use the popular retrofit, and then use Spring Cloud LoadBalance to realize service registration and discovery. Of course, retrofit itself cannot complete network request processing. It depends on other libraries. Look at the okhttp library first.
- Create a new application <font color="blue">consumer-retrofit-okhttp</font>. Its pom.xml is as follows. Note that you must rely on spring-cloud-square-retrofit and spring-cloud-square-okhttp. In addition, for :
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-square-tutorials</artifactId>
<groupId>com.bolingcavalry</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>consumer-retrofit-okhttp</artifactId>
<dependencies>
<dependency>
<groupId>com.bolingcavalry</groupId>
<artifactId>client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-square-retrofit</artifactId>
<version>0.4.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-square-okhttp</artifactId>
<version>0.4.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<!--skip deploy (this is just a test module) -->
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- Configuration file:
spring:
application:
name: consumer-retrofit-okhttp
server:
port: 18082
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
- Startup class:
package com.bolingcavalry.consumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class RetrofitOkhttpApplication {
public static void main(String[] args) {
SpringApplication.run(RetrofitOkhttpApplication.class, args);
}
}
- The configuration class is no different from the previous application. Think about it. Isn't the bottom layer all okhttp:
package com.bolingcavalry.consumer;
import okhttp3.OkHttpClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.square.retrofit.EnableRetrofitClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableRetrofitClients
class OkHttpClientConfig{
@Bean
@LoadBalanced
public OkHttpClient.Builder okHttpClientBuilder() {
return new OkHttpClient.Builder();
}
}
- Next, the interesting part appears. First define HelloService.java. The annotation RetrofitClient inside specifies the corresponding service name <font color="blue">provider</font>. In the hello method, the provider is specified with the GET annotation The provided web interface, and the return value of the hello method, Call<HelloResponse>, corresponds to the return value of hello-obj in the provider service, HelloResponse, and the input parameter of hello corresponds to the input parameter of hello-obj in the provider service. Very familiar, indeed, too similar to Feign:
package com.bolingcavalry.consumer.service;
import com.bolingcavalry.client.HelloResponse;
import org.springframework.cloud.square.retrofit.core.RetrofitClient;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;
@RetrofitClient("provider")
public interface HelloService {
@GET("/hello-obj")
Call<HelloResponse> hello(@Query("name") String name);
}
- Next is the code RemoteHello.java that calls the hello-obj interface in the provider service. As shown below, a magical scene has appeared. Just now we only wrote the HelloService interface and did not write its implementation, but it can be accessed from spring through Autowired annotations. The environment gets the instance and uses it directly. In the hello method, the remote call code is not seen, but by executing helloService.hello, you can initiate a remote call and get the result returned by the provider:
package com.bolingcavalry.consumer.controller;
import com.bolingcavalry.client.HelloResponse;
import com.bolingcavalry.consumer.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
@RestController
public class RemoteHello {
@Autowired(required = false)
HelloService helloService;
@GetMapping("/remote-obj")
public HelloResponse hello(@RequestParam("name") String name) throws IOException {
return helloService.hello(name).execute().body();
}
}
- Seeing this, you will feel that Xinchen is a <font color="blue">unknown hillbilly</font>: define the HelloService interface, no need to develop an implementation class, this thing is not available in mybatis Well, I dare to say "magical", I think you are right, Xin Chen is really ignorant, making a fuss...
- The unit test class is as follows. Since the returned json object, the andExpect method can be used in conjunction with MockMvcResultMatchers to check the json:
package com.bolingcavalry.consumer.controller;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
@Slf4j
class RemoteHelloTest {
private MockMvc mvc;
@Autowired
private WebApplicationContext webApplicationContext;
@BeforeEach
public void setUp() {
// 在单元测试的时候,MockHttpServletResponse实例的characterEncoding默认是ISO-8859-1,
// 得到的字符串打印出来也是乱码,
// 下面的设置可以解决此问题
if (null==mvc) {
mvc = MockMvcBuilders
.webAppContextSetup(webApplicationContext)
.addFilter((request, response, chain) -> {
response.setCharacterEncoding("UTF-8"); // this is crucial
chain.doFilter(request, response);
}, "/*")
.build();
}
}
@Test
void hello() throws Exception {
// 请求参数是用户名,实时生成一个
String name = System.currentTimeMillis() + "程序员A";
// 请求
String responseString = mvc.perform(
MockMvcRequestBuilders
.get("/remote-obj")
.param("name", name)
.accept(MediaType.APPLICATION_JSON)
)
.andExpect(status().isOk()) // 验证状态
.andExpect(jsonPath("$.name", is(name))) // 验证json中返回的字段是否含有name
.andDo(print())
.andReturn()
.getResponse()
.getContentAsString();
log.info("response in junit test :\n" + responseString);
}
}
- Execute the unit test, as shown in the figure below, and pass successfully:
consumer-retrofit-webflux, retrofit + webflux based on spring-cloud-square
- The last one to appear is consumer-retrofit-webflux, pom.xml is as follows, and the dependent library is a combination of spring-cloud-square-retrofit + spring-boot-starter-webflux:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-square-tutorials</artifactId>
<groupId>com.bolingcavalry</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>consumer-retrofit-webflux</artifactId>
<dependencies>
<dependency>
<groupId>com.bolingcavalry</groupId>
<artifactId>client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-square-retrofit</artifactId>
<version>0.4.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-square-retrofit-webclient</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<!--skip deploy (this is just a test module) -->
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- Configuration file application.yml:
spring:
application:
name: consumer-retrofit-webflux
server:
port: 18083
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
Start class RetrofitWebfluxApplication.java
package com.bolingcavalry.consumer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class RetrofitWebfluxApplication { public static void main(String[] args) { SpringApplication.run(RetrofitWebfluxApplication.class, args); } }
- The configuration class AppConfiguration.java, the annotation used is EnableRetrofitClients, and the instantiated Buider object is WebClient.Builder, which is different from the previous one. Pay special attention to:
package com.bolingcavalry.consumer;
import okhttp3.OkHttpClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.square.retrofit.webclient.EnableRetrofitClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
@Configuration
@EnableRetrofitClients
class AppConfiguration {
@Bean
@LoadBalanced
public WebClient.Builder builder() {
return WebClient.builder();
}
}
- Next is the interface definition. Note that the return value of the hello method is <font color="blue">Mono<HelloResponse></font>, which is a weflux-style return value, representing asynchronous zero or one element:
package com.bolingcavalry.consumer.service;
import com.bolingcavalry.client.HelloResponse;
import org.springframework.cloud.square.retrofit.core.RetrofitClient;
import reactor.core.publisher.Mono;
import retrofit2.http.GET;
import retrofit2.http.Query;
@RetrofitClient("provider")
public interface HelloService {
@GET("/hello-obj")
Mono<HelloResponse> hello(@Query("name") String name);
}
- Finally, there is the unit test class, which is no different from the previous one:
package com.bolingcavalry.consumer.controller;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
@Slf4j
class RemoteHelloTest {
private MockMvc mvc;
@Autowired
private WebApplicationContext webApplicationContext;
@BeforeEach
public void setUp() {
// 在单元测试的时候,MockHttpServletResponse实例的characterEncoding默认是ISO-8859-1,
// 得到的字符串打印出来也是乱码,
// 下面的设置可以解决此问题
if (null==mvc) {
mvc = MockMvcBuilders
.webAppContextSetup(webApplicationContext)
.addFilter((request, response, chain) -> {
response.setCharacterEncoding("UTF-8"); // this is crucial
chain.doFilter(request, response);
}, "/*")
.build();
}
}
@Test
void hello() throws Exception {
// 请求参数是用户名,实时生成一个
String name = System.currentTimeMillis() + "程序员B";
// 请求
String responseString = mvc.perform(
MockMvcRequestBuilders
.get("/remote-obj")
.param("name", name)
.accept(MediaType.APPLICATION_JSON)
)
.andExpect(status().isOk()) // 验证状态
.andExpect(jsonPath("$.name", is(name))) // 验证json中返回的字段是否含有name
.andDo(print())
.andReturn()
.getResponse()
.getContentAsString();
log.info("response in junit test :\n" + responseString);
}
}
- Run the unit test, as shown in the figure below, it passed smoothly, and there is no garbled Chinese characters in the red box:
- So far, we have all coding experience for the three types of spring-cloud-square. Of course, you won’t just be satisfied with using them. In the next article, let’s go deep into the source code of spring-cloud-square and study its implementation. The details, Xinchen original, will not disappoint your expectations!
You are not alone, Xinchen and original are with you all the way
- Java series
- Spring series
- Docker series
- kubernetes series
- Database + Middleware Series
- DevOps series
Welcome to pay attention to the public account: programmer Xin Chen
Search for "Programmer Xin Chen" on WeChat, I am Xin Chen, and I look forward to traveling the Java world with you...
https://github.com/zq2599/blog_demos
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。