Spring-MVC--ControllerAdvice 全局控制器增强


前言

ControllerAdvice 除了可以完成全局异常的处理,同时也可以在真正调用方法之前对body 中的请求参数进行处理,以及在对body 数据相应写回之前对数据进行处理;


一、ControllerAdvice 使用:

只需要增加@ControllerAdvice 注解 并实现RequestBodyAdvice,ResponseBodyAdvice 覆盖其中的方法即可;

import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.io.IOException;
import java.lang.reflect.Type;

@Component
@ControllerAdvice
public class AdviceController implements RequestBodyAdvice,ResponseBodyAdvice {
    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
    	// 返回true ,beforeBodyRead 和 afterBodyRead 才会起作用
        return true;
    }
	// 在读取body 数据之前,可以对数据进行一些验证和加工
    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        return inputMessage;
    }
	// 在body 数据解析完成之后,调用方法。可以对读取到的数据进行加工
    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }

    @Override
    public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return null;
    }
	
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
    	// 返回true ,beforeBodyWrite  才会起作用
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
    	// 请求的数据相应之前 可以对数据进行加工
        return body;
    }
}

在使用方面需要注意一下几个方面:

  • ControllerAdvice 加强器,只用于带有@RequestMapping注解的Controller类,因为其是在RequestMappingHandlerAdapter 初始化是对加了@ControllerAdvice 注解的类进行的解析;
  • 在调用方法之前对于body 数据读取的增强,只适用有body 体的 post /put 请求对 get 请求并不生效;

二、ControllerAdvice 的原理:

2.1 对于@ControllerAdvice 注解的解析

@ControllerAdvice 注解的解析在 RequestMappingHandlerAdapter bean 初始化之后的afterPropertiesSet 方法中

public void afterPropertiesSet() {
    // 对 @ControllerAdvice 进行了解析
     this.initControllerAdviceCache();
     List handlers;
     if (this.argumentResolvers == null) {
	  
         handlers = this.getDefaultArgumentResolvers();
         this.argumentResolvers = (new HandlerMethodArgumentResolverComposite()).addResolvers(handlers);
     }

     if (this.initBinderArgumentResolvers == null) {
         handlers = this.getDefaultInitBinderArgumentResolvers();
         this.initBinderArgumentResolvers = (new HandlerMethodArgumentResolverComposite()).addResolvers(handlers);
     }

     if (this.returnValueHandlers == null) {
         handlers = this.getDefaultReturnValueHandlers();
         this.returnValueHandlers = (new HandlerMethodReturnValueHandlerComposite()).addHandlers(handlers);
     }

}

initControllerAdviceCache 解析

private void initControllerAdviceCache() {
    if (this.getApplicationContext() != null) {
    	// 获取所有类上有 ControllerAdvice 注解的bean
         List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(this.getApplicationContext());
         List<Object> requestResponseBodyAdviceBeans = new ArrayList();
         Iterator var3 = adviceBeans.iterator();

         while(var3.hasNext()) {
             ControllerAdviceBean adviceBean = (ControllerAdviceBean)var3.next();
             Class<?> beanType = adviceBean.getBeanType();
             if (beanType == null) {
                 throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
             }

             Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
             if (!attrMethods.isEmpty()) {
                 this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
             }

             Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
             if (!binderMethods.isEmpty()) {
                 this.initBinderAdviceCache.put(adviceBean, binderMethods);
             }

             if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
                 requestResponseBodyAdviceBeans.add(adviceBean);
             }
         }

         if (!requestResponseBodyAdviceBeans.isEmpty()) {
         	// 最终加入到 List<Object> requestResponseBodyAdvice 属性中
             this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
         }

         if (this.logger.isDebugEnabled()) {
             int modelSize = this.modelAttributeAdviceCache.size();
             int binderSize = this.initBinderAdviceCache.size();
             int reqCount = this.getBodyAdviceCount(RequestBodyAdvice.class);
             int resCount = this.getBodyAdviceCount(ResponseBodyAdvice.class);
             if (modelSize == 0 && binderSize == 0 && reqCount == 0 && resCount == 0) {
                 this.logger.debug("ControllerAdvice beans: none");
             } else {
                 this.logger.debug("ControllerAdvice beans: " + modelSize + " @ModelAttribute, " + binderSize + " @InitBinder, " + reqCount + " RequestBodyAdvice, " + resCount + " ResponseBodyAdvice");
             }
         }

     }
 }

2.2 requestBodyAdvice&responseBodyAdvice 赋值:

在创建RequestResponseBodyAdviceChain 对象时通过其构造方法完成了 requestBodyAdvice&responseBodyAdvice 属相的赋值:

public RequestResponseBodyAdviceChain(@Nullable List<Object> requestResponseBodyAdvice) {
	// 传入之前扫描到的 ControllerAdvice bean 集合
	// 获取到实现了RequestBodyAdvice 接口的bean
     this.requestBodyAdvice.addAll(getAdviceByType(requestResponseBodyAdvice, RequestBodyAdvice.class));
     // 获取到实现了ResponseBodyAdvice接口的bean
     this.responseBodyAdvice.addAll(getAdviceByType(requestResponseBodyAdvice, ResponseBodyAdvice.class));
 }

static <T> List<T> getAdviceByType(@Nullable List<Object> requestResponseBodyAdvice, Class<T> adviceType) {
     if (requestResponseBodyAdvice != null) {
         List<T> result = new ArrayList();
         Iterator var3 = requestResponseBodyAdvice.iterator();

         while(var3.hasNext()) {
             Object advice = var3.next();
             Class<?> beanType = advice instanceof ControllerAdviceBean ? ((ControllerAdviceBean)advice).getBeanType() : advice.getClass();
             if (beanType != null && adviceType.isAssignableFrom(beanType)) {
                 result.add(advice);
             }
         }

         return result;
     } else {
         return Collections.emptyList();
     }
 }

2.3 方法的调用:

在对body 体进行参数解析时会调用 RequestResponseBodyAdviceChain 的 beforeBodyRead 和 afterBodyRead ,beforeBodyWrite 方法:

public boolean supports(MethodParameter param, Type type, Class<? extends HttpMessageConverter<?>> converterType) {
  throw new UnsupportedOperationException("Not implemented");
 }

 public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
     throw new UnsupportedOperationException("Not implemented");
 }

 public HttpInputMessage beforeBodyRead(HttpInputMessage request, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
     Iterator var5 = this.getMatchingAdvice(parameter, RequestBodyAdvice.class).iterator();

     while(var5.hasNext()) {
         RequestBodyAdvice advice = (RequestBodyAdvice)var5.next();
         if (advice.supports(parameter, targetType, converterType)) {
             request = advice.beforeBodyRead(request, parameter, targetType, converterType);
         }
     }

     return request;
 }

 public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
     Iterator var6 = this.getMatchingAdvice(parameter, RequestBodyAdvice.class).iterator();

     while(var6.hasNext()) {
         RequestBodyAdvice advice = (RequestBodyAdvice)var6.next();
         if (advice.supports(parameter, targetType, converterType)) {
             body = advice.afterBodyRead(body, inputMessage, parameter, targetType, converterType);
         }
     }

     return body;
 }

 @Nullable
 public Object beforeBodyWrite(@Nullable Object body, MethodParameter returnType, MediaType contentType, Class<? extends HttpMessageConverter<?>> converterType, ServerHttpRequest request, ServerHttpResponse response) {
     return this.processBody(body, returnType, contentType, converterType, request, response);
 }

 @Nullable
 public Object handleEmptyBody(@Nullable Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
     Iterator var6 = this.getMatchingAdvice(parameter, RequestBodyAdvice.class).iterator();

     while(var6.hasNext()) {
         RequestBodyAdvice advice = (RequestBodyAdvice)var6.next();
         if (advice.supports(parameter, targetType, converterType)) {
             body = advice.handleEmptyBody(body, inputMessage, parameter, targetType, converterType);
         }
     }

     return body;
 }

可以看到先调用getMatchingAdvice 获取到相应的 ControllerAdvice 类,然后进行遍历 先调用 supports 方法,只有supports 方法返回true 才会继续调用到我我们自己业务中的 beforeBodyRead 和 afterBodyRead ,beforeBodyWrite 方法;

三、实际应用:

3.1 获取解析前的参数:

通常我们在和外部系统进行接口的post调用时,在进入congtroller 之后,得到的是 MappingJackson2HttpMessageConverter 帮我们解析并且映射成某个java 对象之后的参数,如果我们使用 @JsonProperty(“xxx”) 做了一些属性的转换后,可能就看不到转换前的内容,此时就可以使用 beforeBodyRead 方法对参数解析前的body 内容进行打印:
如我们在 @RequestBody 接收请求body 转换后的对象:

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;

@Data
public class UserDto {
    @JsonProperty("user_name")
    private String userName;
    @JsonProperty("use_age")
    private Integer age;
}

因为HttpServletRequest 中body 的流只能被读取一次,所有需要有个副本来保存body 请求流:


import lombok.Data;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;

import java.io.InputStream;

@Data
public class HttpInputMessageCopy implements HttpInputMessage {
    private  HttpHeaders headers;
    private  InputStream body;


    @Override
    public InputStream getBody() {
        // Return an InputStream that can reread the copied body
        return body;
    }
    @Override
    public HttpHeaders getHeaders() {
        // Return the copied headers
        return headers;
    }

}

然后在beforeBodyRead 方法中可以将原始的request body 读取打印后,在将其转为字节流存入到body 中:

// 引入lombok  @Slf4j  日志打印
@Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        log.error("请求header:{}", JSONObject.toJSONString(inputMessage.getHeaders()));

        MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
        Object read = messageConverter.read(Map.class, inputMessage);
        log.error("请求原始body:{}", JSONObject.toJSONString(read));
        // 将对象转换为JSON(这里假设你使用的是Jackson的ObjectMapper或者其他JSON转换库)
        String json = JSONObject.toJSONString(read);
        HttpInputMessageCopy copy = new HttpInputMessageCopy();
        copy.setHeaders(inputMessage.getHeaders());
        copy.setBody(new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)));
        return copy;
    }

这样我们在 @RequestBody 接收对象的时,就可以打印出,解析前body 内容:

在这里插入图片描述
然后在afterBodyRead 方法中可以打印出解析后的body 内容:

@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
     log.error("请求header:{}",JSONObject.toJSONString(inputMessage.getHeaders()));
     log.error("请求解析后body:{}",JSONObject.toJSONString(body));
     return body;
 }

这样我们在 @RequestBody 接收对象的时,就可以打印出,解析后body 内容:

在这里插入图片描述

总结:

以上对@ControllerAdvice 注解的使用,及其实现原理进行了分析,使用RequestMappingHandlerAdapter 对注解进行了解析,并在创建RequestResponseBodyAdviceChain 对象时通过其构造方法完成了 requestBodyAdvice&responseBodyAdvice 属相的赋值,最终通过遍历的方式调用每个ControllerAdvice 类中的方法。

  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值