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,

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM