您好,登錄后才能下訂單哦!
SpringMVC中如何利用@ControllerAdvice和ResponseBodyAdvice接口統(tǒng)一處理返回值,相信很多沒有經(jīng)驗(yàn)的人對(duì)此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個(gè)問題。
在我們進(jìn)行Java的Web應(yīng)用開發(fā)時(shí),如何寫更少的代碼,做更多的事情。如何讓開發(fā)更容易上手,更專注于業(yè)務(wù)層面,不需要太關(guān)心底層的實(shí)現(xiàn)。這里就分享一些我平時(shí)在搭建基礎(chǔ)框架時(shí)候的一些心得體驗(yàn)。
在web應(yīng)用中,通常前后端會(huì)定義一個(gè)統(tǒng)一的對(duì)象來封裝返回值,一般除了業(yè)務(wù)數(shù)據(jù)之外,可能會(huì)包含一些請(qǐng)求相關(guān)的數(shù)據(jù)
例如以下這個(gè)對(duì)象
code
來標(biāo)識(shí)整個(gè)請(qǐng)求的結(jié)果
msg
用于返回錯(cuò)誤信息
data
用于返回實(shí)際的業(yè)務(wù)數(shù)據(jù)。
{ "code": 0, "msg": "success", "data": {} }
統(tǒng)一封裝的好處就是前端可以使用統(tǒng)一的邏輯進(jìn)行請(qǐng)求處理,能夠編寫通用代碼來處理返回值。
當(dāng)然這也需要后端做一定的開發(fā)。通常我們都是直接寫在代碼里面,手動(dòng)去創(chuàng)建一個(gè)封裝對(duì)象,然后將數(shù)據(jù)set進(jìn)去,或者是封裝類添加一些靜態(tài)方法之類的。 在大部分情況下,這些工作都是重復(fù)的。
今天介紹的這個(gè)接口, ResponseBodyAdvice
, 這是由SpringMvc提供的一個(gè)接口,在消息轉(zhuǎn)換前處理返回值,源碼如下:
public interface ResponseBodyAdvice<T>{ boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType); T beforeBodyWrite(T body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response); }
這個(gè)接口在返回值被消息轉(zhuǎn)換器寫回前端之前進(jìn)行處理, 大致處理流程如下:
我們實(shí)現(xiàn)這個(gè)接口的代碼主要在這個(gè)方法里被調(diào)用 RequestResponseBodyAdviceChain.processBody
, 可以看到這一段邏輯很簡單
先執(zhí)行ResponseBodyAdvice.supports
看當(dāng)前切面類是否支持,如果支持再調(diào)用ResponseBodyAdvice.beforeBodyWrite
方法并返回
返回值會(huì)被 HttpMessageConverter.write
接口在進(jìn)行最終的轉(zhuǎn)換(例如轉(zhuǎn)JSON),然后寫回前端
private <T> Object processBody(@Nullable 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; }
SpringMVC在初始化的時(shí)候, 會(huì)調(diào)用RequestMappingHandlerAdapter.initControllerAdviceCache
,將ResponseBodyAdvice
初始化到容器中
里面會(huì)調(diào)用ControllerAdviceBean.findAnnotatedBeans
,獲取所有帶有 @ControllerAdvice
注解的類
將所有實(shí)現(xiàn)了 ResponseBodyAdvice
接口的Bean放入requestResponseBodyAdviceBeans
中, 在之前介紹到的 getAdvice()
方法取得就是該對(duì)象。
//代碼片段 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()); } // 代碼片段 for (ControllerAdviceBean adviceBean : adviceBeans) { 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)) { requestResponseBodyAdviceBeans.add(adviceBean); } if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) { requestResponseBodyAdviceBeans.add(adviceBean); } }
了解到這些,我們實(shí)現(xiàn)一個(gè)通用的返回值處理就很簡單了, 只需要實(shí)現(xiàn) ResponseBodyAdvice
接口,并且加上 @ControllerAdvice
注解就可以了
這是我實(shí)現(xiàn)的一個(gè),統(tǒng)一封裝返回值的實(shí)現(xiàn), 大家可以參考一下,根據(jù)自己的業(yè)務(wù)需求來進(jìn)行修改
package com.diamondfsd.fast.mvc.advice; import com.diamondfsd.fast.mvc.annotations.IgnoreAware; import com.diamondfsd.fast.mvc.entity.FastResult; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; import java.lang.reflect.Method; import java.util.Map; import java.util.WeakHashMap; /** * 統(tǒng)一返回?cái)?shù)據(jù)封裝 * @author Diamond */ @ControllerAdvice public class FastMvcResponseBodyAwareAdvice implements ResponseBodyAdvice<Object> { private final Map<Method, Boolean> supportsCache = new WeakHashMap<>(); private final String [] basePackages; private ObjectMapper objectMapper = new ObjectMapper(); public FastMvcResponseBodyAwareAdvice(String [] basePackages) { this.basePackages = basePackages; } @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { if (supportsCache.containsKey(returnType.getMethod())) { return supportsCache.get(returnType.getMethod()); } boolean isSupport = getIsSupport(returnType); supportsCache.put(returnType.getMethod(), isSupport); return isSupport; } private boolean getIsSupport(MethodParameter returnType) { Class<?> declaringClass = returnType.getMember().getDeclaringClass(); IgnoreAware classIgnore = declaringClass.getAnnotation(IgnoreAware.class); IgnoreAware methodIgnore = returnType.getMethod().getAnnotation(IgnoreAware.class); if (classIgnore != null || methodIgnore != null || FastResult.class.equals(returnType.getGenericParameterType())) { return false; } for (int i = 0; i < basePackages.length; i++) { if (declaringClass.getPackage().getName().startsWith(basePackages[i])) { return true; } } return false; } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { FastResult<Object> result = new FastResult<>(); result.setData(body); if (returnType.getGenericParameterType().equals(String.class)) { try { response.getHeaders().set("Content-Type", "application/json;charset=utf-8"); return objectMapper.writeValueAsString(result); } catch (JsonProcessingException e) { e.printStackTrace(); } } return result; } }
看完上述內(nèi)容,你們掌握SpringMVC中如何利用@ControllerAdvice和ResponseBodyAdvice接口統(tǒng)一處理返回值的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。