Spring MVC 统一返回格式

1. AOP

利用环绕通知,对包含@RequestMapping注解的方法统一处理
优点:配置简单、可捕获功能方法内部的异常
缺点:aop不能修改返回结果的类型,因此功能方法的返回值须统一为Object类型

2. 过滤器

在过滤器层统一处理
优点:配置简单
缺点:无法识别异常结果,须对返回结果进行额外的反序列化

3. 拦截器

获取返回值不方便,且无法获取到String类型的返回值,无法实现该功能

4. HandlerMethodReturnValueHandler

无上述各方法的缺点,且能复用@ResponseBody等注解

SpringMVC流程
在SpringMVC流程中,在调用 HandlerAdapter 的 处理方法的时候 会跳转调用到 RequestMappingHandlerAdapter 的 handleInternal 方法。这里面会将 原本的处理器 HandlerMethod 封装成 ServletInvocableHandlerMethod,然后会调用这个类中的 invokeAndHandle 方法,这个方法中主要进行了相应方法处理器的方法的调用,在调用完成后,会处理返回值。

this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);

在handleReturnValue方法中,会查找合适的处理器Processor,并通过supportsReturnType方法判断是否支持。

若使用了@ResponseBody注解的接口,都会被RequestResponseBodyMethodProcessor处理。所以我们可以在该处理器处理之前,将返回值修改成统一的JSON格式。

具体实现
在初始化ResponseBodyWrapBean时,获取所有的SpringMVC的内部HandlerMethodReturnValueHandler实现,并将RequestResponseBodyMethodProcessor替换为自定义实现ApiReturnValueHandler,并在ApiReturnValueHandler保存RequestResponseBodyMethodProcessor的实例。

@Component
public class ResponseBodyWrapBean implements InitializingBean {

    @Autowired
    private RequestMappingHandlerAdapter adapter;

    @Value("${server.servlet.context-path:/}")
    private String contextPath;

    @Override
    public void afterPropertiesSet() throws Exception {
        List<HandlerMethodReturnValueHandler> returnValueHandlers = adapter.getReturnValueHandlers();
        List<HandlerMethodReturnValueHandler> handlers = new ArrayList(returnValueHandlers);

        // 在handlers中将RequestResponseBodyMethodProcessor替换为ApiReturnValueWrapHandler,
        // 并在ApiReturnValueWrapHandler保存RequestResponseBodyMethodProcessor
        for (HandlerMethodReturnValueHandler handler : handlers) {
            if (handler instanceof RequestResponseBodyMethodProcessor) {
                ApiReturnValueHandler apiReturnValueHandler = new ApiReturnValueHandler(handler,contextPath);
                int index = handlers.indexOf(handler);
                handlers.set(index, apiReturnValueHandler);
                break;
            }
        }
        
        adapter.setReturnValueHandlers(handlers);
    }

}

ApiReturnValueHandler实现HandlerMethodReturnValueHandler接口,并在类里面保存RequestResponseBodyMethodProcessor的实例,使用RequestResponseBodyMethodProcessor的supportsReturnType原实现,即判断类或方法是否有@ResponseBody注解。如果有,就会执行该handleReturnValue方法。

在handleReturnValue方法中将返回值包装后,就继续按RequestResponseBodyMethodProcessor的原实现继续执行。

public class ApiReturnValueHandler implements HandlerMethodReturnValueHandler {

    // 因为RequestResponseBodyMethodProcessor被替换,所以将其保存下来,处理后在调用原逻辑
    private final HandlerMethodReturnValueHandler handler;

    private final String contextPath;

    public ApiReturnValueHandler(HandlerMethodReturnValueHandler handler, String contextPath) {
        this.handler = handler;
        this.contextPath = contextPath;
    }

    /**
     * 判断是否支持值处理引擎
     * 一定要正确,如果不正确,就永远不会被执行
     * @param returnType
     * @return
     */
    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        return handler.supportsReturnType(returnType);
    }

    /**
     * 对值进行处理,并确定是否继续进行下一个处理引擎执行
     * @param returnValue
     * @param returnType
     * @param mavContainer
     * @param webRequest
     * @throws Exception
     */
    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        String uri = ((HttpServletRequest) webRequest.getNativeRequest()).getRequestURI();
        String apiUrl = contextPath + "/api";
        if (uri.startsWith(apiUrl)) {
            // 将api开头的url都包装成统一返回
            Map<String, Object> result = new HashMap<>(3);
            result.put("code", 0);
            result.put("message", "ok");
            result.put("data", returnValue);
            handler.handleReturnValue(result, returnType, mavContainer, webRequest);
        } else {
            handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
        }
    }
}

测试:
api测试
非api测试

5. ResponseBodyAdvice

无上述各方法的缺点,且能复用@ResponseBody等注解。

ResponseBodyAdvice方法是在执行完HandlerMethodReturnValueHandler之后执行,全局捕获的异常返回,也会经过该方法。

借助@RestControllerAdviceResponseBodyAdvice<T>来对项目的每一个@RestController标记的控制类的响应体进行后置切面通知处理。

@RestControllerAdvice
public class ResponseBodyHandler implements ResponseBodyAdvice<Object> {

    @Value("${server.servlet.context-path:}")
    private String contextPath;

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if (body instanceof Result) {
            return body;
        } else {
            String apiUrl;
            if (contextPath != null && contextPath != "") {
                apiUrl = contextPath + "/api";
            } else {
                apiUrl = "/api";
            }

            HttpServletRequest httpServletRequest = ((ServletServerHttpRequest) request).getServletRequest();
            String url = httpServletRequest.getRequestURI();
            // api开头的url才进行包装
            if (url.startsWith(apiUrl)) {
                // String类型特别处理,防止发生类型转换异常
                if (body instanceof String) {
                    return JSON.toJSONString(Result.okData(body));
                } else {
                    return Result.okData(body);
                }
            } else {
                return body;
            }
        }
    }
}

统一返回类

public class Result<T> {

    private Integer code = 200;

    private String message = "ok";

    private T data;

    public static Result<?> ok() {
        return new Result<>();
    }

    public static <T> Result<T> okData(T data) {
        Result<T> result = new Result<>();
        result.setData(data);
        return result;
    }

    public static <T> Result<T> okData(T data, String message) {
        Result<T> result = new Result<>();
        result.setMessage(message);
        result.setData(data);
        return result;
    }

    public static <T> Result<T> build(Integer code, T data, String message) {
        Result<T> result = new Result<>();
        result.setCode(code);
        result.setMessage(message);
        result.setData(data);
        return result;
    }

    public static Result<?> failure(Integer code, String message) {
        Result<?> result = new Result<>();
        result.setCode(code);
        result.setMessage(message);
        return result;
    }
	// getter and setter

参考:
自定义统一api返回json格式(app后台框架搭建三)
Spring MVC 使用介绍(十二)控制器返回结果统一处理
SpringMVC 流程
从零搭建Spring Boot脚手架(2):增加通用的功能
Springboot使用了ResponseBodyAdvice处理返回值异常?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值