RequestBodyAdvice和ResponseBodyAdvice详解,@ControllerAdvice注解

综述

RequestBodyAdvice
RequestBodyAdvice是SpringMVC4.2提供的一个接口,它允许请求体被读取并转换为对象,并将处理结果对象作为@RequestBody参数或者 @HttpEntity方法参数。由此可见,它的作用范围为:

使用@RequestBody进行标记的参数
参数为HttpEntity

ResponseBodyAdvice
ResponseBodyAdvice是SpringMVC4.1提供的一个接口,它允许在 执行 @ResponseBody后自定义返回数据,或者将返回@ResponseEntity的 Controller Method在写入主体前使用 HttpMessageConverter进行自定义操作。由此可见,它的作用范围为:

使用@ResponseBody注解进行标记
返回@ResponseEntity

源码

这是spring 4.2新加的两个接口

1、RequestBodyAdvice

public interface RequestBodyAdvice {
    boolean supports(MethodParameter var1, Type var2, Class<? extends HttpMessageConverter<?>> var3);

    HttpInputMessage beforeBodyRead(HttpInputMessage var1, MethodParameter var2, Type var3, Class<? extends HttpMessageConverter<?>> var4)       throws IOException;
    Object afterBodyRead(Object var1, HttpInputMessage var2, MethodParameter var3, Type var4, Class<? extends HttpMessageConverter<?>> var5);

    @Nullable
    Object handleEmptyBody(@Nullable Object var1, HttpInputMessage var2, MethodParameter var3, Type var4,             Class<? extends HttpMessageConverter<?>> var5);
}

AbstractMessageConverterMethodArgumentResolver的readWithMessageConverters()方法调用了这个接口的方法。

@Nullable
    protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType)               throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
        boolean noContentType = false;

        MediaType contentType;
        try {
            contentType = inputMessage.getHeaders().getContentType();
        } catch (InvalidMediaTypeException var16) {
            throw new HttpMediaTypeNotSupportedException(var16.getMessage());
        }

        if (contentType == null) {
            noContentType = true;
            contentType = MediaType.APPLICATION_OCTET_STREAM;
        }

        Class<?> contextClass = parameter.getContainingClass();
        Class<T> targetClass = targetType instanceof Class ? (Class)targetType : null;
        if (targetClass == null) {
            ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
            targetClass = resolvableType.resolve();
        }

        HttpMethod httpMethod = inputMessage instanceof HttpRequest ? ((HttpRequest)inputMessage).getMethod() : null;
        Object body = NO_VALUE;

        AbstractMessageConverterMethodArgumentResolver.EmptyBodyCheckingHttpInputMessage message;
        try {
            label94: {
                message = new AbstractMessageConverterMethodArgumentResolver.EmptyBodyCheckingHttpInputMessage(inputMessage);
                Iterator var11 = this.messageConverters.iterator();

                HttpMessageConverter converter;
                Class converterType;
                GenericHttpMessageConverter genericConverter;
                while(true) {
                    if (!var11.hasNext()) {
                        break label94;
                    }

                    converter = (HttpMessageConverter)var11.next();
                    converterType = converter.getClass();
                    genericConverter = converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter)converter : null;
                    if (genericConverter != null) {
                        if (genericConverter.canRead(targetType, contextClass, contentType)) {
                            break;
                        }
                    } else if (targetClass != null && converter.canRead(targetClass, contentType)) {
                        break;
                    }
                }

                if (message.hasBody()) {
                    HttpInputMessage msgToUse = this.getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
                    body = genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :                 converter.read(targetClass, msgToUse);
                    body = this.getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
                } else {
                    body = this.getAdvice().handleEmptyBody((Object)null, message, parameter, targetType, converterType);
                }
            }
        } catch (IOException var17) {
            throw new HttpMessageNotReadableException("I/O error while reading input message", var17, inputMessage);
        }

        if (body != NO_VALUE) {
            LogFormatUtils.traceDebug(this.logger, (traceOn) -> {
                String formatted = LogFormatUtils.formatValue(body, !traceOn);
                return "Read \"" + contentType + "\" to [" + formatted + "]";
            });
            return body;
        } else if (httpMethod != null && SUPPORTED_METHODS.contains(httpMethod) && (!noContentType || message.hasBody())) {
            throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
        } else {
            return null;
        }
    }

HttpMessageConverter处理request body的前后做一些处理和body为空的时候做处理。

从这个可以看出,我们可以在使用这些HandlerMethodArgumentResolver的时候,我们能对request body进行前处理和解析后处理

ResponseBodyAdvice

public interface ResponseBodyAdvice<T> {
    boolean supports(MethodParameter var1, Class<? extends HttpMessageConverter<?>> var2);

    @Nullable
    T beforeBodyWrite(@Nullable T var1, MethodParameter var2, MediaType var3, Class<? extends HttpMessageConverter<?>> var4,             ServerHttpRequest var5, ServerHttpResponse var6);
}

此可以对@ResponseBody的返回结果在输出到响应之前做处理。

通过泛型,指定需要被“拦截”的响应体对象类型。该接口的实现会在 Controller 方法返回数据,并且匹配到了 HttpMessageConverter 之后,HttpMessageConverter 进行序列化之前执行。可以通过覆写 beforeBodyWrite 来统一的对响应体进行修改。

应用

RequestBodyAdvice的使用

首先一个实现类实现RequestBodyAdvice,后在类上加上注解@ControllerAdvice,这俩个缺一不可。比如有些请求的请求体已经做加密处理,可以在此将请求体解密。

核心的方法就是 supports,该方法返回的boolean值,决定了是要执行 beforeBodyRead 方法。

而我们主要的逻辑就是在beforeBodyRead方法中,对客户端的请求体进行解密。

注意:不用加@Component注解
//@Component
@ControllerAdvice
public class RequestBodyDecrypt implements RequestBodyAdvice {
    @Reference
    private EquipmentService equipmentService;
    @Override
    public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;  // 必须为true才会执行beforeBodyRead和afterBodyRead方法
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type,                                     Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
        String equipmentNo = httpInputMessage.getHeaders().getFirst("equipmentNo"); // 从请求头中获取equipmentNo,getFirst()方法根据请求头的名称获取值
        String privateKey = null;
        List<EquipmentDTO> mapList = equipmentService.getByEquipmentNo(equipmentNo);
        if (mapList.size() > 0) {
            EquipmentDTO equipmentDTO = mapList.get(0);
            privateKey = equipmentDTO.getProdPriKey();
        }

        // 提取数据
        InputStream is = httpInputMessage.getBody(); // 从HTTPInputMessage中获取请求体,得到字节输入流
        byte[] data = new byte[is.available()];
        is.read(data);
        String dataStr = new String(data, StandardCharsets.UTF_8);
        JSONObject json = JSONObject.parseObject(dataStr);
        String decrypt = null;
        try {
            decrypt = RSAUtils.decryptByPrivateKey(json.getString("applyData"), privateKey); // 私钥解密后的请求体
        } catch (Exception e) {
            throw new RuntimeException("数据错误");
        }     // 将解密后的请求体封装到HttpInputMessage中返回
        return new DecodedHttpInputMessage(httpInputMessage.getHeaders(), new ByteArrayInputStream(decrypt.getBytes(StandardCharsets.UTF_8)));
    }

    @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;
    }

    static class DecodedHttpInputMessage implements HttpInputMessage {
        HttpHeaders headers;
        InputStream body;

        public DecodedHttpInputMessage(HttpHeaders headers, InputStream body) {
            this.headers = headers;
            this.body = body;
        }

        @Override
        public InputStream getBody() throws IOException {
            return body;
        }

        @Override
        public HttpHeaders getHeaders() {
            return headers;
        }
    }
}

decryptByPrivateKey:

public static String decryptByPrivateKey(String encryptData, String priKey) throws NoSuchAlgorithmException,    InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, IOException {
        byte[] sourceBytes = Base64.getDecoder().decode(encryptData);
        byte[] keyBytes = Base64.getDecoder().decode(priKey);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        Key priK = keyFactory.generatePrivate(pkcs8KeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, priK);
        int inputLen = sourceBytes.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段解密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
                cache = cipher.doFinal(sourceBytes, offSet, MAX_DECRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(sourceBytes, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * MAX_DECRYPT_BLOCK;
        }
        String decryptedData = out.toString("UTF-8");
        out.close();
        return decryptedData;
    }

ResponseBodyAdvice的使用

首先一个实现类实现ResponseBodyAdvice,后在类上加上注解@ControllerAdvice,

@ControllerAdvice
public class MyResponseBodyAdvice implements ResponseBodyAdvice<Result> {
 
    /**
     * 加密串一
     */
    private static String md5_keyone;
    /**
     * 加密串二
     */
    private static String md5_keytwo;
 
    @PostConstruct
    public void init() throws Exception {
        md5_keyone = Utils.PT.getProps("md5_keyone");
        md5_keytwo = Utils.PT.getProps("md5_keytwo");
    }
 
    /**
     * 判断支持的类型
     * 
     * @param returnType
     * @param converterType
     * @return
     * @see org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice#supports(org.springframework.core.MethodParameter,
     *      java.lang.Class)
     */
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return returnType.getMethod().getReturnType().isAssignableFrom(Result.class);
    }
 
    /**
     * 对于结果进行加密
     * 
     * @param body
     * @param returnType
     * @param selectedContentType
     * @param selectedConverterType
     * @param request
     * @param response
     * @return
     * @see org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice#beforeBodyWrite(java.lang.Object,
     *      org.springframework.core.MethodParameter,
     *      org.springframework.http.MediaType, java.lang.Class,
     *      org.springframework.http.server.ServerHttpRequest,
     *      org.springframework.http.server.ServerHttpResponse)
     */
    @Override
    public Result beforeBodyWrite(Result body, MethodParameter returnType,org.springframework.http.MediaType selectedContentType,
            Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,ServerHttpResponse response) {
        String jsonString = JSON.toJSONString(body.getData());
        System.out.println(jsonString);
        // 第一次加密
        String data_encode_one = MD5.md5(md5_keyone + jsonString);
        // 第二次加密
        String data_encode_two = MD5.md5(data_encode_one + md5_keytwo);
        body.setToken(data_encode_two);
        return body;
    }
 
}

为什么说要加上@ControllerAdvice注解,且实现RequestBodyAdvice或ResponseBodyAdvice接口

现在来分析为什么需要实现RequestBodyAdvice接口的同时要加上ControllerAdvice注解。
简单来说,要想执行afterBodyRead方法,必须实现ResponseBodyAdvice接口。

RequestResponseBodyAdviceChain的afterBodyRead方法:调用getMatchingAdvice方法,获取RequestBodyAdvice类型的advice

其中:class RequestResponseBodyAdviceChain implements RequestBodyAdvice, ResponseBodyAdvice

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(); // 此advice就是我们定义的类
            if (advice.supports(parameter, targetType, converterType)) { //如果supports方法的返回值为true,则执行RequestBodyAdvice的afterBodyRead方法
                body = advice.afterBodyRead(body, inputMessage, parameter, targetType, converterType);
            }
        }

        return body;
    }

getMatchAdvice:获取RequestBodyAdvice类型的advice(此advice是我们定义的),如果不是RequestBodyAdvice类型就不会加到结果集,所以这就是我们实现RequestBodyAdvice的原因

private <A> List<A> getMatchingAdvice(MethodParameter parameter, Class<? extends A> adviceType) {
     List<Object> availableAdvice = this.getAdvice(adviceType);//获取RequestBodyAdvice类型的advice(此advice是我们定义实现RequestBodyAdvice接口的类)
        if (CollectionUtils.isEmpty(availableAdvice)) {
            return Collections.emptyList();
        } else {
            List<A> result = new ArrayList(availableAdvice.size());
            Iterator var5 = availableAdvice.iterator();

            while(true) {
                Object advice;
                while(true) {
                    if (!var5.hasNext()) {
                        return result;
                    }

                    advice = var5.next();
                    if (!(advice instanceof ControllerAdviceBean)) {
                        break;
                    }

                    ControllerAdviceBean adviceBean = (ControllerAdviceBean)advice;
                    if (adviceBean.isApplicableToBeanType(parameter.getContainingClass())) {
                        advice = adviceBean.resolveBean(); // 返回的是我们定义的Advice,即根据Bean的名称从BeanFactory中获取Bean对象
                        break;
                    }
                }
          // 判断这个类是否是RequestBodyAdvice类型,如果不是就不会加到结果集,所以这就是我们实现RequestBodyAdvice的原因
                if (adviceType.isAssignableFrom(advice.getClass())) {
                    result.add(advice);
                }
            }
        }
    }
private List<Object> getAdvice(Class<?> adviceType) {
        if (RequestBodyAdvice.class == adviceType) {
            return this.requestBodyAdvice;
        } else if (ResponseBodyAdvice.class == adviceType) {
            return this.responseBodyAdvice;
        } else {
            throw new IllegalArgumentException("Unexpected adviceType: " + adviceType);
        }
    }
public Object resolveBean() {
        if (this.resolvedBean == null) {
            Object resolvedBean = this.obtainBeanFactory().getBean((String)this.beanOrName); // obtainBeanFactory()返回BeanFactory对象
            if (!this.isSingleton) {
                return resolvedBean;
            }

            this.resolvedBean = resolvedBean;
        }

        return this.resolvedBean;
    }

加上@ControllerAdvice注解

简单说,只有加上@ControllerAdvice,才能找到ControllerAdviceBean。

HandlerAdapter字面上的意思就是处理适配器,它的作用用一句话概括就是调用具体的方法对用户发来的请求来进行处理。当handlerMapping获取到执行请求的controller时,DispatcherServlte会根据controller对应的controller类型来调用相应的HandlerAdapter来进行处理。

RequestMappingHandlerAdapter就比较复杂了,可以说,该类是整个SpringMVC中最复杂的类了,却也是目前SpringMVC中使用到的频率最高的类。目前在SpringMVC的使用过程中,对请求的处理主要就是依赖RequestMappingHandlerMapping和RequestMappingHandlerAdapter类的配合使用。下面重点介绍下RequestMappingHandlerAdapter类

RequestMappingHandlerAdapter初始化流程:
RequestMappingHandlerAdapter实现了InitializingBean接口,Spring容器会自动调用其afterPropertiesSet方法。

  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值