千家信息网

SpringMVC中如何利用@ControllerAdvice和ResponseBodyAdvice接口统一处理返回值

发表于:2025-02-06 作者:千家信息网编辑
千家信息网最后更新 2025年02月06日,SpringMVC中如何利用@ControllerAdvice和ResponseBodyAdvice接口统一处理返回值,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这
千家信息网最后更新 2025年02月06日SpringMVC中如何利用@ControllerAdvice和ResponseBodyAdvice接口统一处理返回值

SpringMVC中如何利用@ControllerAdvice和ResponseBodyAdvice接口统一处理返回值,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。

在我们进行Java的Web应用开发时,如何写更少的代码,做更多的事情。如何让开发更容易上手,更专注于业务层面,不需要太关心底层的实现。这里就分享一些我平时在搭建基础框架时候的一些心得体验。

统一处理返回值

在web应用中,通常前后端会定义一个统一的对象来封装返回值,一般除了业务数据之外,可能会包含一些请求相关的数据

例如以下这个对象

  • code来标识整个请求的结果

  • msg用于返回错误信息

  • data用于返回实际的业务数据。

{        "code": 0,        "msg": "success",        "data": {}}

统一封装的好处就是前端可以使用统一的逻辑进行请求处理,能够编写通用代码来处理返回值。

当然这也需要后端做一定的开发。通常我们都是直接写在代码里面,手动去创建一个封装对象,然后将数据set进去,或者是封装类添加一些静态方法之类的。 在大部分情况下,这些工作都是重复的。

ResponseBodyAdvice 的执行流程

今天介绍的这个接口, ResponseBodyAdvice, 这是由SpringMvc提供的一个接口,在消息转换前处理返回值,源码如下:

public interface ResponseBodyAdvice{        boolean supports(MethodParameter returnType, Class> converterType);        T beforeBodyWrite(T body, MethodParameter returnType, MediaType selectedContentType,                        Class> selectedConverterType,                        ServerHttpRequest request, ServerHttpResponse response);}

这个接口在返回值被消息转换器写回前端之前进行处理, 大致处理流程如下:

我们实现这个接口的代码主要在这个方法里被调用 RequestResponseBodyAdviceChain.processBody, 可以看到这一段逻辑很简单

先执行ResponseBodyAdvice.supports看当前切面类是否支持,如果支持再调用ResponseBodyAdvice.beforeBodyWrite方法并返回

返回值会被 HttpMessageConverter.write 接口在进行最终的转换(例如转JSON),然后写回前端

private  Object processBody(@Nullable Object body, MethodParameter returnType, MediaType contentType,                Class> converterType,                ServerHttpRequest request, ServerHttpResponse response) {        for (ResponseBodyAdvice advice : getMatchingAdvice(returnType, ResponseBodyAdvice.class)) {                if (advice.supports(returnType, converterType)) {                        body = ((ResponseBodyAdvice) advice).beforeBodyWrite((T) body, returnType,                                        contentType, converterType, request, response);                }        }        return body;}

ResponseBodyAdvice 的初始化

SpringMVC在初始化的时候, 会调用RequestMappingHandlerAdapter.initControllerAdviceCache,将ResponseBodyAdvice初始化到容器中

里面会调用ControllerAdviceBean.findAnnotatedBeans ,获取所有带有 @ControllerAdvice 注解的类

将所有实现了 ResponseBodyAdvice 接口的Bean放入requestResponseBodyAdviceBeans中, 在之前介绍到的 getAdvice() 方法取得就是该对象。

//代码片段public static List 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 attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);        if (!attrMethods.isEmpty()) {                this.modelAttributeAdviceCache.put(adviceBean, attrMethods);        }        Set 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);        }}

了解到这些,我们实现一个通用的返回值处理就很简单了, 只需要实现 ResponseBodyAdvice 接口,并且加上 @ControllerAdvice 注解就可以了

这是我实现的一个,统一封装返回值的实现, 大家可以参考一下,根据自己的业务需求来进行修改

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;/** * 统一返回数据封装 * @author Diamond */@ControllerAdvicepublic class FastMvcResponseBodyAwareAdvice implements ResponseBodyAdvice {    private final Map 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> 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> selectedConverterType, ServerHttpRequest request,                                  ServerHttpResponse response) {        FastResult 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;    }}

看完上述内容,你们掌握SpringMVC中如何利用@ControllerAdvice和ResponseBodyAdvice接口统一处理返回值的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注行业资讯频道,感谢各位的阅读!

0