關於 Spring 的全局處理,有兩方面要說:
-
統一數據返回格式
-
統一異常處理
通用返回值類定義:
public class GlobalResponse<T> implements POJO {
/**
*
*/
private static final long serialVersionUID = 1L;
@ApiModelProperty(notes = "數據")
private T data;
@ApiModelProperty(notes = "不爲空。等於200時表示業務成功,其他表示業務失敗")
private int code = 200;
@ApiModelProperty(notes = "錯誤信息,如果不爲空,展示給用戶")
private String msg;
public GlobalResponse() {
}
}
配置
沒錯,我們需要藉助幾個關鍵註解來完成一下相關配置:
@EnableWebMvc
public class UnifiedResponseHandler {
@RestControllerAdvice
static class CommonResultResponseAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
if (body instanceof GlobalResponse) {
// 兼容舊版本的數據,已經用GlobalResponse封裝好了的,就不用再進行處理了
return body;
} else if (body instanceof POJO) {
// 目前只針對POJO的返回對象進行封裝
return new GlobalResponse<Object>(body);
} else {
return body;
}
}
}
}
到這裏就結束了,我們就可以縱情的寫任何 RESTful API 了,所有的返回值都會有統一的 JSON 結構
解剖實現過程
從 @EnableWebMvc
這個註解說起,打開該註解看:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
通過 @Import
註解引入了 DelegatingWebMvcConfiguration.class
,那來看這個類吧:
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
@Override
protected void configurePathMatch(PathMatchConfigurer configurer) {
this.configurers.configurePathMatch(configurer);
}
@Override
protected void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
this.configurers.configureContentNegotiation(configurer);
}
@Override
protected void configureAsyncSupport(AsyncSupportConfigurer configurer) {
this.configurers.configureAsyncSupport(configurer);
}
@Override
protected void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
this.configurers.configureDefaultServletHandling(configurer);
}
@Override
protected void addFormatters(FormatterRegistry registry) {
this.configurers.addFormatters(registry);
}
@Override
protected void addInterceptors(InterceptorRegistry registry) {
this.configurers.addInterceptors(registry);
}
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
this.configurers.addResourceHandlers(registry);
}
@Override
protected void addCorsMappings(CorsRegistry registry) {
this.configurers.addCorsMappings(registry);
}
@Override
protected void addViewControllers(ViewControllerRegistry registry) {
this.configurers.addViewControllers(registry);
}
@Override
protected void configureViewResolvers(ViewResolverRegistry registry) {
this.configurers.configureViewResolvers(registry);
}
@Override
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
this.configurers.addArgumentResolvers(argumentResolvers);
}
@Override
protected void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
this.configurers.addReturnValueHandlers(returnValueHandlers);
}
@Override
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
this.configurers.configureMessageConverters(converters);
}
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
this.configurers.extendMessageConverters(converters);
}
@Override
protected void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
this.configurers.configureHandlerExceptionResolvers(exceptionResolvers);
}
@Override
protected void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
this.configurers.extendHandlerExceptionResolvers(exceptionResolvers);
}
@Override
@Nullable
protected Validator getValidator() {
return this.configurers.getValidator();
}
@Override
@Nullable
protected MessageCodesResolver getMessageCodesResolver() {
return this.configurers.getMessageCodesResolver();
}
}
有 @Configuration
註解,你應該很熟悉了,該類的父類 WebMvcConfigurationSupport
中卻隱藏着一段關鍵代碼:
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
adapter.setContentNegotiationManager(mvcContentNegotiationManager());
adapter.setMessageConverters(getMessageConverters());
adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer());
adapter.setCustomArgumentResolvers(getArgumentResolvers());
adapter.setCustomReturnValueHandlers(getReturnValueHandlers());
if (jackson2Present) {
adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice()));
adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));
}
AsyncSupportConfigurer configurer = new AsyncSupportConfigurer();
configureAsyncSupport(configurer);
if (configurer.getTaskExecutor() != null) {
adapter.setTaskExecutor(configurer.getTaskExecutor());
}
if (configurer.getTimeout() != null) {
adapter.setAsyncRequestTimeout(configurer.getTimeout());
}
adapter.setCallableInterceptors(configurer.getCallableInterceptors());
adapter.setDeferredResultInterceptors(configurer.getDeferredResultInterceptors());
return adapter;
}
RequestMappingHandlerAdapter 是每一次請求處理的關鍵,來看該類的定義:
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
implements BeanFactoryAware, InitializingBean {
...
}
該類實現了 InitializingBean 接口,其中 InitializingBean 接口的afterPropertiesSet
方法就是關鍵之一,在 RequestMappingHandlerAdapter 類中同樣重寫了該方法:
@Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBody advice beans
initControllerAdviceCache();
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.initBinderArgumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
該方法內容都非常關鍵,但我們先來看 initControllerAdviceCache
方法,其他內容後續再單獨說明:
private void initControllerAdviceCache() {
...
if (logger.isInfoEnabled()) {
logger.info("Looking for @ControllerAdvice: " + getApplicationContext());
}
List<ControllerAdviceBean> beans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
AnnotationAwareOrderComparator.sort(beans);
List<Object> requestResponseBodyAdviceBeans = new ArrayList<Object>();
for (ControllerAdviceBean bean : beans) {
...
if (ResponseBodyAdvice.class.isAssignableFrom(bean.getBeanType())) {
requestResponseBodyAdviceBeans.add(bean);
}
}
}
通過 ControllerAdviceBean 靜態方法掃描 ControllerAdvice
註解,可是我們在UnifiedResponseHandler中實際實現上使用的是 @RestControllerAdvice
註解,打開看該註解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice {
該註解由 @ControllerAdvice
和 @ResponseBody
標記,就好比你熟悉的 @RestController
註解由 @Controller
和 @ResponseBody
標記是一樣的
到這裏你已經知道我們用 @RestControllerAdvice
標記的 Bean 是如何被加載到 Spring 上下文的,接下來就要知道是 Spring 是如何使用我們的 bean 以及對返回 body 做處理的
其實在 HttpMessageConverter是這樣轉換數據的這篇文章中已經說明了一部分,希望小夥伴先看這篇文章,下面的部分就會秒懂了,我們在這裏做進一步的說明
在 AbstractMessageConverterMethodProcessor 的 writeWithMessageConverters
方法中,有一段核心代碼:
if (messageConverter instanceof GenericHttpMessageConverter) {
if (((GenericHttpMessageConverter) messageConverter).canWrite(
declaredType, valueType, selectedMediaType)) {
outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
inputMessage, outputMessage);
...
return;
}
}
可以看到通過 getAdvice() 調用了 beforeBodyWrite
方法,我們已經接近真相了
protected RequestResponseBodyAdviceChain getAdvice() {
return this.advice;
}
RequestResponseBodyAdviceChain,看名字帶有 Chain,很明顯用到了「責任鏈設計模式」,只不過它傳遞責任鏈以循環的方式完成:
class RequestResponseBodyAdviceChain implements RequestBodyAdvice, ResponseBodyAdvice<Object> {
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType contentType,
Class<? extends HttpMessageConverter<?>> converterType,
ServerHttpRequest request, ServerHttpResponse response) {
return processBody(body, returnType, contentType, converterType, request, response);
}
@SuppressWarnings("unchecked")
private <T> Object processBody(Object body, MethodParameter returnType, MediaType contentType,
Class<? extends HttpMessageConverter<?>> converterType,
ServerHttpRequest request, ServerHttpResponse response) {
for (ResponseBodyAdvice<?> advice : getMatchingAdvice(returnType, ResponseBodyAdvice.class)) {
if (advice.supports(returnType, converterType)) {
body = ((ResponseBodyAdvice<T>) advice).beforeBodyWrite((T) body, returnType,
contentType, converterType, request, response);
}
}
return body;
}
}
我們重寫的 beforeBodyWrite
方法終究會被調用到,真相就是這樣了!!!
其實還沒完,你有沒有想過,如果我們的 API 方法返回值是 org.springframework.http.ResponseEntity<T>
類型,我們可以指定 HTTP 返回狀態碼,但是這個返回值會直接放到我們的 beforeBodyWrite 方法的 body 參數中嗎?如果這樣做很明顯是錯誤的,因爲 ResponseEntity 包含很多我們非業務數據在裏面,那 Spring 是怎麼幫我們處理的呢?
在我們方法取得返回值並且在調用 beforeBodyWrite
方法之前,還要選擇 HandlerMethodReturnValueHandler 來處理不同類型的返回值
在類 HandlerMethodReturnValueHandlerComposite 中的 handleReturnValue 方法中
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
通過調用 selectHandler 方法來選擇合適的 handler,Spring 內置了很多個 Handler,我們來看類圖:
HttpEntityMethodProcessor 就是其中之一,它重寫了 supportsParameter 方法,支持 HttpEntity 類型,即支持 ResponseEntity 類型:
@Override
public boolean supportsParameter(MethodParameter parameter) {
return (HttpEntity.class == parameter.getParameterType() ||
RequestEntity.class == parameter.getParameterType());
}
所以當我們返回的類型爲 ResponseEntity 時,就要通過 HttpEntityMethodProcessor 的 handleReturnValue 方法來處理我們的結果:
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
...
if (responseEntity instanceof ResponseEntity) {
int returnStatus = ((ResponseEntity<?>) responseEntity).getStatusCodeValue();
outputMessage.getServletResponse().setStatus(returnStatus);
if (returnStatus == 200) {
if (SAFE_METHODS.contains(inputMessage.getMethod())
&& isResourceNotModified(inputMessage, outputMessage)) {
// Ensure headers are flushed, no body should be written.
outputMessage.flush();
// Skip call to converters, as they may update the body.
return;
}
}
}
// Try even with null body. ResponseBodyAdvice could get involved.
writeWithMessageConverters(responseEntity.getBody(), returnType, inputMessage, outputMessage);
// Ensure headers are flushed even if no body was written.
outputMessage.flush();
}
該方法提取出 responseEntity.getBody(),並傳遞個 MessageConverter,然後再繼續調用 beforeBodyWrite
方法,這纔是真相!!!