Spring cloud square development practice (full coverage of three types)

Welcome to my GitHub

Here we classify and summarize all the original works of Xinchen (including supporting source code): https://github.com/zq2599/blog_demos

Overview of this article

  • Above Five minutes to understand spring cloud square It introduces in detail what spring cloud square is and the detailed concepts of three implementation types. 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 brought to us by the official spring

  • As mentioned in the title, in the following, we will code the three client experiences provided by spring cloud square. In general, this article consists of the following contents:

  1. Create a new maven project named spring cloud square tutorials, which is the parent project of all applications in this article, and the library version is managed uniformly in this project;
  2. Create the sub project eureka as the registry
  3. Create a sub project client and put some common data structures
  4. Create a sub project provider as a service provider. The next three sub projects using spring cloud square call the service of the provider
  5. Create the sub project consumer okhttp, and make remote calls based on the okhttp capability of spring cloud square
  6. Create the sub project consumer retro okhttp, and make remote calls based on the retro + okhttp capability of spring cloud square
  7. Create a sub project consumer retrofit weblux, and make remote calls based on the retrofit + weblux capability of spring cloud square
  • The relationship between the above services is shown in the figure below:

How to verify

  • After the code is written, how to verify whether the function meets the expectations? This article adopts the method of unit test. Consumer okhttp, consumer retrofit okhttp and consumer retrofit weblux all have their own unit test code. Passing the execution means that the code functions meet the expectations

Source download

  • The complete source code in this actual combat can be downloaded from GitHub. The address and link information are shown in the table below( https://github.com/zq2599/blog_demos):
name link remarks
Project Home https://github.com/zq2599/blog_demos The project is on the GitHub home page
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 project source code warehouse address, ssh protocol
  • There are multiple folders in the git project. The source code of this article is in the spring cloud square tutorials folder, as shown in the red box below:

Version information

  • The main versions involved in the actual combat are as follows:
  1. JDK: 1.8.0_291
  2. IDEA: 2021.1.3 (Ultimate Edition)
  3. maven: 3.8.1
  4. Operating system: win10 64 bit
  5. springboot: 2.4.4
  6. spring-cloud: 2020.0.2
  7. spring-cloud-square: 0.4.0-SNAPSHOT

Parent project spring cloud square tutorials

<?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. 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 regular configuration file application.yml has a port of 8761, and the following applications should also be consistent:
server:
  port: 8761

spring:
  application:
    name: eureka

eureka:
  client:
    registerWithEureka: false
    fetchRegistry: false
  server:
    waitTimeInMsWhenSyncEmpty: 0
  • Start the class EurekaApplication.java and remember to use the annotation EnableEurekaServer to start the eureka service:
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 has been completed, and the next step is the service provider

Service provider

  • -Create a new application named provider. pom.xml is as follows. It can be seen that it is a common web project and will register itself with 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>
            <!-- If the parent project is not springboot,You need to use the plug-in in the following ways to generate a normal plug-in 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 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);
    }
}
  • It can be seen that the web service class provides two interfaces: Hello STR and hello obj. The former returns a string or an 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;

    /**
     * Randomly take a provider instance and return its description information,
     * If there is only one provider instance, the current service information is returned
     * @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 plain web service

Start service

  • Now you can start eureka and provider services successively, so that later applications can be tested directly after coding

Consumer okhttp, based on the okhttp capability of spring cloud square

  • The application consumer okhttp to be created next uses the first of the three spring cloud square capabilities: okhttp

  • pom.xml is as follows, focusing on the introduction of spring cloud square okhttp and spring cloud starter loadbalancer libraries:

<?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>
            <!-- If the parent project is not springboot,You need to use the plug-in in the following ways to generate a normal plug-in 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 also 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 with 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 provider is used in the url field of the input request, which is equivalent to that in OkHttpClient. If you can also obtain the specific service address through the service name, how to obtain it will be analyzed in detail in the following article. The whole code is not worth learning except that the url uses the service name Note: it is only used by ordinary OkHttpClient:
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 {
        // Use service name directly
        Request request = new Request.Builder().url("http://provider/hello-str").build();

        // remote access
        Response response = builder.build().newCall(request).execute();

        return "get remote response : " + response.body().string();
    }
}
  • Next, let's look at the unit test code. Use mockmvcrequebuilders to construct the http request, and 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 running, you can directly run the unit test class and successfully pass the test, as shown in the following figure:

Consumer retrofit okhttp, which is based on the okhttp capability of spring cloud square

  • The next two applications use the popular retro fit, and then use Spring Cloud LoadBalance to realize service registration discovery. Of course, retro fit itself cannot complete network request processing. It needs to rely on other libraries. First, look at the okhttp library

  • Create a new application consumer retrofit okhttp. Its pom.xml is as follows. Note that it must rely on spring cloud square retrofit and spring cloud square okhttp. In addition, in order to:

<?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>
  • Profile:
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 that of the previous application. Think about it. The bottom layer is 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 specifies the corresponding service name provider. In the hello method generation, the GET annotation specifies the web interface provided by the provider. Moreover, the return value Call of the hello method corresponds to the return value HelloResponse of hello obj in the provider service. In addition, the input parameter of hello corresponds to P The hello obj input parameter in the provider service is very familiar. Indeed, it is very 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);
}
  • The next step is to call the code RemoteHello.java of the Hello obj interface in the provider service. As shown below, a magical scene appears. Just now we only wrote the HelloService interface, not its implementation, but we can get an instance from the spring environment through the Autowired annotation for direct use. In the Hello method, we don't see the code of remote call, Instead, execute helloService.hello to 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 here, smart you will think Xinchen is a hick who has never seen the world: defining HelloService interface without developing implementation classes. This thing is available in mybatis. I dare to say "magic". I think you are right. Xinchen really has no knowledge and is surprised

  • The unit test classes are as follows. Because the returned json object is json, you can use the andExpect method and MockMvcResultMatchers to check 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() {
        // During unit testing, the characterEncoding of the MockHttpServletResponse instance is ISO-8859-1 by default,
        // The obtained string is also garbled when printed,
        // The following settings can solve this problem
        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 {
        // The request parameter is the user name, which is generated in real time
        String name = System.currentTimeMillis() + "programmer A";

        // request
        String responseString = mvc.perform(
                MockMvcRequestBuilders
                        .get("/remote-obj")
                        .param("name", name)
                        .accept(MediaType.APPLICATION_JSON)
        )
                .andExpect(status().isOk())                           // Verification status
                .andExpect(jsonPath("$.name", is(name)))    // Verify that the field returned in json contains 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 it successfully:

Consumer retro fit weblux, retro fit + weblux based on spring cloud square

  • The last one is consumer retrofit weblux. pom.xml is as follows. The dependent library is a combination of spring cloud square retrofit + spring boot starter weblux:
<?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);
    }
}
  • For 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 Mono, which is a weflux style return value, representing 0 or one asynchronous 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);
}
  • The last 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() {
        // During unit testing, the characterEncoding of the MockHttpServletResponse instance is ISO-8859-1 by default,
        // The obtained string is also garbled when printed,
        // The following settings can solve this problem
        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 {
        // The request parameter is the user name, which is generated in real time
        String name = System.currentTimeMillis() + "programmer B";

        // request
        String responseString = mvc.perform(
                MockMvcRequestBuilders
                        .get("/remote-obj")
                        .param("name", name)
                        .accept(MediaType.APPLICATION_JSON)
        )
                .andExpect(status().isOk())                           // Verification status
                .andExpect(jsonPath("$.name", is(name)))    // Verify that the field returned in json contains name
                .andDo(print())
                .andReturn()
                .getResponse()
                .getContentAsString();

        log.info("response in junit test :\n" + responseString);
    }
}
  • Run the unit test, as shown in the figure below, and it passes smoothly, and the Chinese shown in the red box is not garbled:

  • So far, we have coded and experienced all the three types of spring cloud square. Of course, smart you are not only satisfied with using them. In the next article, we will go deep into the spring cloud square source code and study the details of its implementation. Xinchen is original and will not live up to your expectations!

You're not alone. Xinchen's original accompanies you all the way

  1. Java series
  2. Spring collection
  3. Docker series
  4. kubernetes series
  5. Database + middleware series
  6. DevOps series

Welcome to the official account: programmer Xin Chen

Wechat search "programmer Xinchen", I'm Xinchen, looking forward to traveling with you in the Java World
https://github.com/zq2599/blog_demos

Posted by zavin on Sun, 31 Oct 2021 17:53:37 -0700