简介
Spring Framework 6 和 Spring Boot 3 引入了一些新的特性和改进,以简化 HTTP API 的消费。它允许开发者通过声明式接口来定义对外部 HTTP API 的调用。其中开发者只需要定义接口和方法签名,而具体的实现细节由框架自动生成。
这个特性通常被称为 "声明式 REST 客户端" 或者 "Feign 客户端"(如果使用的是 Netflix Feign 库),在 Spring 生态中也得到了支持。当您使用这样的声明式客户端时,您将创建一个接口,并使用注解来指定每个方法对应哪个 HTTP 路径、请求类型(GET, POST, PUT 等)、路径参数、查询参数等信息。然后,Spring 框架会基于这些接口和方法自动创建代理实例,处理所有的 HTTP 通信细节。
添加依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
测试环境ChuckNorris
https://api.chucknorris.io/
是 Chuck Norris Facts API 的地址,它提供了一个非正式的、用于娱乐目的的接口来获取关于 Chuck Norris 的趣闻。这个 API 可以用来展示如何与 RESTful 服务进行交互,非常适合用作学习和演示 HTTP 请求和响应处理。
Chuck Norris Jokes Api
测试返回结果
声明式接口
在Spring框架中,特别是使用Spring WebFlux或Spring WebClient时,声明式HTTP客户端接口为开发者提供了一种简洁且类型安全的方式来定义与远程服务交互的方法。通过使用这些注解,您可以轻松地指定要执行的HTTP方法、端点以及如何处理响应。以下是对您提到的内容的详细解释和扩展:
使用ResponseEntity
和 反应式类型
当涉及到HTTP接口方法的返回类型时,如果您想要访问HTTP头和状态码,可以使用ResponseEntity<T>
,其中T
是响应体的类型。对于反应式编程模型(如WebFlux),还支持使用Mono<ResponseEntity<T>>
和Flux<ResponseEntity<T>>
来处理异步响应。
Mono<ResponseEntity<MyResponseType>> getJoke() {return webClient.get().uri("/jokes/random").retrieve().toEntity(MyResponseType.class);
}
HTTP 方法相关的注解
为了简化对不同HTTP方法的调用,Spring提供了多个注解用于标注接口方法,使得代码更加清晰易读。以下是常用的几个注解及其用途:
-
@GetExchange
: 用于HTTP GET请求。@GetExchange("/jokes/random") Mono<MyResponseType> getRandomJoke();
-
@PostExchange
: 用于HTTP POST请求。表示请求有效载荷的HTTP接口方法参数应该使用@RequestBody
注解进行标注。@PostExchange("/items") Mono<Item> createItem(@RequestBody Item item);
-
@PutExchange
: 用于HTTP PUT请求。@PutExchange("/items/{id}") Mono<Void> updateItem(@PathVariable String id, @RequestBody Item item);
-
@PatchExchange
: 用于HTTP PATCH请求。@PatchExchange("/items/{id}") Mono<Item> partiallyUpdateItem(@PathVariable String id, @RequestBody ItemUpdateRequest request);
-
@DeleteExchange
: 用于HTTP DELETE请求。@DeleteExchange("/items/{id}") Mono<Void> deleteItem(@PathVariable String id);
-
@HttpExchange
: 最通用的注解。上述所有特定于HTTP方法的注解都是基于此元注解创建的。它允许更灵活地配置URL和HTTP方法。@HttpExchange(url = "/jokes/random", method = "GET") Mono<MyResponseType> getRandomJokeUsingHttpExchange();
示例
Controller
package com.coderlk.restdemo.controller;import com.coderlk.restdemo.client.RestDemo;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@Slf4j
public class RestDemoController {@ResourceRestDemo client;@GetMapping("/")public String remote(){log.info("ChuckNorrisController.remote start random: {}", client.getRandomQuote());log.info("Categories: {}", client.getCategories());log.info("Joke from money category: {}", client.getQuoteFromCategory("money"));return "SUCCESS";}
}
Client
package com.coderlk.restdemo.client;import com.coderlk.restdemo.vo.RestDemoVO;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;import java.util.List;@HttpExchange(url = "/jokes")
public interface RestDemo {@HttpExchange(url = "/random", method = "GET")RestDemoVO getRandomQuote();@GetExchange("/random")RestDemoVO getQuoteFromCategory(@RequestParam("category") String category);@GetExchange("/categories")List<String> getCategories();
}
Config
package com.coderlk.restdemo.config;import com.coderlk.restdemo.client.RestDemo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.support.WebClientAdapter;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;@Configuration
public class AppConfig {@Beanpublic RestDemo chuckNorrisClient() throws Exception {WebClient webClient = WebClient.builder().baseUrl("https://api.chucknorris.io/").build();HttpServiceProxyFactory httpServiceProxyFactory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(webClient)).build();return httpServiceProxyFactory.createClient(RestDemo.class);}
}
VO
package com.coderlk.restdemo.vo;import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;@Data
public class RestDemoVO {private String value;private String url;private String id;@JsonProperty("icon_url")private String iconUrl;@JsonProperty("created_at")private String createdAt;@JsonProperty("updated_at")private String updatedAt;
}
Application
package com.coderlk.restdemo;import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
@Slf4j
public class RestDemoApplication {public static void main(String[] args) {SpringApplication.run(RestDemoApplication.class, args);}}
application.properties
server.port=80
测试
2024-12-23T09:42:16.253+08:00 INFO 87005 --- [p-nio-80-exec-3] c.c.c.r.controller.RestDemoController : ChuckNorrisController.remote start random: RestDemoVO(value=Mick Jagger: "oh you can't always get what you waaaannt." Chuck Norris "Yes I can.", url=https://api.chucknorris.io/jokes/NqcgUOhxQH2cf7oWrvTj1Q, id=NqcgUOhxQH2cf7oWrvTj1Q, iconUrl=https://api.chucknorris.io/img/avatar/chuck-norris.png, createdAt=2020-01-05 13:42:22.089095, updatedAt=2020-01-05 13:42:22.089095)
2024-12-23T09:42:16.640+08:00 INFO 87005 --- [p-nio-80-exec-3] c.c.c.r.controller.RestDemoController : Categories: [animal, career, celebrity, dev, explicit, fashion, food, history, money, movie, music, political, religion, science, sport, travel]
2024-12-23T09:42:17.021+08:00 INFO 87005 --- [p-nio-80-exec-3] c.c.c.r.controller.RestDemoController : Joke from money category: RestDemoVO(value=Chuck Norris is the true Sultan of Swing. He also gets his money for nothing, and his chicks for free., url=https://api.chucknorris.io/jokes/L1GsWHngRoSW1UPXidGQgg, id=L1GsWHngRoSW1UPXidGQgg, iconUrl=https://api.chucknorris.io/img/avatar/chuck-norris.png, createdAt=2020-01-05 13:42:28.420821, updatedAt=2020-05-22 06:16:41.133769)