1. 介绍
在我们使用对请求进行加解密的时候,常常需要在controller层进行做拦截,拦截器是一种实现方式,还可以使用controllerAdvice。
Controller Advice字面上意思是“控制器通知”,Advice除了“劝告”、“意见”之外,还有“通知”的意思。
2. 使用
2.1 作用
controllerAdvice的作用有如下几点使用
- 对请求前数据参数进行处理(解密、加参数等)
- 对返回的数据参数进行处理(加密、加参数等)
- 进行全局异常处理(@ExceptionHandler)
2.2 作用域
controllerAdvice的作用域,controllerAdvice本质上也是一个拦截器,也是一个aop操作,controllerAdvice注解
1. 基于包名的作用域
2. 基于类名的作用域
3. 基于注解的作用域
2.3 请求加解密
编写一个类实现requestBodyAdvice,在其类上引入controllerAdvice注解,并且标注其中basePackages,ResponseBodyAdvice同理。
/**
*
* @author : fangcong
* @date : 2022/11/14 10:16 上午
* @description : 对openApi, 进行解密处理
**/
@Slf4j
@ControllerAdvice(basePackages = "com.xx.comm.controller.openApi")
@Order(1)
public class OpenApiRequestBodyEncryptor implements RequestBodyAdvice {
@Resource
private HttpControllerAdviceIEncryptor myOpenApiKeyService;
@Resource
private ApiSecurityRequestSignService apiSecurityRequestSignService;
/**
* 判断是否所有的方法都需要进行拦截
*/
@Override
public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
/**
* 在controller执行前,更改body内容
*/
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
String body = verityData(httpInputMessage, bodyContent);
return buildNewHttpInputMessage(httpInputMessage, body);
}
private String getHttpInputMessageBody(HttpInputMessage httpInputMessage) {
try {
return IOUtils.toString(httpInputMessage.getBody());
} catch (IOException e) {
throw new RuntimeException("httpInputMessage cannot read body");
}
}
private String verityData(HttpInputMessage httpInputMessage, String bodyContent) {
try{
return body(bodyContent);
}catch (Exception e){
throw new RuntimeException("decrypt httpInputMessage body fail");
}
}
private void verityHeaderParam(HttpInputMessage httpInputMessage) {
HttpHeaders headers = httpInputMessage.getHeaders();
apiSecurityRequestSignService.preHandle(headers);
}
private HttpInputMessage buildNewHttpInputMessage(HttpInputMessage httpInputMessage, String body) {
return new HttpInputMessage() {
@Override
public HttpHeaders getHeaders() {
return httpInputMessage.getHeaders();
}
@Override
public InputStream getBody() {
return new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8));
}
};
}
private String body(String bodyContent) {
try {
return myOpenApiKeyService.decrypt(bodyContent);
}catch (Exception e){
throw new RuntimeException("无法解析body");
}
}
@Override
public Object afterBodyRead(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return body;
}
@Override
public Object handleEmptyBody(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return body;
}
}
2.4 实现全局异常处理器
定义一个类引入controllerAdvice注解,并且指定其basePackages,使用@ExceptionHandler指定异常类
@Slf4j
@ControllerAdvice(basePackages = "com.xx.comm.controller.openApi")
@Order(1)
public class OpenApiExceptionHandler {
@ExceptionHandler(Exception.class)
@ResponseBody
public final ControllerResultVO handleAllExceptions(Exception ex) {
log.error(ex.getMessage(), ex);
ex.printStackTrace();
return ControllerResultVO.FAILED(ex.getMessage());
}
}
3. 多个controllerAdvice处理
系统中存在多个controllerAdvice,
controllerAdvice1:
@Slf4j
@ControllerAdvice(basePackages = “com.xx”)
public class ControllerAdvice1 {
}
controllerAdvice2:
@Slf4j
@ControllerAdvice(basePackages = “com.xx.openApi”)
public class ControllerAdvice1 {
}
其中处理步骤如下所示:
如果请求com.xx.openApi包的接口,则会执行controllerAdvice1不会执行controllerAdvice2,因为在定义多个controllerAdvice的,因为controllerAdvice1也能映射到。
处理解决如下所示:
- 在controllerAdvice2添加order注解,值越小,越先加载,因此在请求请求com.xx.openApi包的接口,则会执行controllerAdvice2。
3. ResponseBodyAdvice 与 ExceptionHandler 执行顺序
- 如果存在ResponseBodyAdvice 指定了对应的具体包名,如比:
@Slf4j
@ControllerAdvice(basePackages = "com.xx.comm.controller.openApi")
@Order(1)
public class OpenApiResponseBodyEncryption implements ResponseBodyAdvice {
-
又定义了一个ExceptionHandler对com.xx.comm.controller.openApi的异常拦截处理。
-
如果接口抛出异常后,则会被ExceptionHandler捕获,捕获后会进入ResponseBodyAdvice处理中,但是ExceptionHandler的定义包是在com.xx.comm.service中,这样的返回体是不能被ResponseBodyAdvice捕获的,因此需要将ExceptionHandler定义类定义在com.xx.comm.controller.openApi包下。