- spring-cloud-tutorials文件夹下有多个子工程,本篇的代码是gateway-change-body,如下图红框所示:
为何不用常规手段
- 提到全局异常处理,经验丰富的您应该想到了常用的ControllerAdvice和ExceptionHandler注解修饰的全局异常处理类,但是Spring Cloud Gateway是基于WebFlux的,咱们之前处理异常时用到的HttpServletRequest在Spring Cloud Gateway中并不适用,因此,不能用ControllerAdvice和ExceptionHandler的手段来处理全局异常
基本思路
-
在动手前做好充足的理论分析,写出的代码才能正常工作
-
打开DefaultErrorWebExceptionHandler.java,找到renderErrorResponse方法,来看看Spring Cloud Gateway原本是如何构造异常返回内容的:
- 此刻聪明的您应该想到怎么做了:做个新的类继承DefaultErrorWebExceptionHandler,覆盖其renderErrorResponse方法,新的renderErrorResponse方法中,按照实际业务需要来设置返回内容,没错,这就是咱们的思路,不过还要细化一下,最终具体的步骤如下:
-
新增一个异常类CustomizeInfoException.java,该类有三个字段:http返回码、业务返回码、业务描述信息
-
在返回异常的代码位置,使用CustomizeInfoException类来抛出异常,按照实际业务场景设置CustomizeInfoException实例的各个字段
-
新增MyErrorWebExceptionHandler.java,继承自DefaultErrorWebExceptionHandler,重写了renderErrorResponse方法,这里面检查异常实例是否是CustomizeInfoException类型,如果是,就从其中取出http返回码、业务返回码、业务描述信息等字段,构造返回body的内容,异常实例若不是CustomizeInfoException类型,就保持之前的处理逻辑不变;
-
新增configuration类,用于将MyErrorWebExceptionHandler实例注册到spring环境
- 分析完毕,开始编码吧,为了简单起见,本篇不再新增maven子工程,而是基于前文创建的子工程gateway-change-body,在这里面继续写代码;
编码
- 新增异常类CustomizeInfoException.java:
package com.bolingcavalry.changebody.exception;
import lombok.Data;
import org.springframework.http.HttpStatus;
@Data
public class CustomizeInfoException extends Exception {
/**
- http返回码
*/
private HttpStatus httpStatus;
/**
- body中的code字段(业务返回码)
*/
private String code;
/**
- body中的message字段(业务返回信息)
*/
private String message;
}
- 修改RequestBodyRewrite.java的apply方法,这里面是在处理请求body,如果检查到没有user-id字段,就不将请求转发到服务提供方provider-hello,而是返回错误,这里的错误就用CustomizeInfoException类来处理:
@Override
public Publisher apply(ServerWebExchange exchange, String body) {
try {
Map<String, Object> map = objectMapper.readValue(body, Map.class);
// 如果请求参数中不含user-id,就返回异常
if (!map.containsKey(“user-id”)) {
CustomizeInfoException customizeInfoException = new CustomizeInfoException();
// 这里返回406,您可以按照业务需要自行调整
customizeInfoException.setHttpStatus(HttpStatus.NOT_ACCEPTABLE);
// 这里按照业务需要自行设置code
customizeInfoException.setCode(“010020003”);
// 这里按照业务需要自行设置返回的message
customizeInfoException.setMessage(“请确保请求参数中的user-id字段是有效的”);
return Mono.error(customizeInfoException);
}
// 取得id
int userId = (Integer)map.get(“user-id”);
// 得到nanme后写入map
map.put(“user-name”, mockUserName(userId));
return Mono.just(objectMapper.writeValueAsString(map));
} catch (Exception ex) {
log.error(“1. json process fail”, ex);
return Mono.error(new Exception(“1. json process fail”, ex));
}
}
- 异常处理类MyErrorWebExceptionHandler.java,这里有一处需要**重点关注的是:**下面的代码仅是参考而已,您无需拘泥于CustomizeInfoException有关的逻辑,完全能按照业务需求自由设置返回的状态码和body:
package com.bolingcavalry.changebody.handler;
import com.bolingcavalry.changebody.exception.CustomizeInfoException;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;
public class MyErrorWebExceptionHandler extends DefaultErrorWebExceptionHandler {
public MyErrorWebExceptionHandler(ErrorAttributes errorAttributes, WebProperties.Resources resources, ErrorProperties errorProperties, ApplicationContext applicationContext) {
super(errorAttributes, resources, errorProperties, applicationContext);
}
@Override
protected Mono renderErrorResponse(ServerRequest request) {
// 返回码
int status;
// 最终是用responseBodyMap来生成响应body的
Map<String, Object> responseBodyMap = new HashMap<>();
// 这里和父类的做法一样,取得DefaultErrorAttributes整理出来的所有异常信息
Map<String, Object> error = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
// 原始的异常信息可以用getError方法取得
Throwable throwable = getError(request);
// 如果异常类是咱们定制的,就定制
if (throwable instanceof CustomizeInfoException) {
CustomizeInfoException myGatewayException = (CustomizeInfoException) throwable;
// http返回码、body的code字段、body的message字段,这三个信息都从CustomizeInfoException实例中获取
status = myGatewayException.getHttpStatus().value();
responseBodyMap.put(“code”, myGatewayException.getCode());
responseBodyMap.put(“message”, myGatewayException.getMessage());
responseBodyMap.put(“data”, null);
} else {
// 如果不是咱们定制的异常,就维持和父类一样的逻辑
// 返回码
status = getHttpStatus(error);
// body内容
responseBodyMap.putAll(error);
}
return ServerResponse
// http返回码
.status(status)
// 类型和以前一样
.contentType(MediaType.APPLICATION_JSON)
// 响应body的内容
.body(BodyInserters.fromValue(responseBodyMap));
}
}
- 最后是配置类MyErrorWebFluxAutoConfiguration.java:
package com.bolingcavalry.changebody.config;
总结
虽然面试套路众多,但对于技术面试来说,主要还是考察一个人的技术能力和沟通能力。不同类型的面试官根据自身的理解问的问题也不尽相同,没有规律可循。
上面提到的关于这些JAVA基础、三大框架、项目经验、并发编程、JVM及调优、网络、设计模式、spring+mybatis源码解读、Mysql调优、分布式监控、消息队列、分布式存储等等面试题笔记及资料
有些面试官喜欢问自己擅长的问题,比如在实际编程中遇到的或者他自己一直在琢磨的这方面的问题,还有些面试官,尤其是大厂的比如 BAT 的面试官喜欢问面试者认为自己擅长的,然后通过提问的方式深挖细节,刨根到底。
试官根据自身的理解问的问题也不尽相同,没有规律可循。
[外链图片转存中…(img-JGXqD5Ay-1714057426975)]
[外链图片转存中…(img-WgpWv1x1-1714057426976)]
上面提到的关于这些JAVA基础、三大框架、项目经验、并发编程、JVM及调优、网络、设计模式、spring+mybatis源码解读、Mysql调优、分布式监控、消息队列、分布式存储等等面试题笔记及资料
有些面试官喜欢问自己擅长的问题,比如在实际编程中遇到的或者他自己一直在琢磨的这方面的问题,还有些面试官,尤其是大厂的比如 BAT 的面试官喜欢问面试者认为自己擅长的,然后通过提问的方式深挖细节,刨根到底。