优雅的实现接口统一返回,看着真舒服

>>强大,10k+点赞的 SpringBoot 后台管理系统竟然出了详细教程!

前言

相信你已经看过铺天盖的统一风格返回的文章了,什么切面得都给你用上,在自定义一个Result类,类中有code、msg、data字段,是不是很熟悉,但是加切面确实有点大体小做了,其实SpringBoot中还可以用@RestControllerAdvice来做统一返回。

一般都知道他是处理异常的,做全局异常统一返回,但是当标有@RestControllerAdvice的类实现ResponseBodyAdvice接口,那他会被赋予一个额外的功能,这个功能就是在向客户端真正输出信息之前,在尝试做最后一次返回值的修改。

@RestControllerAdvice原理

这些都是SpringMVC中的功能,理解@RestControllerAdvice还要追随到DispatcherServlet下,当DispatcherServlet调用到我们的Controller下,如果发生异常,SpringMVC会进行捕获,最后会进入下面这个方法进行结果处理,最后一个参数就是我们Controller下发生的异常信息。

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

在processDispatchResult中就会先判断有没有异常发生,有的话会交给HandlerExceptionResolver去处理。

/**
 * 处理异常,如果异常不为null
 */

if (exception != null) {
   if (exception instanceof ModelAndViewDefiningException) {
      logger.debug("ModelAndViewDefiningException encountered", exception);
      mv = ((ModelAndViewDefiningException) exception).getModelAndView();
   } else {
      Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
      //交给异常转换器,把这个异常处理成为ModelAndView
      mv = processHandlerException(request, response, handler, exception);
      errorView = (mv != null);
   }
}

而HandlerExceptionResolver下由SpringMVC提供的实现类会收集所有带有ControllerAdvice注解的类,这里的收集就是从容器中查找,如果有异常,会尝试交给这些带有ControllerAdvice的类去处理,同时会判断这个类是不是ResponseBodyAdvice的实现类,如果是,则保存起来,为最后向客户端输出之前做准备,也就是上面我们说的调用所有ResponseBodyAdvice实现类去尝试做最后的数据更改。

if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
   this.responseBodyAdvice.add(adviceBean);
}

中间就不看了,我们在看最后,也就是向客户端输出内容时,需要进入下面这个类中的一个方法。

AbstractMessageConverterMethodProcessor.writeWithMessageConverters

调用getAdvice()获取一个调用链,因为ResponseBodyAdvice可能不止一个,所以要把他们形成一个链,依次调用,上一个ResponseBodyAdvice的返回值会传递给下一个ResponseBodyAdvice作为参数。

body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
      (Class<? extends HttpMessageConverter<?>>) converter.getClass(),
      inputMessage, outputMessage);

示例

到这里基本就结束了,下面我们编写一个ResponseBodyAdvice作为示例

下面定义一个接口,多了一个自定义注解@ResultResponse,用来表示会将这个返回值封装为另一个对象。

class User {
    var name: String = ""
}
@RestController
class TestController {
    @ResultResponse
    @GetMapping("test")
    fun test(): User {
        return User().apply { this.name="zhangsan" }
    }
}

在看ResponseBodyAdvice的实现类,泛型表示输入的类型,也表示输出的类型,输入类型是Controller的返回值,输出类型是我们最后尝试封装成其他对象的类型,所以没办法用具体类型,需要用Any表示。

data class Result( val code: Int,  val msg: String,  val data: Any) {}

fun create(code: Int, msg: String, data: Any?): Result {
    data?.run { return Result(code, msg, data) }
    return Result(code, msg, "null")
}

fun createWithSuccess(data: Any?): Result {
    return create(0"OK", data)
}

@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class ResultResponse()

@RestControllerAdvice
class TestAdvice : ResponseBodyAdvice<Any> 
{
    override fun supports(returnType: MethodParameter, converterType: Class<out HttpMessageConverter<*>>): Boolean {
        var resultResponse: ResultResponse? = returnType.getMethodAnnotation(ResultResponse::class.java)
            ?: AnnotationUtils.findAnnotation(returnType.containingClassResultResponse::class.java)
        return resultResponse !
null
    }

    override fun beforeBodyWrite(
        body: Any?,
        returnType: MethodParameter,
        selectedContentType: MediaType,
        selectedConverterType: Class<out HttpMessageConverter<*>>,
        request: ServerHttpRequest,
        response: ServerHttpResponse
    )
: Result 
{
        return createWithSuccess(body)
    }
}

supports方法用来判断需不需要转换,参数说明如下:

returnType:返回值类型封装,也就是我们Controller的返回值

converterType:内部会选择一个HttpMessageConverter用来HTTP消息转换

beforeBodyWrite参数说明如下

body: 返回值

returnType: 返回值类型

selectedContentType:Media类型

selectedConverterType:HttpMessageConverter

request和response就不用说了吧。

这样当我们方法上或者类上标明了ResultResponse注解后,实际就会被转换为下面这种格式,

{"code":0,"msg":"OK","data":{"name":"zhangsan"}}

- END -


原文始发于微信公众号(十四个字节):优雅的实现接口统一返回,看着真舒服