• RequestBodyAdvice和ResponseBodyAdvice详解,@ControllerAdvice注解


    一、源码解析

    这是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进行前处理和解析后处理。

    2、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注解。

    1、为什么要实现RequestBodyAdvice或ResponseBodyAdvice接口?

    简单来说,要想执行afterBodyRead方法,必须实现ResponseBodyAdvice接口。

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

    其中:class RequestResponseBodyAdviceChain implements RequestBodyAdvice, ResponseBodyAdvice<Object>

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

    getAdvice方法:

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

    resolveBean方法:this.beanOrName是string类型的,从beanFactory中再拿到对应的bean对象。

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

    2、为什么要加上@ControllerAdvice注解?

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

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

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

    RequestMappingHandlerAdapter初始化流程:

    RequestMappingHandlerAdapter实现了InitializingBean接口,Spring容器会自动调用其afterPropertiesSet方法。

    public void afterPropertiesSet() {
            this.initControllerAdviceCache();  // 获取ControllerAdviceBean的集合
            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);
            }
    
        }
    • argumentResolvers:用于给处理器方法设置参数
    • initBinderArgumentResolvers:用于给注释了@InitBinder方法设置参数
    • returnValueHandlers:用于对处理器返回值进行相应处理

    (1) 获取@ControllerAdvice注解的类封装为ControllerAdviceBean的集合,遍历ControllerAdviceBean集合

      提取没有@RequestMapping注解标注的,但有@ModelAttribute注解标注的方法

      提取@InitBinder注解标注的方法

      提取实现了RequestBodyAdvice或ResponseBodyAdvice接口的类

    (2) 没有设置argumentResolvers则获取默认的HandlerMethodArgumentResolver

    (3) 没有设置initBinderArgumentResolvers则获取默认的处理参数绑定的HandlerMethodArgumentResolver

    (4) 没有设置returnValueHandlers则获取默认的HandlerMethodReturnValueHandler

    RequestMappingHandlerAdapter的getDefaultReturnValueHandlers方法:初始化了RequestResponseBodyMethodProcessor

    private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
            List<HandlerMethodReturnValueHandler> handlers = new ArrayList(20);
            handlers.add(new ModelAndViewMethodReturnValueHandler());
            handlers.add(new ModelMethodProcessor());
            handlers.add(new ViewMethodReturnValueHandler());
            handlers.add(new ResponseBodyEmitterReturnValueHandler(this.getMessageConverters(), this.reactiveAdapterRegistry, 
              this.taskExecutor, this.contentNegotiationManager)); handlers.add(new StreamingResponseBodyReturnValueHandler()); handlers.add(new HttpEntityMethodProcessor(this.getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice)); handlers.add(new HttpHeadersReturnValueHandler()); handlers.add(new CallableMethodReturnValueHandler()); handlers.add(new DeferredResultMethodReturnValueHandler()); handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory)); handlers.add(new ServletModelAttributeMethodProcessor(false)); handlers.add(new RequestResponseBodyMethodProcessor(this.getMessageConverters(), this.contentNegotiationManager,
                this.requestResponseBodyAdvice)); handlers.add(new ViewNameMethodReturnValueHandler()); handlers.add(new MapMethodProcessor()); if (this.getCustomReturnValueHandlers() != null) { handlers.addAll(this.getCustomReturnValueHandlers()); } if (!CollectionUtils.isEmpty(this.getModelAndViewResolvers())) { handlers.add(new ModelAndViewResolverMethodReturnValueHandler(this.getModelAndViewResolvers())); } else { handlers.add(new ServletModelAttributeMethodProcessor(true)); } return handlers; }

    requestResponseBodyAdvice:用来保存实现了RequestBodyAdviceResponseBodyAdvice接口的类

    RequestMappingHandlerAdapter类的initControllerAdviceCache方法:初始化List集合requestResponseBodyAdvice

    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); // 将ControllerAdviceBean放入List集合中 } } if (!requestResponseBodyAdviceBeans.isEmpty()) {
              // 将存有ControllerAdviceBean的集合存入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"); } } } }

    ControllerAdviceBean(有@ControllerAdvice注解的Bean)的findAnnotatedBeans方法:获得所有的ControllerAdvice类,之后封装为ControllerAdviceBean

    public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext context) {
            ListableBeanFactory beanFactory = context;
            if (context instanceof ConfigurableApplicationContext) {
                beanFactory = ((ConfigurableApplicationContext)context).getBeanFactory();
            }
    
            List<ControllerAdviceBean> adviceBeans = new ArrayList();
            String[] var3 = BeanFactoryUtils.beanNamesForTypeIncludingAncestors((ListableBeanFactory)beanFactory, Object.class);
            int var4 = var3.length;
    
            for(int var5 = 0; var5 < var4; ++var5) {
                String name = var3[var5];
                if (!ScopedProxyUtils.isScopedTarget(name)) {
                    ControllerAdvice controllerAdvice = (ControllerAdvice)((ListableBeanFactory)beanFactory).findAnnotationOnBean(name, 
                                    ControllerAdvice.class); // 从application中找到ControllerAdvice注解 if (controllerAdvice != null) {
                // 将有@ControllerAdvice注解的Bean添加到List集合中并返回 adviceBeans.add(
    new ControllerAdviceBean(name, (BeanFactory)beanFactory, controllerAdvice)); } } } OrderComparator.sort(adviceBeans); return adviceBeans; }

    会从applicationContext中获取有ControllerAdvice注解的bean,

  • 相关阅读:
    转-mysql中int、bigint、smallint 和 tinyint的区别详细介绍
    转-通用接口测试用例设计
    转-接口测试
    剑指offer 面试题43 n个骰子的点数 DP
    HYSBZ 1503 郁闷的出纳员 伸展树
    HYSBZ 1588 营业额统计 平衡二叉树模板
    剑指offer 面试题35 第一个只出现一次的字符
    LightOJ 1030 Discovering Gold 期望
    LightOJ 1027 A Dangerous Maze 概率期望
    剑指offer 面试题34 丑数
  • 原文地址:https://www.cnblogs.com/zwh0910/p/15219042.html
Copyright © 2020-2023  润新知