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

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

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

  • code来标识整个请求的结果
  • msg用于返回错误信息
  • data用于返回实际的业务数据。
{
   
  "code": 0,
  "msg": "success",
  "data": {
   }
}

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

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

ResponseBodyAdvice 的执行流程

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

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);
}

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

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

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

返回值会被 HttpMessageConverter.write 接口在进行最终的转换(例如转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在初始化的时候, 会调用RequestMappingHandlerAdapter.initControllerAdviceCache,将ResponseBodyAdvice初始化到容器中

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

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

//代码片段
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);
	}
}

了解到这些,我们实现一个通用的返回值处理就很简单了, 只需要实现 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
 */
@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;
    }

}

相关推荐

  1. SpringMvc处理器方法的返回

    2023-12-05 20:56:09       14 阅读
  2. 75.实现统一异常处理封装统一返回结果

    2023-12-05 20:56:09       36 阅读
  3. SpringMVC controller方法返回见解3

    2023-12-05 20:56:09       30 阅读
  4. 项目搭建之统一返回

    2023-12-05 20:56:09       19 阅读
  5. nodejs + express 接口统一返回错误信息

    2023-12-05 20:56:09       14 阅读

最近更新

  1. TCP协议是安全的吗?

    2023-12-05 20:56:09       19 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2023-12-05 20:56:09       20 阅读
  3. 【Python教程】压缩PDF文件大小

    2023-12-05 20:56:09       20 阅读
  4. 通过文章id递归查询所有评论(xml)

    2023-12-05 20:56:09       20 阅读

热门阅读

  1. 算法通关村第四关—理解栈手写栈(青铜)

    2023-12-05 20:56:09       39 阅读
  2. flutter记录报错日志

    2023-12-05 20:56:09       43 阅读
  3. 讲讲ES6中 对象合并

    2023-12-05 20:56:09       39 阅读
  4. C语言还会存在多久

    2023-12-05 20:56:09       35 阅读
  5. Kotlin 作用域函数:理解 apply, let, 和 with

    2023-12-05 20:56:09       30 阅读
  6. 设计模式 -职责链模式

    2023-12-05 20:56:09       42 阅读
  7. 9-MapReduce开发技术

    2023-12-05 20:56:09       27 阅读
  8. php获取时间和MongoDB保存时间不一致

    2023-12-05 20:56:09       35 阅读