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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/icarusliu/article/details/80515222

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

{"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;
    }
}

猜你喜欢

转载自blog.csdn.net/icarusliu/article/details/80515222