工具使用集| 远程调用工具 之 WebClient & RestTemplate & Feign

远程调用工具

前言

在工作中,除了通过切面的方式来记录调用日志或者通过MVc提供的Filter来进行操作,还有什么别的方式嘛?远程调用工具自身携带一些类可以实现一些类似的功能。记录多种远程调用工具的使用。想让自己对它们有一个整体的认识,想自己可以顺手的使用他们。

WebClient

非阻塞和响应式:WebClient 基于 Reactor 提供了非阻塞的编程模型,可以处理大量并发请求,减少线程的阻塞等待时间,并提供了响应式的操作符和数据流处理。

  • 非阻塞指的是在 IO 操作中,线程不会被阻塞等待操作完成,可以继续执行其他任务。而响应式是一种基于事件流的编程范式,关注数据流的处理和转换。

异步和同步请求:WebClient 支持异步和同步的请求方式。使用异步方式,可以通过返回 Mono(单个值)或 Flux(多个值)来处理响应。而同步方式会阻塞当前线程,直到收到完整的响应。

  • 同步请求:

    • 发起请求后,当前线程会阻塞等待服务器响应。
    • 在收到完整的响应后,才会继续执行后续代码。
    • 适用于简单的请求场景,其中请求和响应之间的关系是一对一的。
    • 请求处理和响应处理是串行进行的。
  • 异步请求:

    • 发起请求后,当前线程不会阻塞等待服务器响应。
    • 可以通过回调函数、Future/Promise 或响应式编程等方式,注册回调来处理响应或获取响应结果。
    • 适用于同时发起多个请求、并行处理多个请求或不需要立即等待响应的场景。
    • 请求处理和响应处理是并行进行的。

Mono 表示一个包含零个或一个元素的异步序列。可以将它理解为一个单值的异步结果,类似于 Java 8 中的 CompletableFutureMono 在处理单个值的情况下非常有用,例如获取单个资源或执行单个操作。

Flux 表示一个包含零个或多个元素的异步序列。可以将它理解为一个多值的异步结果,类似于 Java 8 中的 StreamFlux 在处理多个值的情况下非常有用,例如处理多个资源或批量操作。

功能丰富的 API:WebClient 提供了丰富的方法和操作符来构建和处理 HTTP 请求和响应。你可以设置请求方法、添加请求头、设置请求体、处理响应状态码、处理响应体等。

  1. 发起请求:

    • 使用 get()post()put()delete() 等方法来指定 HTTP 方法。
    • 可以使用 uri() 方法设置请求的 URI。
    • 通过 headers() 方法设置请求头。
    • 可以使用 body() 方法设置请求体。
  2. 发送请求并处理响应:

    • 使用 exchange() 方法发送请求并获取响应。
    • 使用 retrieve() 方法获取响应体,可以通过 toEntity()toFlux()toMono() 等方法将响应转换为不同的数据类型。
    • 可以使用 bodyToXxx() 方法将响应体转换为指定的数据类型,如 bodyToMono()bodyToFlux()bodyToEntity() 等。
  3. 异常处理:

    • 使用 onStatus() 方法可以处理特定的 HTTP 状态码,并进行相应的处理逻辑。
    • 可以使用 onError() 方法处理请求过程中的错误情况。
    • 通过 onStatus()onError() 等方法可以使用操作符链式组合多个异常处理逻辑。
  4. 响应处理:

    • 使用操作符(如 map()flatMap()filter() 等)对响应进行处理和转换。
    • 可以使用操作符进行响应流的合并、转换、过滤、排序等操作。
    • 可以使用 doOnXxx() 方法对响应进行副作用操作,如记录日志、统计信息等。
  5. 超时和重试:

    • 使用 timeout() 方法可以设置请求的超时时间。
    • 可以使用 retry() 方法进行请求重试,可以自定义重试条件和重试策略。
  6. 文件上传和下载:

    • 使用 multipart() 方法可以进行文件上传,支持单个文件或多个文件的上传。
    • 可以使用 body(BodyInserters.fromResource()) 方法下载文件,将响应体保存为文件。
  7. 请求拦截器和过滤器:

    • 可以通过 filter() 方法添加请求拦截器,对请求进行预处理或修改请求参数。
    • 通过自定义 ExchangeFilterFunction 可以添加全局的请求过滤器,对所有请求进行统一的处理。
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
​
public class GlobalRequestFilterExample {
​
    public static void main(String[] args) {
        WebClient webClient = WebClient.builder()
                .baseUrl("https://api.example.com")
                .filter(globalRequestFilter())
                .build();
​
        webClient.get()
                .uri("/users")
                .retrieve()
                .toEntity(String.class)
                .subscribe(response -> {
                    HttpStatus statusCode = response.getStatusCode();
                    HttpHeaders headers = response.getHeaders();
                    String body = response.getBody();
                    System.out.println("Status: " + statusCode);
                    System.out.println("Headers: " + headers);
                    System.out.println("Body: " + body);
                });
    }
​
    private static ExchangeFilterFunction globalRequestFilter() {
        return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
            ClientRequest.Builder builder = ClientRequest.from(clientRequest);
            // 添加全局请求头
            builder.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
            // 修改请求方法
            builder.method(HttpMethod.GET);
​
            // 返回修改后的请求
            return Mono.just(builder.build());
        });
    }
}
​
复制代码

数据转换和编解码:WebClient 支持数据转换,可以将请求和响应体转换为各种数据类型,如 JSON、XML 等。它使用 Jackson、Gson、XML 编解码器等来实现数据的序列化和反序列化。

  1. 响应体的数据类型转换:

    • 使用 toEntity() 方法将响应体转换为 ResponseEntity 对象,包含了响应的状态码、响应头和响应体。
    • 可以使用 toFlux() 方法将响应体转换为 Flux 对象,用于处理响应体为多个元素的情况。
    • 可以使用 toMono() 方法将响应体转换为 Mono 对象,用于处理响应体为单个元素的情况。
  2. 响应体的数据格式转换:

    • WebClient 支持将响应体从 JSON、XML、HTML、文本等格式转换为对象或字符串。
    • 可以使用 bodyToXxx() 方法将响应体转换为指定的数据类型,如 bodyToMono()bodyToFlux()bodyToEntity() 等。
    • WebClient 默认集成了 JSON 和 XML 的数据转换器,可以将响应体直接转换为 Java 对象。
  3. 请求体的数据类型转换:

    • 使用 bodyValue() 方法可以设置请求体的数据,WebClient 会根据请求的 Content-Type 进行适当的编码。
    • 可以使用 body() 方法指定请求体的数据类型,WebClient 会根据数据类型进行编码,如 JSON、XML、表单等。
    • 可以使用 body(BodyInserters.fromXxx()) 方法将 Java 对象转换为请求体的数据,如 body(BodyInserters.fromValue())body(BodyInserters.fromFormData()) 等。
  4. 数据编解码器的自定义:

    • 可以通过自定义 EncoderDecoder 实现数据的自定义编解码。
    • WebClient 提供了 codecs() 方法来配置自定义的编解码器,可以设置 JSON、XML、表单等数据格式的编解码器。
import org.springframework.core.ResolvableType;
import org.springframework.http.MediaType;
import org.springframework.http.codec.json.AbstractJackson2Decoder;
import org.springframework.http.codec.json.AbstractJackson2Encoder;
import org.springframework.http.codec.json.Jackson2CodecSupport;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
​
public class CustomCodecExample {
​
    public static void main(String[] args) {
        WebClient webClient = WebClient.builder()
                .exchangeStrategies(customExchangeStrategies())
                .build();
​
        webClient.get()
                .uri("https://api.example.com/users")
                .accept(MediaType.APPLICATION_JSON)
                .retrieve()
                .bodyToMono(User.class)
                .subscribe(user -> {
                    System.out.println("User: " + user);
                });
    }
​
    static class User {
        private String name;
        private int age;
​
        // 构造方法、getter 和 setter 省略
​
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + ''' +
                    ", age=" + age +
                    '}';
        }
    }
​
    private static ExchangeStrategies customExchangeStrategies() {
        return ExchangeStrategies.builder()
                .codecs(configurer -> {
                    configurer.defaultCodecs().jackson2JsonEncoder(new CustomJsonEncoder());
                    configurer.defaultCodecs().jackson2JsonDecoder(new CustomJsonDecoder());
                })
                .build();
    }
​
    private static class CustomJsonEncoder extends AbstractJackson2Encoder {
​
        CustomJsonEncoder() {
            super(new Jackson2CodecSupport() {
                @Override
                public void setSerializedType(ResolvableType type) {
                    // 自定义序列化的类型
                    super.setSerializedType(ResolvableType.forClass(User.class));
                }
            }, MediaType.APPLICATION_JSON);
        }
    }
​
    private static class CustomJsonDecoder extends AbstractJackson2Decoder {
​
        CustomJsonDecoder() {
            super(new Jackson2CodecSupport() {
                @Override
                public void setSerializedType(ResolvableType type) {
                    // 自定义反序列化的类型
                    super.setSerializedType(ResolvableType.forClass(User.class));
                }
            }, MediaType.APPLICATION_JSON);
        }
    }
}
复制代码

连接池管理:WebClient 内置了连接池管理,可以重用连接,减少资源消耗和连接建立的时间。

  • WebClient 在默认情况下使用一个共享的连接池,这意味着多个 WebClient 实例将共享同一个连接池。这样可以减少资源的占用,并提高性能。
  • WebClient 默认会重用连接,如果一个请求完成后仍然有其他请求需要发起,WebClient 会尝试重用现有的连接,而不是关闭它。
  • 连接池管理还涉及连接的保持时间。当连接在一段时间内没有被使用时,连接可能会被关闭或释放。
  • 当请求完成后,连接可以被释放回连接池以供其他请求使用。默认情况下,WebClient 会自动释放连接。
import org.springframework.boot.web.reactive.function.client.WebClientCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient;
​
@Configuration
public class WebClientConfig {
​
    @Bean
    public WebClient webClient(WebClient.Builder builder) {
        // 设置连接池最大连接数和每个主机的最大连接数
        builder.clientConnector(new ReactorClientHttpConnector(HttpClient.create().wiretap(true)
                .tcpConfiguration(tcpClient -> tcpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
                        .doOnConnected(conn -> conn
                                .addHandlerLast(new ReadTimeoutHandler(5))
                                .addHandlerLast(new WriteTimeoutHandler(5))))).build();
        
        // 设置连接的保持时间
        builder.keepAlive(Duration.ofMinutes(5));
        
        // 可以根据需要进行其他的配置,如数据转换器、错误处理等
        builder.exchangeStrategies(ExchangeStrategies.builder()
                .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(16 * 1024 * 1024))
                .build());
        
        return builder.build();
    }
​
    @Bean
    public WebClientCustomizer webClientCustomizer() {
        return webClientBuilder -> {
            // 可以在这里进行全局的 WebClient 配置
            webClientBuilder.baseUrl("https://api.test.com");
            // ...
        };
    }
}
​
复制代码

错误处理:WebClient 提供了错误处理机制,可以处理请求失败、超时、错误状态码等情况,并支持自定义错误处理逻辑。

  1. 异常处理:

    • 使用 onStatus() 方法可以指定处理特定的 HTTP 状态码,如 onStatus(HttpStatus::isError, errorHandler) 可以处理错误状态码。
    • 使用 onError() 方法可以指定处理请求过程中的异常情况,如网络连接失败、连接超时等。
  2. 错误处理器:

    • 使用 exchange() 方法可以获取完整的响应信息,包括状态码、响应头和响应体。
    • 使用 bodyToMono()bodyToFlux() 方法将响应体转换为相应的 MonoFlux 对象。
    • 可以使用 onErrorResume() 方法设置自定义的错误处理逻辑,返回一个备用的 MonoFlux 对象。
  3. 超时处理:

    • 使用 timeout() 方法可以设置请求的超时时间,当请求超过指定时间仍未完成时,会触发超时异常。
    • 使用 retry() 方法可以进行请求的重试,可以设置最大重试次数和重试条件,用于处理请求失败的情况。
  4. 错误信号转换:

    • 使用 onErrorMap() 方法可以将错误信号转换为不同的异常类型,以便在错误处理时进行更精细的判断和处理。
    • 使用 onErrorResume() 方法可以根据不同的错误类型返回不同的备用数据,用于灵活处理不同类型的错误情况。
import org.springframework.http.HttpStatus;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
​
public class ErrorHandlingExample {
​
    public static void main(String[] args) {
        WebClient webClient = WebClient.create();
​
        webClient.get()
                .uri("https://api.example.com/users/123")
                .retrieve()
                .onStatus(HttpStatus::isError, response -> {
                    if (response.statusCode() == HttpStatus.NOT_FOUND) {
                        return Mono.error(new UserNotFoundException("User not found"));
                    } else {
                        return Mono.error(new RuntimeException("Request failed with status code: " + response.statusCode()));
                    }
                })
                .bodyToMono(User.class)
                .doOnError(UserNotFoundException.class, ex -> {
                    System.out.println("User not found: " + ex.getMessage());
                })
                .doOnError(RuntimeException.class, ex -> {
                    System.out.println("Request failed: " + ex.getMessage());
                })
                .subscribe(user -> {
                    System.out.println("User: " + user);
                });
    }
​
    static class User {
        private String name;
        private int age;
​
        // 构造方法、getter 和 setter 省略
​
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + ''' +
                    ", age=" + age +
                    '}';
        }
    }
​
    static class UserNotFoundException extends RuntimeException {
        public UserNotFoundException(String message) {
            super(message);
        }
    }
}
​
复制代码

可扩展性和定制性:WebClient 可以通过配置不同的 ExchangeStrategies、过滤器、拦截器等来进行定制和扩展,以满足不同的需求。

配置过滤

import org.springframework.http.HttpStatus;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
​
public class ErrorHandlingExample {
​
    public static void main(String[] args) {
        WebClient webClient = WebClient.builder()
                .baseUrl("https://api.example.com")
                .filter(new ErrorHandlingFilter())
                .build();
​
        webClient.get()
                .uri("/users/123")
                .retrieve()
                .bodyToMono(User.class)
                .subscribe(user -> {
                    System.out.println("User: " + user);
                });
    }
​
    static class User {
        private String name;
        private int age;
​
        // 构造方法、getter 和 setter 省略
​
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + ''' +
                    ", age=" + age +
                    '}';
        }
    }
​
    static class ErrorHandlingFilter implements ExchangeFilterFunction {
​
        @Override
        public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
            return next.exchange(request)
                    .flatMap(response -> {
                        if (response.statusCode().isError()) {
                            if (response.statusCode() == HttpStatus.NOT_FOUND) {
                                return Mono.error(new UserNotFoundException("User not found"));
                            } else {
                                return Mono.error(new RuntimeException("Request failed with status code: " + response.statusCode()));
                            }
                        }
                        return Mono.just(response);
                    });
        }
    }
​
    static class UserNotFoundException extends RuntimeException {
        public UserNotFoundException(String message) {
            super(message);
        }
    }
}
​
复制代码

实战:

添加所需的依赖到 pom.xml 文件中:

<dependencies>
    <!-- Spring Boot Webflux -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
​
    <!-- Spring Boot Starter Validation -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
​
    <!-- Jackson JSON Processor -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>
</dependencies>
复制代码

注意:

Spring WebFlux中的WebClient,它的底层实现可以使用不同的HTTP客户端,包括Apache HttpClient和OkHttp。

显式地使用Apache HttpClient作为WebClient的底层实现,可以通过添加spring-webflux-httpclient依赖来实现,同时需要排除默认的Reactor Netty依赖。

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<exclusions>
   <exclusion>
       <groupId>io.projectreactor.netty</groupId>
       <artifactId>reactor-netty-http</artifactId>
   </exclusion>
</exclusions>
</dependency><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<exclusions>
   <exclusion>
       <groupId>io.projectreactor.netty</groupId>
       <artifactId>reactor-netty</artifactId>
   </exclusion>
</exclusions>
</dependency><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<exclusions>
   <exclusion>
       <groupId>io.netty</groupId>
       <artifactId>netty-transport-native-epoll</artifactId>
   </exclusion>
   <exclusion>
       <groupId>io.netty</groupId>
       <artifactId>netty-transport-native-unix-common</artifactId>
   </exclusion>
</exclusions>
</dependency><dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>复制代码

WebClient 配置为 @Configuration 类:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ClientCodecConfigurer;
import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient;
​
@Configuration
public class WebClientConfig {
​
    @Bean
    public WebClient webClient() {
        ExchangeStrategies strategies = ExchangeStrategies.builder()
                .codecs(this::customCodecs)
                .build();
​
        return WebClient.builder()
                .exchangeStrategies(strategies)
                .build();
    }
​
    private void customCodecs(ClientCodecConfigurer configurer) {
        configurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder());
        configurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder());
        configurer.defaultCodecs().maxInMemorySize(1024 * 1024);
        configurer.defaultCodecs().defaultContentType(MediaType.APPLICATION_JSON);
    }
}
​
复制代码

创建一个 UserController 类,用于定义 REST API 的处理器方法:

import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;
​
@RestController
@RequestMapping("/users")
@Validated
public class UserController {
​
    private final UserService userService;
​
    public UserController(UserService userService) {
        this.userService = userService;
    }
​
    @GetMapping("/{id}")
    public Mono<User> getUserById(@PathVariable String id) {
        return userService.getUserById(id);
    }
​
    @PostMapping
    public Mono<User> createUser(@RequestBody @Validated CreateUserRequest request) {
        return userService.createUser(request);
    }
}
​
复制代码

创建一个 UserService 类,用于处理用户相关的业务逻辑:

import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
​
@Service
public class UserService {
​
    public Mono<User> getUserById(String id) {
        // Simulate fetching user data from external service
        WebClient webClient = WebClient.create();
        return webClient.get()
                .uri("https://api.example.com/users/{id}", id)
                .retrieve()
                .bodyToMono(User.class);
    }
​
    public Mono<User> createUser(CreateUserRequest request) {
        // Simulate creating a user by sending a POST request
        WebClient webClient = WebClient.create();
        return webClient.post()
                .uri("https://api.example.com/users")
                .contentType(MediaType.APPLICATION_JSON)
                .bodyValue(request)
                .retrieve()
                .bodyToMono(User.class);
    }
}
​
复制代码

创建一个 User 类,作为数据模型:

public class User {
    private String id;
    private String name;
    private int age;
​
    // 构造方法、getter 和 setter 省略
​
    @Override
    public String toString() {
        return "User{" +
                "id='" + id + ''' +
                ", name='" + name + ''' +
                ", age=" + age +
                '}';
    }
}
​
复制代码

创建一个 CreateUserRequest 类,用于接收创建用户的请求:

import javax.validation.constraints.NotEmpty;
​
public class CreateUserRequest {
    @NotEmpty(message = "Name cannot be empty")
    private String name;
    private int age;
​
    // 构造方法、getter 和 setter 省略
​
    @Override
    public String toString() {
        return "CreateUserRequest{" +
                "name='" + name + ''' +
                ", age=" + age +
                '}';
    }
}
​
复制代码

RestTemplate

RestTemplate 是 Spring 3.x 版本引入的,并在 Spring 5.x 版本中仍然可用。它的设计受到了传统的 Spring MVC 的影响,并提供了许多方便的方法和功能。

RestTemplate 是一个同步的、阻塞式的 HTTP 客户端,它主要以同步方式发送和接收 HTTP 请求。在默认情况下,RestTemplate 的方法会阻塞当前线程,直到收到响应或发生超时。

RestTemplate 的主要特点和功能如下:

发送 HTTP 请求:RestTemplate 提供了多种方法,例如 GET、POST、PUT、DELETE 等,用于发送不同类型的 HTTP 请求。

getForObject(): 发送 GET 请求,并将响应体转换为指定类型的对象。

User user = restTemplate.getForObject("http://example.com/api/user/{id}", User.class, 1);
复制代码

getForEntity(): 发送 GET 请求,并将响应体转换为 ResponseEntity 对象,包含响应的状态码、头部信息和响应体。

ResponseEntity<User> response = restTemplate.getForEntity("http://example.com/api/user/{id}", User.class, 1);
User user = response.getBody();
HttpStatus status = response.getStatusCode();
复制代码

postForObject(): 发送 POST 请求,并将请求体转换为指定类型的对象。可以传递 URL 参数作为变量。

User user = restTemplate.postForObject("http://example.com/api/user", newUser, User.class);
复制代码

postForEntity(): 发送 POST 请求,并将请求体转换为 ResponseEntity 对象,包含响应的状态码、头部信息和响应体。

ResponseEntity<User> response = restTemplate.postForEntity("http://example.com/api/user", newUser, User.class);
User createdUser = response.getBody();
HttpStatus status = response.getStatusCode();
复制代码

put(): 发送 PUT 请求,用于更新资源。可以传递 URL 参数作为变量。

restTemplate.put("http://example.com/api/user/{id}", updatedUser, 1);
复制代码

delete(): 发送 DELETE 请求,用于删除资源。可以传递 URL 参数作为变量。

restTemplate.delete("http://example.com/api/user/{id}", 1);
复制代码

exchange() 方法是 RestTemplate 提供的灵活且强大的方法,它可以发送任意类型的 HTTP 请求,并且可以自定义请求和响应的处理。

RestTemplate restTemplate = new RestTemplate();
String username = "username";
String password = "password";
String credentials = username + ":" + password;
String encodedCredentials = Base64.getEncoder().encodeToString(credentials.getBytes());

HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Basic " + encodedCredentials);
HttpEntity<String> requestEntity = new HttpEntity<>(headers);

ResponseEntity<User> response = restTemplate.exchange("http://example.com/api/user/{id}", HttpMethod.GET, requestEntity, User.class, 1);


Resource fileResource = new FileSystemResource("/path/to/file.jpg");
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("file", fileResource);

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);

ResponseEntity<String> response = restTemplate.exchange("http://example.com/api/upload", HttpMethod.POST, requestEntity, String.class);
复制代码

请求和响应处理:RestTemplate 支持将请求参数设置为 URL 查询参数、表单参数或 JSON 请求体。它还能够将响应转换为对象或者是原始的 HTTP 响应数据。

请求参数的设置:

  • 将参数作为 URL 查询参数:可以通过将参数拼接到 URL 中来设置查询参数。例如:http://example.com/api/user?id=123.
  • 将参数作为表单参数:可以使用 MultiValueMapMap 对象作为请求体中的表单参数。例如:
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
formData.add("username", "john");
formData.add("password", "secret");
复制代码
  • 将参数作为 JSON 请求体:可以将对象转换为 JSON 字符串,并将其作为请求体发送。例如:
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
User user = new User("john", "secret");
HttpEntity<User> requestEntity = new HttpEntity<>(user, headers);
复制代码

请求体的处理:

  • 设置请求体的类型和编码:可以通过设置请求头的 Content-Type 来指定请求体的类型和编码。例如:headers.setContentType(MediaType.APPLICATION_JSON).
  • 将请求体转换为 JSON 字符串:可以使用 JSON 库将对象转换为 JSON 字符串,并将其作为请求体发送。

响应的处理:

  • 将响应转换为对象:可以使用 exchange()getForObject() 方法将响应转换为指定的对象类型。例如:User user = restTemplate.getForObject("http://example.com/api/user/{id}", User.class, 1).
  • 获取原始的 HTTP 响应数据:可以使用 exchange() 方法获取包含响应状态码、响应头和响应体的 ResponseEntity 对象。例如:
ResponseEntity<String> response = restTemplate.getForEntity("http://example.com/api/user/{id}", String.class, 1);
String responseBody = response.getBody();
复制代码

错误处理:RestTemplate 可以处理请求过程中发生的错误,包括网络异常、HTTP 状态码错误等。可以根据需要自定义错误处理逻辑。

抛出异常:

try {
    ResponseEntity<String> response = restTemplate.getForEntity("http://example.com/api/resource", String.class);
    // 处理成功响应
} catch (RestClientException ex) {
    // 处理异常情况
    if (ex instanceof HttpClientErrorException) {
        HttpClientErrorException httpClientErrorException = (HttpClientErrorException) ex;
        HttpStatus statusCode = httpClientErrorException.getStatusCode();
        // 处理特定的 HTTP 状态码错误
    } else if (ex instanceof HttpServerErrorException) {
        HttpServerErrorException httpServerErrorException = (HttpServerErrorException) ex;
        HttpStatus statusCode = httpServerErrorException.getStatusCode();
        // 处理服务器端错误
    } else if (ex instanceof ResourceAccessException) {
        ResourceAccessException resourceAccessException = (ResourceAccessException) ex;
        // 处理网络异常等其他错误
    } else {
        // 处理其他类型的异常
    }
}
复制代码

自定义异常处理器:

public class CustomRestTemplate extends RestTemplate {
    public CustomRestTemplate() {
        setErrorHandler(new CustomResponseErrorHandler());
    }
}
​
public class CustomResponseErrorHandler extends DefaultResponseErrorHandler {
    @Override
    protected void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException {
        if (statusCode.is4xxClientError()) {
            // 处理客户端错误
        } else if (statusCode.is5xxServerError()) {
            // 处理服务器端错误
        } else {
            // 处理其他类型的错误
        }
    }
}
​
复制代码

自定义了一个 CustomRestTemplate 类,继承自 RestTemplate,并在构造函数中设置了自定义的异常处理器 CustomResponseErrorHandler。在自定义异常处理器中,我们可以根据不同的 HTTP 状态码来处理对应的错误情况。

URI 变量替换:RestTemplate 支持将 URI 中的占位符替换为实际的值,以便动态构建请求 URL。

RestTemplate restTemplate = new RestTemplate();
​
// 定义 URI 模板,其中 {id} 是占位符
String uriTemplate = "http://example.com/api/resource/{id}";
​
// 创建 URI 变量,设置实际的值
Map<String, String> uriVariables = new HashMap<>();
uriVariables.put("id", "123");
​
// 使用 URI.expand() 方法进行 URI 变量替换
URI uri = UriComponentsBuilder.fromUriString(uriTemplate)
        .buildAndExpand(uriVariables)
        .toUri();
​
// 发送 GET 请求
ResponseEntity<String> response = restTemplate.getForEntity(uri, String.class);复制代码

拦截器和过滤器:RestTemplate 允许注册请求和响应的拦截器和过滤器,用于在发送请求前和处理响应时进行额外的处理。

拦截器(Interceptor)是一种针对请求和响应的拦截处理机制,可以在发送请求前和处理响应时执行预定义的逻辑。你可以实现 ClientHttpRequestInterceptor 接口,并将其注册到 RestTemplate 中的 interceptors 集合中,以便在请求发送之前和响应处理之后进行拦截。

RestTemplate restTemplate = new RestTemplate();
​
restTemplate.setInterceptors(List.of(new LoggingClientHttpRequestInterceptor()));
​
​
public class LoggingClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
​
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
            throws IOException {
        // 在发送请求前的拦截处理
        logRequest(request, body);
​
        // 继续执行请求
        ClientHttpResponse response = execution.execute(request, body);
​
        // 在处理响应后的拦截处理
        logResponse(response);
​
        return response;
    }
​
    private void logRequest(HttpRequest request, byte[] body) {
        // 记录请求日志的逻辑
        System.out.println("Sending request: " + request.getMethod() + " " + request.getURI());
        System.out.println("Request body: " + new String(body, StandardCharsets.UTF_8));
    }
​
    private void logResponse(ClientHttpResponse response) throws IOException {
        // 记录响应日志的逻辑
        System.out.println("Received response: " + response.getStatusCode());
        System.out.println("Response body: " + StreamUtils.copyToString(response.getBody(), StandardCharsets.UTF_8));
    }
}
​
复制代码

过滤器(Filter)是一种更细粒度的请求处理机制,它可以在请求的各个阶段添加自定义逻辑。

可以通过使用 ClientHttpRequestFactory 来注册过滤器。下面是一个示例,展示如何实现一个简单的过滤器来添加请求头信息:

public class HeaderFilter extends OncePerRequestFilter {
​
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        // 添加自定义的请求头
        request.addHeader("X-Request-Id", UUID.randomUUID().toString());
​
        // 继续执行过滤器链
        filterChain.doFilter(request, response);
    }
}
​
复制代码

创建了一个名为 HeaderFilter 的过滤器类,继承了 Spring Framework 提供的 OncePerRequestFilter 抽象类。在 doFilterInternal() 方法中,我们添加了一个自定义的请求头,将一个随机生成的请求 ID 添加到请求中。

需要创建一个自定义的 ClientHttpRequestFactory,并在其中注册过滤器。下面是一个示例:

public class CustomHttpRequestFactory extends SimpleClientHttpRequestFactory {
​
    private final List<Filter> filters;
​
    public CustomHttpRequestFactory(List<Filter> filters) {
        this.filters = filters;
    }
​
    @Override
    protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
        if (filters != null && !filters.isEmpty()) {
            for (Filter filter : filters) {
                if (filter instanceof OncePerRequestFilter) {
                    OncePerRequestFilter oncePerRequestFilter = (OncePerRequestFilter) filter;
                    oncePerRequestFilter.doFilter(null, null, null);
                }
            }
        }
        super.prepareConnection(connection, httpMethod);
    }
}
​
复制代码

创建了一个名为 CustomHttpRequestFactory 的自定义请求工厂类,继承了 SimpleClientHttpRequestFactory。在 prepareConnection() 方法中,我们遍历注册的过滤器列表,并调用过滤器的 doFilter() 方法进行处理。

使用这个自定义请求工厂,可以在创建 RestTemplate 实例时进行设置,示例如下:

List<Filter> filters = List.of(new HeaderFilter());
RestTemplate restTemplate = new RestTemplate(new CustomHttpRequestFactory(filters));复制代码

引申一下基于 spring 的过滤

使用 javax.servlet.Filter 接口来创建过滤器:

@WebFilter(urlPatterns = "/*") //通过注解的方式
public class HeaderFilter implements Filter {
​
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
       throws IOException, ServletException {
   HttpServletRequest httpRequest = (HttpServletRequest) request;
   HttpServletResponse httpResponse = (HttpServletResponse) response;
​
   // 添加自定义的请求头
   httpRequest.addHeader("X-Request-Id", UUID.randomUUID().toString());
​
   // 继续执行过滤器链
   chain.doFilter(httpRequest, httpResponse);
}
​
// 其他方法...
}
​
复制代码

web.xml 配置

<web-app>
<!-- 其他配置... --><filter>
   <filter-name>headerFilter</filter-name>
   <filter-class>com.example.HeaderFilter</filter-class>
</filter><filter-mapping>
   <filter-name>headerFilter</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping><!-- 其他配置... -->
</web-app>复制代码

实战:

使用 RestTemplate 进行 GET 请求,并添加自定义的请求头、处理响应结果以及错误处理:

@Configuration
public class RestTemplateConfig {
​
    @Bean
    public RestTemplate restTemplate() {
        RestTemplate restTemplate = new RestTemplate();
​
        // 添加自定义的请求拦截器
        restTemplate.getInterceptors().add(new CustomHeaderInterceptor());
​
        // 设置连接超时时间和读取超时时间
        restTemplate.setRequestFactory(requestFactory());
​
        // 设置错误处理器
        restTemplate.setErrorHandler(new CustomResponseErrorHandler());
​
        // 设置消息转换器,例如处理 JSON 数据
        restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
​
        return restTemplate;
    }
​
    @Bean
    public HttpComponentsClientHttpRequestFactory requestFactory() {
        HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
        requestFactory.setConnectTimeout(5000); // 设置连接超时时间为5秒
        requestFactory.setReadTimeout(5000); // 设置读取超时时间为5秒
        return requestFactory;
    }
}
​
@Component
public class CustomHeaderInterceptor implements ClientHttpRequestInterceptor {
​
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
            throws IOException {
        HttpHeaders headers = request.getHeaders();
        headers.add("X-Custom-Header", "Custom Value");
​
        return execution.execute(request, body);
    }
}
​
@Component
public class CustomResponseErrorHandler extends DefaultResponseErrorHandler {
​
    @Override
    public void handleError(ClientHttpResponse response) throws IOException {
        // 获取错误的 HTTP 状态码
        HttpStatus statusCode = response.getStatusCode();
​
        if (statusCode.is4xxClientError()) {
            // 处理客户端错误,例如 4xx 状态码
            if (statusCode == HttpStatus.NOT_FOUND) {
                throw new ResourceNotFoundException("Requested resource not found");
            } else if (statusCode == HttpStatus.UNAUTHORIZED) {
                throw new UnauthorizedException("Unauthorized access");
            } else {
                throw new RuntimeException("Client error occurred");
            }
        } else if (statusCode.is5xxServerError()) {
            // 处理服务器错误,例如 5xx 状态码
            if (statusCode == HttpStatus.INTERNAL_SERVER_ERROR) {
                throw new ServerErrorException("Internal server error");
            } else if (statusCode == HttpStatus.SERVICE_UNAVAILABLE) {
                throw new ServiceUnavailableException("Service unavailable");
            } else {
                throw new RuntimeException("Server error occurred");
            }
        } else {
            // 其他错误处理
            super.handleError(response);
        }
    }
}
​
@Service
public class ApiService {
​
    private final RestTemplate restTemplate;
​
    public ApiService(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }
​
    public String fetchData(String url) {
        try {
            ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, null, String.class);
            if (response.getStatusCode().is2xxSuccessful()) {
                return response.getBody();
            } else {
                throw new RuntimeException("Request failed with status code: " + response.getStatusCodeValue());
            }
        } catch (RestClientException ex) {
            // 处理异常情况
            throw new RuntimeException("Request failed: " + ex.getMessage(), ex);
        }
    }
}
​
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
        ApiService apiService = context.getBean(ApiService.class);
        String data = apiService.fetchData("http://example.com/api/resource");
        System.out.println("Response data: " + data);
    }
}
​
复制代码

使用Apache HttpClient作为RestTemplate的底层实现:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency><dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>复制代码
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
​
@Configuration
@EnableConfigurationProperties
public class CustomRestTemplateConfig {
@Autowired(required = false)
private ClientHttpRequestFactory requestFactory;
​
@Bean
@ConditionalOnMissingBean
public RestTemplate restTemplate() {
   return new RestTemplate(getRequestFactory());
}
​
private ClientHttpRequestFactory getRequestFactory() {
   if (this.requestFactory != null) {
       return this.requestFactory;
   } else {
       return new HttpComponentsClientHttpRequestFactory(httpClient());
   }
}
​
@Bean
@ConditionalOnMissingBean
public HttpClient httpClient() {
   return HttpClientBuilder.create().build();
}
}
​
@SpringBootApplication(exclude = {RestTemplateAutoConfiguration.class})
public class MyApplication {
// ...
}
​
​
复制代码

Feign

Feign是一个声明式的HTTP客户端库,它简化了与RESTful服务进行交互的过程。

声明式API:Feign允许使用注解方式定义HTTP请求的接口,而无需编写具体的实现代码。通过编写接口,可以声明请求的URL、HTTP方法、请求参数等信息。

Feign提供了一系列注解来帮助定义请求接口,其中常用的注解包括:

  1. @FeignClient:用于标识一个Feign客户端,指定服务的名称或URL。
  2. @RequestMapping:类似于Spring MVC中的注解,用于指定请求的URL路径和HTTP方法。
  3. @PathVariable:用于将URL路径中的变量绑定到方法参数。
  4. @RequestParam:用于将请求参数绑定到方法参数。
  5. @RequestBody:用于将请求体绑定到方法参数。
  6. @RequestHeader:用于将请求头信息绑定到方法参数。
@FeignClient(name = "example-service")
public interface ExampleFeignClient {

    @RequestMapping(value = "/api/resource/{id}", method = RequestMethod.GET)
    ResourceDTO getResource(@PathVariable("id") String id, @RequestParam("param") String param);

    @RequestMapping(value = "/api/resource", method = RequestMethod.POST)
    void createResource(@RequestBody ResourceDTO resource);

    @RequestMapping(value = "/api/resource/{id}", method = RequestMethod.PUT)
    void updateResource(@PathVariable("id") String id, @RequestBody ResourceDTO resource);

    @RequestMapping(value = "/api/resource/{id}", method = RequestMethod.DELETE)
    void deleteResource(@PathVariable("id") String id);
}
复制代码

使用Feign声明式API时,还需要在Spring Boot应用程序的配置文件中启用Feign客户端。可以通过添加@EnableFeignClients注解或feign.enable=true的配置来实现。

内部集成了Ribbon和Hystrix:Feign内部集成了Netflix Ribbon和Netflix Hystrix,可以提供负载均衡和容错功能。Feign可以通过服务名称自动解析服务实例,并实现请求的负载均衡。

  1. Ribbon负载均衡:Feign集成了Ribbon,可以通过服务名称自动解析服务实例,并实现请求的负载均衡。只需要在Feign客户端接口上使用@FeignClient注解指定服务名称,Feign就能够根据服务名称从服务注册中心(如Eureka)获取服务实例列表,并根据负载均衡策略选择一个实例发送请求。

  2. Hystrix容错:Feign还集成了Hystrix,可以提供容错功能,防止远程调用的失败影响整个系统。Hystrix可以对远程调用进行熔断、降级和限流等操作,以保护系统的稳定性。在使用Feign时,可以通过配置feign.hystrix.enabled=true启用Hystrix支持,并在Feign客户端接口的方法上添加@HystrixCommand注解来定义容错逻辑。

    • 熔断(Circuit Breaker)是Hystrix的核心功能之一,它可以在调用失败或达到一定的错误阈值时,打开断路器,阻止继续向该服务发起请求,从而快速失败,避免资源的浪费和雪崩效应。当断路器打开后,可以定义一个降级逻辑,返回一个预先设定的默认值或错误信息,以提供给调用方使用。
    • 降级(Fallback)是在熔断状态下触发的操作,用于替代实际调用的逻辑,返回一个备选的响应。降级逻辑可以是从缓存中获取数据、返回静态数据、调用本地方法等。通过降级处理,可以在服务不可用时提供一个有意义的响应,保证系统的可用性。
    • 限流(Rate Limiting)是指控制对服务的并发请求量或并发连接数,以防止系统过载。Hystrix提供了线程池隔离和信号量隔离两种限流模式,可以根据具体需求选择合适的限流策略。
<dependencies>
    <!-- Feign -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    
    <!-- Ribbon -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    </dependency>
    
    <!-- Hystrix -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
</dependencies>
复制代码

在Spring Boot的启动类上添加@EnableFeignClients注解,启用Feign客户端:

@SpringBootApplication
@EnableFeignClients
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}
复制代码

创建一个Feign客户端接口,使用@FeignClient注解指定服务名称,并使用@RibbonClient注解启用Ribbon负载均衡:

@FeignClient(name = "my-service")
@RibbonClient(name = "my-service")
public interface MyServiceClient {
    @GetMapping("/api/resource")
    String getResource();
}
复制代码

在配置文件中配置Ribbon的服务列表和Hystrix的相关配置:


# Ribbon配置
my-service:
  ribbon:
    listOfServers: example.com:8081, example.com:8082
    
# Hystrix配置
hystrix:
  command:
    default:
      execution.isolation.thread.timeoutInMilliseconds: 5000
复制代码

关于 Hystrix 请求超时需要注意的点:

  1. Feign请求超时:如果Feign请求设置的超时时间较长,而另一个项目使用HTTP请求的超时时间较短,那么在请求另一个项目时,Feign请求可能会等待较长时间才会超时。这可能导致Feign请求的响应时间较长,从而影响系统的性能和吞吐量。
  2. HTTP请求超时:如果Feign请求设置的超时时间较短,而另一个项目使用HTTP请求的超时时间较长,那么在请求另一个项目时,Feign请求可能会因为超时而中断,并触发熔断逻辑。此时,Feign可以根据熔断策略进行降级处理,例如返回默认值、调用备用接口等,以确保系统的可用性。

自动化的请求和响应转换:Feign通过集成Jackson等序列化库,自动处理请求和响应的数据转换。可以通过注解指定请求和响应的数据格式,例如JSON、XML等。

Feign会根据注解中指定的数据格式,自动将请求和响应的数据进行序列化和反序列化。默认情况下,Feign使用JSON作为数据的格式,但也可以通过其他注解(如@Produces@Consumes)来指定其他的数据格式,例如XML。

@FeignClient(name = "example-service", url = "http://example.com")
public interface ExampleFeignClient {

    @PostMapping(value = "/api/resource", consumes = MediaType.APPLICATION_XML_VALUE,
            produces = MediaType.APPLICATION_XML_VALUE)
    ExampleResponseDto postResource(@RequestBody ExampleRequestDto request);
}

@XmlRootElement
public class ExampleRequestDto {
    // 请求数据的字段
}

@XmlRootElement
public class ExampleResponseDto {
    // 响应数据的字段
}

@RestController
public class ExampleController {

    @Autowired
    private ExampleFeignClient feignClient;

    @PostMapping("/call-example-service")
    public ExampleResponseDto callExampleService(@RequestBody ExampleRequestDto request) {
        return feignClient.postResource(request);
    }
}
复制代码

consumes属性指定了请求数据的格式为XML,produces属性指定了响应数据的格式为XML。

使用@XmlRootElement注解标记了请求和响应的数据对象,以便进行XML序列化和反序列化。

定制化和扩展性:Feign提供了丰富的扩展点和自定义配置选项,允许根据需要进行定制化。可以自定义请求拦截器、错误处理器、编码器、解码器等,以满足特定的业务需求。

  • 请求拦截器(Request Interceptors):可以实现RequestInterceptor接口,编写自定义的请求拦截器,用于在发送请求前进行额外的处理。例如,可以在请求头中添加认证信息、日志记录等。通过注册请求拦截器,可以在全局范围或特定的Feign客户端中应用自定义逻辑。
  • 响应拦截器(Response Interceptors):类似于请求拦截器,可以实现ResponseInterceptor接口,编写自定义的响应拦截器,用于在处理响应数据前进行额外的处理。例如,可以对响应进行统一的处理、错误处理等。
  • 错误处理器(Error Handlers):通过实现ErrorDecoder接口,可以自定义错误处理器来处理特定的错误情况。例如,可以根据不同的HTTP状态码或响应内容来处理异常,实现定制化的错误处理逻辑。
  • 编码器(Encoders)和解码器(Decoders):Feign支持自定义编码器和解码器,用于处理请求和响应数据的编码和解码。可以实现Encoder接口和Decoder接口,根据需求选择合适的序列化和反序列化方式。这样可以适应不同的数据格式,如JSON、XML等。

请求拦截器(Request Interceptor):

public class CustomRequestInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        // 在请求前进行额外处理,如添加请求头、认证信息等
        template.header("Authorization", "Bearer token");
    }
}
复制代码

响应拦截器(Response Interceptor):

public class CustomResponseInterceptor implements ResponseInterceptor {
    @Override
    public void apply(Response response) {
        // 在处理响应前进行额外处理,如统一处理响应数据、错误处理等
        if (response.status() == 404) {
            // 处理特定的HTTP状态码
            throw new NotFoundException("Resource not found");
        }
    }
}
复制代码

错误处理器(Error Decoder):

public class CustomErrorDecoder implements ErrorDecoder {
    @Override
    public Exception decode(String methodKey, Response response) {
        // 根据响应内容和状态码自定义异常处理逻辑
        if (response.status() == 500) {
            return new CustomServerException("Server Error");
        } else if (response.status() == 400) {
            return new CustomBadRequestException("Bad Request");
        }
        return new FeignException(response.status(), response.reason());
    }
}
复制代码

编码器(Encoder)和解码器(Decoder):

public class CustomJsonEncoder implements Encoder {
    @Override
    public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
        // 自定义JSON编码逻辑,将对象转换为JSON格式的请求体
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            String jsonBody = objectMapper.writeValueAsString(object);
            template.body(jsonBody, MediaType.APPLICATION_JSON_VALUE);
        } catch (JsonProcessingException e) {
            throw new EncodeException("Error encoding object to JSON", e);
        }
    }
}

public class CustomJsonDecoder implements Decoder {
    @Override
    public Object decode(Response response, Type type) throws DecodeException, FeignException {
        // 自定义JSON解码逻辑,将响应体转换为对象
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            return objectMapper.readValue(response.body().asInputStream(), objectMapper.constructType(type));
        } catch (IOException e) {
            throw new DecodeException("Error decoding JSON response", e);
        }
    }
}
复制代码
@Configuration
public class FeignConfig {
    
    @Bean
    public CustomRequestInterceptor customRequestInterceptor() {
        return new CustomRequestInterceptor();
    }
    
    
    @Bean
    public CustomResponseInterceptor customResponseInterceptor() {
        return new CustomResponseInterceptor();
    }
    
    
    @Bean
    public CustomErrorDecoder customErrorDecoder() {
        return new CustomErrorDecoder();
    }
    
    
     @Bean
    public CustomJsonEncoder customJsonEncoder() {
        return new CustomJsonEncoder();
    }
    
    @Bean
    public CustomJsonDecoder customJsonDecoder() {
        return new CustomJsonDecoder();
    }
    
}

@FeignClient(name = "example-service", configuration = FeignConfig.class)
public interface ExampleClient {
    // 接口方法定义
}
复制代码

整合Spring Cloud:Feign是Spring Cloud生态系统的一部分,可以与其他Spring Cloud组件无缝集成,例如Eureka、Zuul等。它可以与Spring Boot应用程序无缝配合使用,简化微服务架构中的服务间通信。

HTTP 客户端库

Apache HttpClient

强大的功能:Apache HttpClient支持HTTP/1.1协议的各种特性,包括连接管理、请求和响应拦截、重定向、认证、代理等。它可以执行各种类型的HTTP请求,如GET、POST、PUT、DELETE等,并支持多种数据格式的处理,如JSON、XML等。

import org.apache.http.HttpHost;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
​
@Configuration
public class HttpClientConfig {
​
    @Value("${http.maxTotalConnections}")
    private int maxTotalConnections;
​
    @Value("${http.defaultMaxPerRoute}")
    private int defaultMaxPerRoute;
​
    @Value("${http.connectionRequestTimeout}")
    private int connectionRequestTimeout;
​
    @Value("${http.connectTimeout}")
    private int connectTimeout;
​
    @Value("${http.socketTimeout}")
    private int socketTimeout;
​
    @Value("${http.proxyHost}")
    private String proxyHost;
​
    @Value("${http.proxyPort}")
    private int proxyPort;
​
    @Bean
    public CloseableHttpClient httpClient() {
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
        connectionManager.setMaxTotal(maxTotalConnections);
        connectionManager.setDefaultMaxPerRoute(defaultMaxPerRoute);
​
        HttpHost proxy = new HttpHost(proxyHost, proxyPort);
​
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectionRequestTimeout(connectionRequestTimeout)
                .setConnectTimeout(connectTimeout)
                .setSocketTimeout(socketTimeout)
                .setProxy(proxy)
                .build();
​
        CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        // 设置认证信息
​
        return HttpClientBuilder.create()
                .setConnectionManager(connectionManager)
                .setDefaultRequestConfig(requestConfig)
                .setDefaultCredentialsProvider(credentialsProvider)
                .build();
    }
}
​
复制代码

通过在HttpClient的配置中设置代理信息,可以将请求通过代理服务器发送出去,而不是直接使用本机的IP和端口。在示例代码中,使用了HttpHost来定义代理的主机和端口,并将其设置到RequestConfig中。这样配置后,HttpClient会将请求发送到指定的代理服务器,然后由代理服务器转发请求到目标服务器,实现了通过代理发送请求的功能。

高级配置选项:Apache HttpClient提供了丰富的配置选项,可以根据需要进行定制化。可以设置连接池的大小、超时时间、重试策略等。它还支持连接的复用和持久连接,以提高性能和效率。

import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class HttpClientConfig {

    @Bean
    public CloseableHttpClient httpClient() {
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
        connectionManager.setMaxTotal(100);
        connectionManager.setDefaultMaxPerRoute(20);

        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(5000)
                .setSocketTimeout(5000)
                .build();

        ConnectionKeepAliveStrategy keepAliveStrategy = (response, context) -> {
            HeaderElementIterator it = new BasicHeaderElementIterator(response.headerIterator(HTTP.CONN_KEEP_ALIVE));
            while (it.hasNext()) {
                HeaderElement he = it.nextElement();
                String param = he.getName();
                String value = he.getValue();
                if (value != null && param.equalsIgnoreCase("timeout")) {
                    return Long.parseLong(value) * 1000;
                }
            }
            return 30 * 1000;
        };

        return HttpClients.custom()
                .setConnectionManager(connectionManager)
                .setDefaultRequestConfig(requestConfig)
                .setKeepAliveStrategy(keepAliveStrategy)
                .build();
    }
}
复制代码

请求和响应处理:Apache HttpClient提供了灵活的请求和响应处理机制。可以通过设置请求头、请求参数、请求体等来定制请求。对于响应,可以获取响应的状态码、响应头、响应体等信息,并对其进行处理。

import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.*;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import java.io.IOException;

public class HttpClientExample {
    
    public static void main(String[] args) throws IOException {
        CloseableHttpClient httpClient = HttpClients.createDefault();

        // 设置请求头
        String token = "your-token";
        String authorizationHeader = "Bearer " + token;

        // 发起GET请求
        HttpGet httpGet = new HttpGet("http://example.com/api/resource");
        httpGet.setHeader(HttpHeaders.AUTHORIZATION, authorizationHeader);
        HttpResponse getResponse = httpClient.execute(httpGet);
        printResponse(getResponse);

        // 发起POST请求
        HttpPost httpPost = new HttpPost("http://example.com/api/resource");
        httpPost.setHeader(HttpHeaders.AUTHORIZATION, authorizationHeader);
        httpPost.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType());
        String requestBody = "{"key": "value"}";
        httpPost.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
        HttpResponse postResponse = httpClient.execute(httpPost);
        printResponse(postResponse);

        // 发起DELETE请求
        HttpDelete httpDelete = new HttpDelete("http://example.com/api/resource/123");
        httpDelete.setHeader(HttpHeaders.AUTHORIZATION, authorizationHeader);
        HttpResponse deleteResponse = httpClient.execute(httpDelete);
        printResponse(deleteResponse);

        // 发起PUT请求
        HttpPut httpPut = new HttpPut("http://example.com/api/resource/123");
        httpPut.setHeader(HttpHeaders.AUTHORIZATION, authorizationHeader);
        httpPut.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType());
        String requestBody = "{"key": "value"}";
        httpPut.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
        HttpResponse putResponse = httpClient.execute(httpPut);
        printResponse(putResponse);

        // 关闭HttpClient
        httpClient.close();
    }

    private static void printResponse(HttpResponse response) throws IOException {
        System.out.println("Status Code: " + response.getStatusLine().getStatusCode());
        System.out.println("Response Body: " + EntityUtils.toString(response.getEntity()));
        // 可以根据需要处理响应的其他信息
    }
}
复制代码

并发执行:Apache HttpClient支持并发执行多个HTTP请求,通过使用连接池和线程池来提高并发性能。可以同时发送多个请求,并异步地获取它们的响应。

Apache HttpClient提供的异步执行机制来实现并发执行多个请求。通过将请求提交到线程池中,可以并发地发送请求,并通过回调或Future对象来获取每个请求的响应结果。

CloseableHttpAsyncClient httpClient = HttpAsyncClients.createDefault();

// 创建多个HttpGet请求
HttpGet request1 = new HttpGet("http://api.example.com/resource1");
HttpGet request2 = new HttpGet("http://api.example.com/resource2");
HttpGet request3 = new HttpGet("http://api.example.com/resource3");

// 提交请求到线程池,并异步执行
Future<HttpResponse> future1 = httpClient.execute(request1, null);
Future<HttpResponse> future2 = httpClient.execute(request2, null);
Future<HttpResponse> future3 = httpClient.execute(request3, null);

// 获取每个请求的响应结果
HttpResponse response1 = future1.get();
HttpResponse response2 = future2.get();
HttpResponse response3 = future3.get();

// 处理响应结果
// ...

// 关闭HttpClient
httpClient.close();
复制代码

有时候我们遇到一个场景:

下一个请求需要使用到上一个请求返回类的参数,虽然我们可以通过分两次请求,但是这样写代码不好看,并且不清晰。我们可以通过使用异步请求和回调函数的方式来处理。

CloseableHttpAsyncClient httpClient = HttpAsyncClients.createDefault();

// 第一个请求
HttpGet request1 = new HttpGet("http://api.example.com/resource1");
Future<HttpResponse> future1 = httpClient.execute(request1, null);

// 处理第一个请求的响应结果
future1.thenApply(response1 -> {
// 解析第一个请求的响应结果
String result1 = parseResponse(response1);

// 第二个请求,将第一个请求的结果作为参数
HttpGet request2 = new HttpGet("http://api.example.com/resource2?param=" + result1);
Future<HttpResponse> future2 = httpClient.execute(request2, null);

// 处理第二个请求的响应结果
future2.thenApply(response2 -> {
   // 解析第二个请求的响应结果
   String result2 = parseResponse(response2);

   // 执行下一步操作,使用第二个请求的结果
   performNextStep(result2);

   return null;
});

return null;
});

// 等待所有请求执行完成
CompletableFuture.allOf(future1).join();

// 关闭HttpClient
httpClient.close();
复制代码

实战:

import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

//创建一个配置类 HttpClientConfig,用于配置 HttpClient 的相关参数:
@Configuration
public class HttpClientConfig {
    private static final int TIMEOUT = 5000; // 请求超时时间,单位:毫秒

    @Bean
    public CloseableHttpClient httpClient() {
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(TIMEOUT)
                .setConnectionRequestTimeout(TIMEOUT)
                .setSocketTimeout(TIMEOUT)
                .build();

        return HttpClients.custom()
                .setDefaultRequestConfig(requestConfig)
                .build();
    }
}

//创建一个使用 HttpClient 的服务类 HttpService,用于发起 HTTP 请求:
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class HttpService {
    private final CloseableHttpClient httpClient;

    @Autowired
    public HttpService(CloseableHttpClient httpClient) {
        this.httpClient = httpClient;
    }

    public String get(String url) throws Exception {
        HttpGet httpGet = new HttpGet(url);
        HttpResponse response = httpClient.execute(httpGet);
        HttpEntity entity = response.getEntity();
        return EntityUtils.toString(entity);
    }
}

//需要使用 HttpClient 的地方,注入 HttpService 并调用其方法即可:
    
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application implements CommandLineRunner {
    private final HttpService httpService;

    @Autowired
    public Application(HttpService httpService) {
        this.httpService = httpService;
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        String response = httpService.get("http://api.example.com/resource");
        System.out.println("Response: " + response);
    }
}
复制代码

OkHttp

简洁的 API:OkHttp 提供了易于使用的 API,使发送 HTTP 请求变得简单和直观。可以使用链式调用的方式设置请求的 URL、方法、请求头、请求体等信息。

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
​
public class OkHttpExample {
    public static void main(String[] args) throws Exception {
        OkHttpClient client = new OkHttpClient();
​
        Request request = new Request.Builder()
                .url("https://api.example.com/data")
                .get()
                .addHeader("Authorization", "Bearer token")
                .build();
​
        Response response = client.newCall(request).execute();
        String responseBody = response.body().string();
​
        System.out.println("Response: " + responseBody);
    }
}
​
//发送POST请求
OkHttpClient client = new OkHttpClient();
​
Request request = new Request.Builder()
        .url("https://api.example.com/users")
        .post(RequestBody.create(MediaType.parse("application/json"), requestBodyJson))
        .build();
​
Response response = client.newCall(request).execute();
​
​
//发送DELETE请求
OkHttpClient client = new OkHttpClient();
​
Request request = new Request.Builder()
        .url("https://api.example.com/users/123")
        .delete()
        .build();
​
Response response = client.newCall(request).execute();
​
​
//发送PUT请求:
OkHttpClient client = new OkHttpClient();
​
Request request = new Request.Builder()
        .url("https://api.example.com/users/123")
        .put(RequestBody.create(MediaType.parse("application/json"), requestBodyJson))
        .build();
​
Response response = client.newCall(request).execute();复制代码

高效性能:OkHttp 使用了连接池和复用连接的技术,可以有效地管理和重用连接,减少网络请求的延迟和资源消耗。它还支持异步请求和流式传输,以提高并发性能。

异步请求

OkHttpClient client = new OkHttpClient();
​
Request request = new Request.Builder()
        .url("https://api.example.com/data")
        .build();
​
client.newCall(request).enqueue(new Callback() {
    @Override
    public void onResponse(Call call, Response response) throws IOException {
        // 处理响应结果
        String responseBody = response.body().string();
        System.out.println("Response: " + responseBody);
    }
​
    @Override
    public void onFailure(Call call, IOException e) {
        // 处理请求失败
        e.printStackTrace();
    }
});
​
复制代码

同步请求:

OkHttpClient client = new OkHttpClient();
​
Request request = new Request.Builder()
        .url("https://api.example.com/data")
        .build();
​
try (Response response = client.newCall(request).execute()) {
    // 处理响应结果
    String responseBody = response.body().string();
    System.out.println("Response: " + responseBody);
} catch (IOException e) {
    // 处理请求失败
    e.printStackTrace();
}
​
复制代码

文件下载:

OkHttpClient client = new OkHttpClient();
​
Request request = new Request.Builder()
        .url("https://example.com/file.pdf")
        .build();
​
try (Response response = client.newCall(request).execute()) {
    // 将响应结果写入文件
    InputStream inputStream = response.body().byteStream();
    FileOutputStream outputStream = new FileOutputStream("downloaded_file.pdf");
    byte[] buffer = new byte[4096];
    int bytesRead;
    while ((bytesRead = inputStream.read(buffer)) != -1) {
        outputStream.write(buffer, 0, bytesRead);
    }
    outputStream.close();
} catch (IOException e) {
    // 处理请求失败
    e.printStackTrace();
}
​
复制代码

请求和响应拦截器:OkHttp 提供了拦截器的机制,允许在发送请求和接收响应的过程中对其进行处理和修改。可以添加自定义的拦截器来实现身份验证、请求重试、日志记录等功能。

拦截器使用Interceptor接口进行定义,它包含了两个方法:intercept()chain.proceed()

记录请求和响应的日志:

class LoggingInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
​
        long startTime = System.nanoTime();
        System.out.println("Sending request: " + request.url());
​
        Response response = chain.proceed(request);
​
        long endTime = System.nanoTime();
        System.out.println("Received response for " + request.url() + " in " + ((endTime - startTime) / 1e6) + " ms");
​
        return response;
    }
}
​
OkHttpClient client = new OkHttpClient.Builder()
        .addInterceptor(new LoggingInterceptor())
        .build();
​
复制代码

身份验证拦截器示例:

class AuthInterceptor implements Interceptor {
    private String authToken;
​
    public AuthInterceptor(String authToken) {
        this.authToken = authToken;
    }
​
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request originalRequest = chain.request();
​
        // 添加身份验证的请求头
        Request authenticatedRequest = originalRequest.newBuilder()
                .header("Authorization", authToken)
                .build();
​
        return chain.proceed(authenticatedRequest);
    }
}
复制代码

请求重试拦截器示例:

class RetryInterceptor implements Interceptor {
    private int maxAttempts;
    private int currentAttempt = 0;
​
    public RetryInterceptor(int maxAttempts) {
        this.maxAttempts = maxAttempts;
    }
​
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Response response = null;
​
        while (currentAttempt < maxAttempts) {
            try {
                response = chain.proceed(request);
                break;
            } catch (IOException e) {
                // 请求失败,进行重试
                currentAttempt++;
                if (currentAttempt >= maxAttempts) {
                    throw e; // 达到最大重试次数,抛出异常
                }
            }
        }
​
        return response;
    }
}
​
复制代码

支持同步和异步请求:OkHttp 提供了同步和异步两种方式发送请求。通过同步方式发送请求,可以直接获取到响应的结果;而通过异步方式发送请求,可以注册回调来处理响应的结果,这对于处理大量并发请求非常有用。

enqueue(Callback callback): 这是最常用的发送异步请求的方法。可以通过实现Callback接口来处理请求的响应结果和错误情况。

OkHttpClient client = new OkHttpClient();
​
Request request = new Request.Builder()
        .url("https://api.example.com/users")
        .build();
​
client.newCall(request).enqueue(new Callback() {
    @Override
    public void onResponse(Call call, Response response) throws IOException {
        // 处理响应结果
        String responseBody = response.body().string();
        // ...
    }
​
    @Override
    public void onFailure(Call call, IOException e) {
        // 处理异常
        e.printStackTrace();
    }
});
​
复制代码

enqueue(Callback callback, Executor executor): 这个方法与上述方法类似,但是允许指定一个Executor来执行回调。这样可以在指定的线程池中处理回调,例如使用Executors.newCachedThreadPool()来创建线程池。

OkHttpClient client = new OkHttpClient();
​
Request request = new Request.Builder()
        .url("https://api.example.com/users")
        .build();
​
Executor executor = Executors.newCachedThreadPool();
client.newCall(request).enqueue(new Callback() {
    // ...
​
}, executor);复制代码

newWebSocket(Request request, WebSocketListener listener): 如果需要进行WebSocket通信,可以使用此方法来创建一个WebSocket连接。需要实现WebSocketListener接口来处理WebSocket的事件和消息。

OkHttpClient client = new OkHttpClient();
​
Request request = new Request.Builder()
        .url("wss://api.example.com/socket")
        .build();
​
WebSocketListener listener = new WebSocketListener() {
    // ...
​
};
​
WebSocket webSocket = client.newWebSocket(request, listener);复制代码

支持WebSocket:OkHttp 还支持 WebSocket 协议,可以使用 OkHttp 来创建 WebSocket 连接并进行双向通信。

使用OkHttp创建WebSocket连接:

OkHttpClient client = new OkHttpClient();
​
​
Request request = new Request.Builder()
    .url("ws://example.com/socket")
    .build();
​
WebSocket webSocket = client.newWebSocket(request, new WebSocketListener() {
    @Override
    public void onOpen(WebSocket webSocket, Response response) {
        // WebSocket连接已打开,可以进行通信
    }
​
    @Override
    public void onMessage(WebSocket webSocket, String text) {
        // 接收到WebSocket服务器发送的消息
    }
​
    @Override
    public void onClosed(WebSocket webSocket, int code, String reason) {
        // WebSocket连接已关闭
    }
​
    @Override
    public void onFailure(WebSocket webSocket, Throwable t, Response response) {
        // WebSocket连接出现异常
    }
});
​
// 发送文本消息
webSocket.send("Hello, server!");
​
// 关闭WebSocket连接
webSocket.close(1000, "Goodbye, server!");
​
复制代码

定制化和扩展性:OkHttp 提供了丰富的定制化选项,可以根据需要进行配置和扩展。可以自定义连接池、超时设置、缓存策略等,以满足特定的需求。

  1. 连接池定制化:可以通过 ConnectionPool 类设置连接池的最大连接数、保持时间等参数,以控制连接的复用和管理。例如:
ConnectionPool connectionPool = new ConnectionPool(maxIdleConnections, keepAliveDuration, TimeUnit.MILLISECONDS);
OkHttpClient client = new OkHttpClient.Builder()
    .connectionPool(connectionPool)
    .build();
复制代码
  1. 超时设置:可以使用 OkHttpClient.Builder 中的方法来设置连接超时、读取超时和写入超时等参数。例如:
OkHttpClient client = new OkHttpClient.Builder()
    .connectTimeout(10, TimeUnit.SECONDS)
    .readTimeout(30, TimeUnit.SECONDS)
    .writeTimeout(30, TimeUnit.SECONDS)
    .build();
复制代码
  1. 缓存策略定制化:OkHttp 支持 HTTP 缓存,并提供了 CacheControl 类来设置缓存策略。可以根据具体需求设置缓存的有效期、条件等。例如:
CacheControl cacheControl = new CacheControl.Builder()
    .maxAge(1, TimeUnit.DAYS)
    .build();
Request request = new Request.Builder()
    .url("http://example.com")
    .cacheControl(cacheControl)
    .build();
复制代码
  1. 拦截器扩展:可以使用 Interceptor 接口来定义自定义的请求和响应拦截器,以对请求和响应进行处理和修改。例如,实现一个自定义的日志拦截器:
Interceptor loggingInterceptor = new Interceptor() {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        long startTime = System.nanoTime();
        System.out.println("Sending request: " + request.url());
        Response response = chain.proceed(request);
        long endTime = System.nanoTime();
        long duration = endTime - startTime;
        System.out.println("Received response for: " + response.request().url() + " in " + duration + " nanoseconds");
        return response;
    }
};
​
OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(loggingInterceptor)
    .build();
复制代码

猜你喜欢

转载自juejin.im/post/7229172559571402812
今日推荐