🎉🎉《Spring Boot实战案例合集》目前已更新190个案例,我们将持续不断的更新。文末有电子书目录。
💪💪永久更新承诺
我们郑重承诺,所有订阅合集的粉丝都将享受永久免费的后续更新服务。
💌💌如何获取
订阅我们的合集《点我订阅》,并通过私信联系我们,我们将第一时间将电子书发送给您。
环境:SpringBoot3.4.2
1. 简介
在使用Spring Boot项目开发中,你可能大部分时间都花在API集成上——调用第三方API、暴露自己的REST端点、处理身份验证、错误处理以及性能优化等等。
本篇文章将详细介绍关于API接口开发及调用中的9大核心开发技巧。
默认情况下,若API无响应,RestTemplate或WebClient可能出现卡死现象。为此我们需要配置超时和重试机制。如下示例:
// 配置读写超时HttpClient httpClient = HttpClient.create().doOnConnected(conn -> conn.addHandlerLast(new ReadTimeoutHandler(10)).addHandlerLast(new WriteTimeoutHandler(10)));// 全局连接设置WebClient webClient = WebClient.builder().clientConnector(new ReactorClientHttpConnector(httpClient)).build();
Mono<String> ret = webClient.get().uri("/data").retrieve().bodyToMono(String.class).retryWhen(Retry.fixedDelay(3, Duration.ofSeconds(2)));
我们使用Resilience4j或Spring Cloud Circuit Breaker(引入对应的Resilientce4j实现)。接下来我们通过2种方式实现接口保护,基于注解和编程方式:
基于注解方式:
(name = "a-info", fallbackMethod = "infoFallback")public Map<String, Object> info(Integer id) {if (id == -1) {throw new RuntimeException("非法数字") ;}return this.restTemplate.getForObject("http://localhost:8088/demos/info/{id}", Map.class, id) ;}public Map<String, Object> infoFallback(Integer id, Throwable e) {return Map.of("code", -1, "message", e.getMessage(), "args", id) ;}
resilience4j:circuitbreaker:instances:a-info:minimum-number-of-calls: 10
private final WebClient webClient ;private final ReactiveCircuitBreakerFactory reactiveCircuitBreakerFactory ;public CircuitBreakerService(WebClient webClient,ReactiveCircuitBreakerFactory reactiveCircuitBreakerFactory) {this.webClient = webClient ;this.reactiveCircuitBreakerFactory = reactiveCircuitBreakerFactory ;}// 接口调用public Mono<Map> reactiveInvoke(Integer id) {return webClient.get().uri("/demos/info/{id}", id).retrieve().bodyToMono(Map.class).transform(it -> this.reactiveCircuitBreakerFactory.create("akk").run(it,ex -> Mono.just(Map.ofEntries(entry("code", -1), entry("message", ex.getMessage()))))) ;}
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId></dependency>
// 1.定义拦截器public class LoggerRequestInterceptor implements ClientHttpRequestInterceptor {private static final Logger logger = LoggerFactory.getLogger("ClientHttpRequestInterceptor") ;public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)throws IOException {logger.info("Rest Client request before...") ;ClientHttpResponse response = execution.execute(request, body) ;logger.info("Rest Client response after...") ;return response ;}}// 2.全局配置RestClientRestClient restClient(RestClient.Builder builder) {RestClient restClient = builder.requestInterceptor(new LoggerRequestInterceptor()).build() ;return restClient ;}
WebClient webClient = WebClient.builder().clientConnector(new ReactorClientHttpConnector(httpClient)).filter((request, next) -> {ClientRequest filtered = ClientRequest.from(request).header("x-token", "xxxooo-packxg").build() ;return next.exchange(filtered);}).build();
(name = "api.timeout", fallbackMethod = "reactorTimeoutFallback")("/t")public Mono<String> query() {return webClient.get().uri("http://localhost:8081/api/query").retrieve().bodyToMono(String.class) ;}public Mono<String> reactorTimeoutFallback(Throwable e) {return Mono.just("请求超时") ;}
resilience4j:timelimiter:instances:#该名称为上面注解中的namequeryUser:timeout-duration: 1s
<dependency><groupId>io.github.resilience4j</groupId><artifactId>resilience4j-reactor</artifactId><version>2.2.0</version></dependency>
webClient.get().uri("http://localhost:8081/api/query").retrieve().onStatus(HttpStatusCode::is4xxClientError, response ->Mono.error(new RuntimeException("Client error"))).onStatus(HttpStatusCode::is5xxServerError, response ->Mono.error(new RuntimeException("Server error"))).bodyToMono(String.class) ;
public class SecurityConfig {SecurityFilterChain apiSecurity(HttpSecurity http) throws Throwable {http.csrf(csrf -> csrf.disable()) ;http.securityMatcher("/api/**", "/login") ;http.formLogin(Customizer.withDefaults()) ;http.authorizeHttpRequests(registry -> {registry.anyRequest().authenticated() ;}) ;return http.build() ;}// 配置内存用户(你也可以直接在配置文件中配置)UserDetailsService userDetailsService() {UserDetails user = User.withUsername("pack").password("{noop}123123").roles("ADMIN").build() ;return new InMemoryUserDetailsManager(user) ;}}
使用 MockWebServer(来自 OkHttp)或 WireMock 模拟 API 响应。在单元测试中使用 @MockBean RestTemplate/WebClient。如下示例:
public class UserControllerTest1 {// 用于模拟 HTTP 请求。private MockMvc mockMvc;// 被模拟的服务(真实的服务被忽略)。private UserService userService;public void testUsers() throws Exception {// 1.设置模拟行为when(userService.queryUsers()).thenReturn(List.of(new User(1L, "pack", 33))) ;// 2.模拟 HTTP GET 请求到 /users 并验证响应mockMvc.perform(get("/users")).andExpect(status().isOk()).andExpect(jsonPath("$[0].name").value("pack"));}public void testUser() throws Exception {when(userService.queryUser(1L)).thenThrow(new UserNotFoundException());mockMvc.perform(get("/users/1")).andExpect(status().isNotFound());}}
Mono<String> request1 = webClient.get().uri("http://localhost:8081/api/query").retrieve().bodyToMono(String.class);Mono<String> request2 = webClient.get().uri("http://localhost:8081/api/query").retrieve().bodyToMono(String.class);String ret = Mono.zip(request1, request2).map(tuple -> tuple.getT1() + ", " + tuple.getT2()).block();System.err.println(ret) ;
要定义多版本的API接口,我们可以采用URL版本控制 → /api/v1/users, /api/v2/users;基于头部的版本控制 → X-API-VERSION: 1;内容协商 → Accept: application/vnd.app.v1+json。如下示例:
(value = "/query", headers = "X-API-VERSION=1")public String queryV1() { return "User v1"; }(value = "/query", headers = "X-API-VERSION=2")public String queryV2() { return "User v2"; }
弃用HTTP!Spring Boot 集成 GRPC 优化接口调用性能
Spring Boot自定义注解玩转Controller接口参数任意转换
强大!基于Spring Boot动态注册 / 删除Controller接口(支持动态上传)
查漏补缺!OpenFeign整合Resilience4j,你真的会用吗?
Spring Boot 通过 6 种方式实现开关功能,最后一种直接封神
TargetSource炸场!Spring AOP动态换“靶”超神!
高手必备!Spring Boot 非常实用的10个核心扩展点


