欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

8 Spring Boot返回数据及异常统一封装

程序员文章站 2022-06-01 12:57:17
...

项目开发中,一般情况下对数据返回的格式可能会有一个统一的要求,一般会包括状态码、信息及数据三部分。举个例子,假设规范要求数据返回的结构如下所示:

{"data":[{"id":5,"userId":5,"name":"test1","articleCount":0}],"errorMessage":"","statusCode":"200"}

其中,data字段存储实际的返回数据;errorMessage存储当出现异常时的异常信息;statusCode存储处理码;一般用一个特殊的码如200来表示无异常;而出现异常时可以存储具体的异常码。

要返回这样的数据,最直接的做法当然是在每一个Controller中去处理,返回的数据本身就封装有处理码、数据、出现异常时的异常信息等字段。这样做导致的问题,就是每一个Controller向外暴露的方法都要创建一个返回的对象来封装这种处理,并在出现异常时捕获异常进行处理。

因此最好是能够统一处理这种转换,这样的话服务提供者就只需关注他原本就需要处理的事情:一是在无异常时返回数据本身;二是在出现异常时抛出合适的异常。

为达到统一处理的目的,需要针对两个场景做单独的处理:一是当无异常时,在原返回的数据基础上封装一层,将状态码等信息包含进来;二是当出现异常时,将异常信息进行封装然后返回给调用方。

1. 执行无异常时返回数据封装

在Spring Boot中,针对返回值的处理是在HandlerAdapter的returnValueHandlers中进行的。我们先尝试创建一个ReturnValueHandler对象实现HandlerMethodReturnValueHandler接口,然后通过WebMvcConfigurer中的addReturnValueHandlers将其添加。如下所示:

@Configuration
public class WebConfiguration implements WebMvcConfigurer {
    @Autowired
    private RestReturnValueHandler restReturnValueHandler;

    @Override
    public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
        handlers.clear();
        handlers.add(restReturnValueHandler);
    }
}

其中RestReturnValueHanlder定义如下:

/**
 * REST类型的返回值处理器
 * 将REST的返回结果进行进一步的封装,如原本返回的是data,那么封装后将会是:
 * {statusCode: '', errorMessage: '', exception: {}, data: data}
 *
 * @author LiuQI 2018/5/30 10:48
 * @version V1.0
 **/
@Component
public class RestReturnValueHandler implements HandlerMethodReturnValueHandler {
    @Autowired
    private MessageConverter messageConverter;

    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        if (returnType.hasMethodAnnotation(ResponseBody.class)
                || (!returnType.getDeclaringClass().equals(ModelAndView.class))
                    && returnType.getMethod().getDeclaringClass().isAnnotationPresent(RestController.class)) {
            return true;
        }

        return false;
    }

    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest) throws Exception {
        mavContainer.setRequestHandled(true);

        Map<String, Object> resultMap = new HashMap<>();

        resultMap.put("statusCode", STATUS_CODE_SUCCEEDED);
        resultMap.put("data", returnValue);

        HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);

        messageConverter.write(resultMap, MediaType.APPLICATION_JSON_UTF8, new ServletServerHttpResponse(response));
    }

    private static final String STATUS_CODE_SUCCEEDED = "200";
    private static final String STATUS_CODE_INTERNAL_ERROR = "500";
}

然而在测试过程中,发现Rest的请求并未执行这个Handler!最终通过分析源代码,发现Spring Boot本身在RequestMappingHandlerAdapter中注册了一系列的Handler:

private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
        List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>();

        // Single-purpose return value types
        handlers.add(new ModelAndViewMethodReturnValueHandler());
        handlers.add(new ModelMethodProcessor());
        handlers.add(new ViewMethodReturnValueHandler());
        handlers.add(new ResponseBodyEmitterReturnValueHandler(getMessageConverters(),
                this.reactiveAdapterRegistry, this.taskExecutor, this.contentNegotiationManager));
        handlers.add(new StreamingResponseBodyReturnValueHandler());
        handlers.add(new HttpEntityMethodProcessor(getMessageConverters(),
                this.contentNegotiationManager, this.requestResponseBodyAdvice));
        handlers.add(new HttpHeadersReturnValueHandler());
        handlers.add(new CallableMethodReturnValueHandler());
        handlers.add(new DeferredResultMethodReturnValueHandler());
        handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));

        // Annotation-based return value types
        handlers.add(new ModelAttributeMethodProcessor(false));
        handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),
                this.contentNegotiationManager, this.requestResponseBodyAdvice));

        // Multi-purpose return value types
        handlers.add(new ViewNameMethodReturnValueHandler());
        handlers.add(new MapMethodProcessor());

        // Custom return value types
        if (getCustomReturnValueHandlers() != null) {
            handlers.addAll(getCustomReturnValueHandlers());
        }

        // Catch-all
        if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) {
            handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers()));
        }
        else {
            handlers.add(new ModelAttributeMethodProcessor(true));
        }

        return handlers;
    }

其中关键的就是:RequestResponseBodyMethodProcessor,它用于处理REST接口时的返回数据;在添加的时候是先添加这个Processor的,然后才添加getCustomReturnValueHandlers这个方法返回的ValueHandler的。而我们通过WebMvcConfigurer添加进去的ValueHandler是在这个方法里面返回的。而不同的ValueHandler之间又不能通过Order来进行控制,先执行的如果处理过了就不会再执行后续的了。因此只能采取另外的方式进行:一是通过自定义注解的方式;二是通过修改RequestMappingHandlerAdapter中的returnValueHandlers中的值,将RequestResponseBodyMethodProcessor替换成自定义对象。为尽量不变动Controller的开发,此处采用第二种方式进行。

先定义一个RequestResponseBodyMethodProcessor的包装类:

public class HandlerMethodReturnValueHandlerProxy implements HandlerMethodReturnValueHandler {
    private HandlerMethodReturnValueHandler proxyObject;

    public HandlerMethodReturnValueHandlerProxy(HandlerMethodReturnValueHandler proxyObject) {
        this.proxyObject = proxyObject;
    }

    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        return proxyObject.supportsReturnType(returnType);
    }

    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest) throws Exception {
        Map<String, Object> resultMap = new HashMap<>();

        resultMap.put("statusCode", STATUS_CODE_SUCCEEDED);
        resultMap.put("errorMessage", "");
        resultMap.put("data", returnValue);

        proxyObject.handleReturnValue(resultMap, returnType, mavContainer, webRequest);
    }

    private static final String STATUS_CODE_SUCCEEDED = "200";
}

然后通过InitializingBean的方式来修改其属性:

@Configuration
public class RestReturnValueHandlerConfigurer implements InitializingBean {
    @Autowired
    private RequestMappingHandlerAdapter handlerAdapter;

    @Override
    public void afterPropertiesSet() throws Exception {
        List<HandlerMethodReturnValueHandler> list = handlerAdapter.getReturnValueHandlers();
        List<HandlerMethodReturnValueHandler> newList = new ArrayList<>();
        if (null != list) {
            for (HandlerMethodReturnValueHandler valueHandler: list) {
                if (valueHandler instanceof RequestResponseBodyMethodProcessor) {
                    HandlerMethodReturnValueHandlerProxy proxy = new HandlerMethodReturnValueHandlerProxy(valueHandler);
                    newList.add(proxy);
                } else {
                    newList.add(valueHandler);
                }
            }
        }

        handlerAdapter.setReturnValueHandlers(newList);
    }
}

经过这两步就可以了。

2. 执行出现异常时的处理

可以通过ExceptionHandler来进行处理。其实现如下:

@ControllerAdvice
public class ExceptionHandlerAdvice {

    /**
     * 处理Rest接口请求时的异常
     * @param request
     * @param response
     * @param ex
     * @return
     */
    @ExceptionHandler(RestException.class)
    @ResponseBody
    public Map<String, Object> restError(HttpServletRequest request, HttpServletResponse response, Exception ex) {
        RestException restException = (RestException) ex;
        Map<String, Object> map = new HashMap<>();
        map.put("exception", null != restException.getT() ? restException.getT() : restException);
        map.put("errorMessage", restException.getMessage());
        map.put("url", request.getRequestURL());
        map.put("statusCode",  restException.getCode());
        return map;
    }
}
相关标签: Spring Boot