通過RequestBodyAdvice對@RequestBody屬性進行統一處理
scio
在平時的開發中,我們定義RestController的時候,會通過@RequestBody進行JSON參數接受,并且會有一個通用的BaseRequest進行統一封裝,BaseRequest的屬性一般是由請求方傳入,有些特殊場景需要針對統一的請求參數進行特殊處理,比如攔截、預處理、日志記錄等:
- SpringMvc是如何把JSON請求轉換為JavaBean?
- 如何在調用Controller的方法前進行JavaBean的處理?
- 相關資料
SpringMvc是如何把JSON請求轉換為JavaBean?
我們在定義Controller方法的時候,可以指定一個JavaBean作為參數,SpringMvc會自動幫我們生成并填充JavaBean,屬性值來源于url-parameter或者form-body。在JavaBean前添加@RequestBody注解,SpringMvc會自動把JSON轉換為JavaBean。
1. 如何尋找切入點
-
@RequestBody,查看其定義,里面有
boolean required() default true;
,利用IDE查看required方法的引用,Eclipse下雙擊選擇方法,Ctrl+Alt+H -
RequestResponseBodyMethodProcessor,該類調用了
required()
方法進行判斷,再根據類名初步推斷該類就是請求和響應的處理器。 -
RequestResponseBodyMethodProcessor::readWithMessageConverters,該方法會根據請求的類型和參數,調用不同的消息轉換器來進行消息處理,查看調用
required()
方法前,調用了父類方法。 - AbstractMessageConverterMethodArgumentResolver::readWithMessageConverters,該方法會根據content-type,同時遍歷messageConverters,進行消息的轉換。繼續向下跟進發現下面代碼:
if (message.hasBody()) {
//轉換前處理
HttpInputMessage msgToUse =
getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
//轉換后處理
body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
}
在遍歷消息轉換器進行消息處理的時候:轉換前,轉換后,會調用RequestResponseBodyAdviceChain對消息進行兩次處理。而根據RequestResponseBodyAdviceChain名稱可知,這是一個advice鏈,處理的次數可能是多次。由此可以發現,我們在消息轉換前,轉換后都可以對消息進行加工處理,由此可見Aop思想在切面編程上的優勢。
-
RequestResponseBodyAdviceChain,繼續跟進,
getAdvice()
返回的是成員變量advice,它的設置是通過構造函數設置,繼續調用Ctrl+Alt+H
- AbstractMessageConverterMethodArgumentResolver
public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> converters,
@Nullable List<Object> requestResponseBodyAdvice) {
Assert.notEmpty(converters, "'messageConverters' must not be empty");
this.messageConverters = converters;
this.allSupportedMediaTypes = getAllSupportedMediaTypes(converters);
//把advice列表構建成鏈
this.advice = new RequestResponseBodyAdviceChain(requestResponseBodyAdvice);
}
- AbstractMessageConverterMethodProcessor
protected AbstractMessageConverterMethodProcessor(List<HttpMessageConverter<?>> converters,
@Nullable ContentNegotiationManager manager, @Nullable List<Object> requestResponseBodyAdvice) {
super(converters, requestResponseBodyAdvice);
this.contentNegotiationManager = (manager != null ? manager : new ContentNegotiationManager());
this.pathStrategy = initPathStrategy(this.contentNegotiationManager);
this.safeExtensions.addAll(this.contentNegotiationManager.getAllFileExtensions());
this.safeExtensions.addAll(WHITELISTED_EXTENSIONS);
}
----
public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters,
@Nullable List<Object> requestResponseBodyAdvice) {
super(converters, null, requestResponseBodyAdvice);
}
- RequestMappingHandlerAdapter
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
// Annotation-based argument resolution
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
resolvers.add(new RequestParamMapMethodArgumentResolver());
resolvers.add(new PathVariableMethodArgumentResolver());
resolvers.add(new PathVariableMapMethodArgumentResolver());
resolvers.add(new MatrixVariableMethodArgumentResolver());
resolvers.add(new MatrixVariableMapMethodArgumentResolver());
resolvers.add(new ServletModelAttributeMethodProcessor(false));
//構造方法
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
繼續跟進,requestResponseBodyAdvice成員變量的來源,除了setter方法以外,在private void initControllerAdviceCache()方法中
private void initControllerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
AnnotationAwareOrderComparator.sort(adviceBeans);
....
}
- ControllerAdviceBean::findAnnotatedBeans
public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext context) {
return Arrays.stream(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, Object.class))
.filter(name -> context.findAnnotationOnBean(name, ControllerAdvice.class) != null)
.map(name -> new ControllerAdviceBean(name, context))
.collect(Collectors.toList());
}
通過以上代碼,可以發現,注冊邏輯是通過在
applicationContext
中尋找@ControllerAdvice注解的類,然后繼續針對掃描處理的實例,判斷:
...
if (RequestBodyAdvice.class.isAssignableFrom(beanType)) {
requestResponseBodyAdviceBeans.add(adviceBean);
}
if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
requestResponseBodyAdviceBeans.add(adviceBean);
}
if (!requestResponseBodyAdviceBeans.isEmpty()) {
this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
}
...
2. 如何在調用Controller的方法前進行JavaBean的處理?
通過上面的跟蹤發現,我們只需要新建一個類,并實現RequestBodyAdvice接口,同時在類上添加@ControllerAdvice注解,就可以被SpringMvc掃描并自動加載。
那么這個實現類應該怎么寫?老方法,查看RequestBodyAdvice接口的實現類即可,發現有個RequestBodyAdviceAdapter抽象類實現了該接口,這個Adapter只是默認的實現,沒有對消息進行處理,我們自定義實現了,可以直接繼承RequestBodyAdviceAdapter,根據需要覆蓋自己需要處理的方法即可,這也是Adapter設計模式的用處之一。
以下是針對一個簡單的實現,包括了對JavaBean屬性覆蓋,已經通用Session的操作。
/**
* advice request body java bean
*
* @author Wang.ch
* @date 2019-03-28 17:15:52
*/
@ControllerAdvice
public class ScioRequestBodyAdvice extends RequestBodyAdviceAdapter {
@Override
public boolean supports(
MethodParameter methodParameter,
Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
return targetType.getTypeName().equals(QueryVo.class.getName());
}
@Override
public Object afterBodyRead(
Object body,
HttpInputMessage inputMessage,
MethodParameter parameter,
Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
if (body != null) {
QueryVo vo = (QueryVo) body;
vo.setName("Advice By ScioRequestBodyAdvice");
HttpSession session = getRequest().getSession(true);
session.setAttribute("advice", vo);
return vo;
} else {
return body;
}
}
/**
* try to get httpServletRequest from current thread holder
*
* @return
*/
public static HttpServletRequest getRequest() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
HttpServletRequest request =
(HttpServletRequest)
requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
return request;
}
}