溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

SpringMVC中如何利用@ControllerAdvice和ResponseBodyAdvice接口統(tǒng)一處理返回值

發(fā)布時(shí)間:2021-10-19 17:27:32 來源:億速云 閱讀:121 作者:柒染 欄目:大數(shù)據(jù)

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)。

統(tǒng)一處理返回值

在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ù)的。

ResponseBodyAdvice 的執(zhí)行流程

今天介紹的這個(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)行處理, 大致處理流程如下:

SpringMVC中如何利用@ControllerAdvice和ResponseBodyAdvice接口統(tǒng)一處理返回值

我們實(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;
}

ResponseBodyAdvice 的初始化

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è)資訊頻道,感謝各位的閱讀!

向AI問一下細(xì)節(jié)

免責(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)容。

AI