Spring统一格式返回

目录

一:统一结果返回

1:统一结果返回写法

2:String类型报错问题

解决方法

二:统一异常返回

统一异常返回写法

三:总结


同志们,今天咱来讲一讲统一格式返回啊,也是好久没有讲过统一格式返回了,说实话这个统一格式返回就是一个让咱返回的数据能有一个统一的格式嘛,不为别的就为了和你一块共事的同事不会提着刀追着你满大街跑,要跟你交流交流工作经验。

前端:我这数据咋老是对不上啊?

后端:奥兄弟,这个接口我返回的是Boolean类型

前端:我这数据怎么又对不上了啊?

后端:奥好兄弟,我觉得Boolean类型太丑了我就换成String类型了,我觉得它顺眼点。

前端:

那咱肯定不能让同事追着我们交流经验啊,所以我们一定要让咱返回的数据能有一个统一的格式

一:统一结果返回

1:统一结果返回写法

定义返回模板

想要统一返回一个结果,就肯定要有一个返回结果的模板这里我们用Result类来定义

下面代码中有Result有三个属性,状态码,错误信息,返回的数据,三个方法分别是成功时返回,和失败时返回。

java 复制代码
@Data
public class Result<T> {
    private Integer code;//后端响应状态码,成功200,失败-1,-2表示未登录
    private String errmsg;//后端发生错误的原因
    private T data;//每个接口返回的类型(BookInfo,boolean之类的,类型不固定所以要用泛型)
    /*
    * 成功时设置
    * */
    public  static<T> Result<T> success(T data){//泛型方法加static需要加上<T>
        Result result = new Result<>();
        result.setData(data);
        result.setCode(200);
        return result;
    }
    /*
     失败时设置
    * */
    public  static<T> Result<T> fail(String errMsg){
        Result result = new Result<>();

        result.setCode(-1);
        result.setErrmsg(errMsg);
        return result;
    }
    public  static<T> Result<T> fail(T data,String errMsg){
        Result result = new Result<>();
        result.setData(data);
        result.setCode(-1);
        result.setErrmsg(errMsg);
        return result;
    }
}

实现ResponseBodyAdvice接口并重写方法,再加上@ControllerAdvice注解

@ControllerAdvice:这个注解的作用就是标记这个类为全局控制器增强类

java 复制代码
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        return Result.success(body);//返回封装后结果结果
    }
}

support()方法:

作用:

supports 方法用于判断是否需要对某个返回值进行处理,返回true就是处理,返回false就是不处理

参数解析:

returnType:这个参数就是表示控制器方法(Controller类)的返回类型信息,它封装的有方法的返回参数的详细信息,比如方法本身,方法所属的类,返回的类型等等...可以根据这些信息来决定是否要对返回值进行处理,返回对应的true或false

Class converterType:这个参数的意思就是用于将响应体对象,转换为,HTTP响应消息的那个消息转换器的类型,后面我们会说到,先按下不表

beforeBodyWrite()方法:

作用:

我们直译一下这个方法的名字是:写 body 之前 ,意思就是在控制器方法返回之前进行的处理

当support方法返回true之后,beforeBodyWrite()方法就需要对返回值进行一些处理

参数解析:

Object body:该参数表示控制器方法实际返回的响应体对象

MethodParameter returnType:同supports方法中的returnType

MediaType selectedContentType:表示响应的类型,如application/json

Class selectedConverterType:表示用于将响应体对象转换为 HTTP 响应的消息转换器的类型

request和response:这个我们很熟悉,就是代表Http的请求和响应

我们定义几个简单的控制器类

java 复制代码
@RestController
public class Controller {
    @RequestMapping("/t1")
    public int t1(){
        return 1;
    }
    @RequestMapping("/t2")
    public Boolean t2(){
        return true;
    }
    @RequestMapping("/t2")
    public String t3(){
        return "老皇甫";
    }
}

然后启动项目测试发现

t1和t2方法没问题,都对控制器的返回结果进行了封装,但是返回String类型的t3却报错了,这是为什么呢?

2:String类型报错问题

首先明确一点,这个报错的原因是类型不匹配问题,报的是ClassCastException

我黄色框框里面框的翻译一下就是Result类型和String类型不匹配,那么为什么呢?

还记得我在解释方法的参数的时候那个先按下不表的Class converterType 参数吗?converterType的意思是转换器类型,SpringMVC默认会注册⼀些⾃带的 HttpMessageConverter(Http消息转换器),它是以链表的形式组织的,它们的顺序是

复制代码
 ByteArrayHttpMessageConverter()->StringHttpMessageConverter()
->SourceHttpMessageConverter<>()->AllEncompassingFormHttpMessageConverter()

其中这个AllEncompassingFormHttpMessageConverter()是根据项目的依赖情况来添加对应的转换器的,如果我们添加了Jackson依赖一般会添加MappingJackson2HttpMessageConverter()转换器到消息转换器链表的末尾

Spring会根据返回的数据类型, 从 messageConverters 链(就是那个链表)选择 合适的消息转换器 .
当返回的数据是**⾮字符串时,使⽤的 MappingJackson2HttpMessageConverter 写⼊返回对象(那个在链表末尾的消息转换器)
.
当返回的数据
是字符串时**, StringHttpMessageConverter 会先被遍历到,这时会认为
StringHttpMessageConverter 可以使⽤,然后就会用StringHttpMessageConverter

我们下面调用的堆栈信息中也发现,最后AbstractMessageConverterMethodProcessor调用的也是StringHttpMessageConverter

这里是AbstractMessageConverterMethodProcessor中的逻辑,body在经过beforeBody方法包装过之后,就会从String类型变为Result类型,但现在匹配到的还是StringHttpMessageConverter 消息转换器

我们点进去这个 StringHttpMessageConverter 的write方法中看一下(点击那个由AbstractHttpMessageConverter实现的

)发现里面有一个addDefaultHeader方法(由 StringHttpMessageConverter实现的),再点进去这个方法,发现这个方法接收到的是String参数,但在上面的我们在beforBodyWrite方法中已经将参数转换为了Result类型,所以才会报出类型不匹配异常

解决方法

既然是因为参数不匹配导致的错误,那就只需要将参数搞成匹配的就行了,如下图所示,如果返回的是字符串类型,那么就将返回类型序列化成字符串类型,而不是Result类型

我知道同志们有时候看源码很懵,不知道哪个调用哪个,这里可以说一下在控制台打印的日志中,调用的顺序一般就是下面的调用上面的,一层一层的,Spring的调用链几乎都有十几层,所以看的很懵是很正常的

二:统一异常返回

还有一个问题,不知道同志们发现没有,就是上面我们在由于String类型不匹配报错的时候哪个返回结果状态码竟然还是200,但这个200可是成功的状态码,这都报错了,那状态码肯定能是200啊,所以这个返回结果肯定是不正确的。

统一异常返回写法

这时候我们就需要通过统一异常捕获,构造出另一种返回结果失败的格式,来返回我们可以用
@ControllerAdvice + @ExceptionHandler 两个注解来实现
写法非常简单,就是加上个@ExceptionHandler后面指定要捕获哪种类型的异常,然后进行对应的封装逻辑,Exception你也可以进行自定义异常,来满足你不同的项目需求
因为我们希望给客户端返回的是数据类型,而不是一个视图,所以要加上**@ResponseBody注解**

java 复制代码
@ControllerAdvice
@ResponseBody
public class ExceptionAdvice {
    @ExceptionHandler(Exception.class)//捕获所有异常
    public Result handleException(Exception e) {
        return Result.fail(e.getMessage()+"异常");
    }
    @ExceptionHandler(Error.class)//捕获error类型
    public Result handleError(Error e) {
        return Result.fail(e.getMessage()+"错误");
    }
    @ExceptionHandler(RuntimeException.class)//捕获运行时异常
    public Result handleRuntimeException(RuntimeException e) {
        return Result.fail(e.getMessage()+"运行时异常");
    }
    @ExceptionHandler(NullPointerException.class)//捕获空指针异常
    public Result handleNullPointerException(NullPointerException e) {
        return Result.fail(e.getMessage()+"空指针异常");
    }


}

我们来认为制造几个异常

查看测试结果

状态码为-1,返回结果符合预期

三:总结

这篇我们说的也不多,大概就是说了一下

为什么要进行统一的格式返回,

然后统一格式返回的写法

再是String类型会报错的问题以及源码级别的原因,

然后是统一异常返回问题

相关推荐
考虑考虑33 分钟前
JDK9中的dropWhile
java·后端·java ee
想躺平的咸鱼干41 分钟前
Volatile解决指令重排和单例模式
java·开发语言·单例模式·线程·并发编程
hqxstudying1 小时前
java依赖注入方法
java·spring·log4j·ioc·依赖
·云扬·1 小时前
【Java源码阅读系列37】深度解读Java BufferedReader 源码
java·开发语言
春生野草2 小时前
关于SpringMVC的整理
spring
Bug退退退1232 小时前
RabbitMQ 高级特性之重试机制
java·分布式·spring·rabbitmq
小皮侠2 小时前
nginx的使用
java·运维·服务器·前端·git·nginx·github
Zz_waiting.3 小时前
Javaweb - 10.4 ServletConfig 和 ServletContext
java·开发语言·前端·servlet·servletconfig·servletcontext·域对象
全栈凯哥3 小时前
02.SpringBoot常用Utils工具类详解
java·spring boot·后端
兮动人3 小时前
获取终端外网IP地址
java·网络·网络协议·tcp/ip·获取终端外网ip地址