
本文共 10534 字,大约阅读时间需要 35 分钟。
文章目录
1. 封装代码
代码使用了Lombok
注解。
首先提供一个枚举,用于封装返回的提示码和提示信息。
package com.example.demo.result;import lombok.AllArgsConstructor;import lombok.Getter;/** * @auther wangbo * @date 2021-01-10 15:59 */@Getter@AllArgsConstructorpublic enum ResultCode { //成功提示码 SUCCESS(20000, "成功"), //自定义失败信息 FAILURE(50000, "失败"), //通用错误码 50001~50099 REQUEST_PARAM_ERROR(50001, "请求参数错误"), //用户模块错误码 50100~50199 USER_HAVE_EXISTS(50101, "用户已存在"), USER_NOT_EXISTS(50102, "用户不存在"), USER_PASSWORD_ERROR(50103, "用户密码错误"), USER_TYPE_ERROR(50104, "用户类型错误"), USER_STATUS_ERROR(50105, "用户状态错误"); //商品模块错误码 50200~50299 //订单模块错误码 50300~50399 private Integer code; private String message;}
接下来提供一个返回类型,这里使用了泛型,用于对返回数据进行统一包装。
package com.example.demo.result;import lombok.Data;import java.io.Serializable;/** * @auther wangbo * @date 2021-01-10 16:38 */@Datapublic class Resultimplements Serializable { private static final long serialVersionUID = -8037171286104362012L; private Integer code; private String message; private T data; /** * 成功,无返回数据(静态方法) */ public static Result success() { Result result = new Result(); result.setCode(ResultCode.SUCCESS.getCode()); result.setMessage(ResultCode.SUCCESS.getMessage()); return result; } /** * 成功,有返回数据(非静态方法) */ public Result success(T data){ this.code = ResultCode.SUCCESS.getCode(); this.message = ResultCode.SUCCESS.getMessage(); this.data = data; return this; } /** * 失败,自定义失败信息(静态方法) */ public static Result failure(String message) { Result result = new Result(); result.setCode(ResultCode.FAILURE.getCode()); result.setMessage(message); return result; } /** * 错误,使用已定义枚举(静态方法) */ public static Result error(ResultCode resultCode) { Result result = new Result(); result.setCode(resultCode.getCode()); result.setMessage(resultCode.getMessage()); return result; }}
2. 使用示例
package com.example.demo.controller;import com.example.demo.entity.User;import com.example.demo.result.Result;import com.example.demo.result.ResultCode;import com.example.demo.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.List;/** * @auther wangbo * @date 2021-01-10 17:48 */@RestController@RequestMapping("/user")public class UserController { @Autowired private UserService userService; @GetMapping("/list") public Result
> list(){ List list = userService.list(); Result
> result = new Result<>(); return result.success(list); } @GetMapping("/success") public Result success(){ return Result.success(); } @GetMapping("/failure") public Result failure(){ return Result.failure("测试自定义失败信息"); } @GetMapping("/error") public Result error(){ return Result.error(ResultCode.REQUEST_PARAM_ERROR); }}
3. 结果示例
http://localhost:8080/user/list
{ "code":20000, "message":"成功", "data":[ { "id":1, "name":"姜晓东", "age":32, "email":"jxd@baomidou", "managerId":3, "createTime":"2020-11-04T19:04:22", "remark":null }, { "id":2, "name":"侯波", "age":32, "email":"hb@baomidou", "managerId":3, "createTime":"2020-11-04T19:18:14", "remark":null }, { "id":3, "name":"赵睿", "age":32, "email":"@baomidou", "managerId":2, "createTime":"2020-11-04T19:20:12", "remark":null } ]}
http://localhost:8080/user/success
{ "code":20000, "message":"成功", "data":null}
http://localhost:8080/user/failure
{ "code":50000, "message":"测试自定义失败信息", "data":null}
http://localhost:8080/user/error
{ "code":50001, "message":"请求参数错误", "data":null}
4. 代码优化
对于上面的使用示例,可以继续做如下修改:
- 对于没有返回数据的接口可以继续使用上面的写法,比如增加,删除,更新接口,统一包装类不会再进行重复包装处理。
- 对于有返回数据的接口,比如查询接口,可以把返回值进行统一包装处理,这样每个查询接口返回的就是业务对象,比如
List<User>
,而不包装对象Result<T>
了。统一包装处理会把这些接口的返回值包装为Result<T>
格式。 - 对于一些第三方回调之类的接口,可能要求我们的接口响应格式符合第三方的规定,所以这些接口不需要进行统一包装,我们可以通过在这些接口上添加一个自定义注解来绕过统一包装类的处理。
注意:如果你的项目使用Swagger
的话,其实这样修改会让生成的接口文档中的响应格式不完整。比如对于本文中的/user/list
查询接口,文档中显示的返回结果将会是List<User>
格式,而不是Result<List<User>>
格式。
这种修改将代码复杂化了,非必要情况下不需要这么做!
4.1. 返回值包装
这里使用到了@RestControllerAdvice
注解,该注解既可以全局拦截异常也可拦截正常的返回值,并且可以对返回值进行修改。添加一个接口返回值包装类:
package com.example.demo.advice;import com.example.demo.annotation.NotResultWrap;import com.example.demo.result.Result;import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.databind.ObjectMapper;import lombok.extern.slf4j.Slf4j;import org.springframework.core.MethodParameter;import org.springframework.http.MediaType;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.http.server.ServerHttpRequest;import org.springframework.http.server.ServerHttpResponse;import org.springframework.web.bind.annotation.RestControllerAdvice;import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;/** * 接口返回值包装 * @auther wangbo * @date 2021-01-13 15:55 */@Slf4j@RestControllerAdvice(basePackages = "com.example.demo.controller")public class ResponseControllerAdvice implements ResponseBodyAdvice
写这个类的时候有以下几点注意事项:
(1) @RestControllerAdvice(basePackages = "com.example.demo.controller")
该注解必须写basePackages
属性,用来指定该返回值包装类所处理的接口包。如果不指定这个属性的话,该包装类会拦截所有的响应。比如404
之类的响应,不属于系统异常,不会被全局异常处理类处理,但是会被返回值包装类拦截,并且会被包装,但是这些异常响应其实是不需要也不应该进行包装的。
未指定basePackages
属性,404
的响应,明显是不合理的:
{ "code": 20000, "message": "成功", "data": { "timestamp": "2021-01-17T10:42:04.406+00:00", "status": 404, "error": "Not Found", "message": "", "path": "/user_info/add3" }}
指定了basePackages
属性,404
的响应:
{ "timestamp": "2021-01-17T10:26:00.865+00:00", "status": 404, "error": "Not Found", "message": "", "path": "/user_info/add3"}
(2)对于返回值为String
的接口,需要单独处理,如果没有进行单独处理,程序会抛出一个类转换异常。
SpringMVC
内部定义了九个不同的MessageConverter
用来处理不同的返回值。在AbstractMessageConverterMethodProcessor
类下面的writeWithMessageConverters
方法可以看出来,每个MessageConverer
是根据返回类型和媒体类型来选择处理的MessageConverter
的。
Controller
层中返回的类型是String
,但是在ResponseBodyAdvice
实现类中,我们把响应的类型修改成了Result
。这就导致了Spring
在处理MessageConverter
的时候,依旧根据之前的String
类型选择对应String
类型的StringMessageConverter
。而在StringMessageConverter
类型,他只接受String
类型的返回值,所以我们在ResponseBodyAdvice
中将返回值从String
类型改成Result
类型之后,调用StringMessageConverter
方法发生类型强转。Result
无法转换成String
,发生类型转换异常。所以需要单独处理。
当然,也可以选择对String
返回类型的接口不进行包装处理,直接返回原生String
,这种直接在supports
方法中进行拦截就可以了。
4.2. 自定义枚举
用于标志接口方法不需要进行统一返回值包装的自定义注解:
package com.example.demo.annotation;import java.lang.annotation.Retention;import java.lang.annotation.Target;import static java.lang.annotation.ElementType.METHOD;import static java.lang.annotation.RetentionPolicy.RUNTIME;/** * 不进行返回值包装注解 * @auther wangbo * @date 2021-01-13 20:55 */@Target({ METHOD})@Retention(RUNTIME)public @interface NotResultWrap { }
4.3. 接口修改示例
接口方法可以修改如下,前四个接口的返回结果和上面的示例是一样的:
package com.example.demo.controller;import com.example.demo.annotation.NotResultWrap;import com.example.demo.entity.User;import com.example.demo.result.Result;import com.example.demo.result.ResultCode;import com.example.demo.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.List;/** * @auther wangbo * @date 2021-01-10 17:48 */@RestController@RequestMapping("/user")public class UserController { @Autowired private UserService userService; //只有这个方法进行了返回值改变,去掉了外层的Result包装,在接口返回值包装类中进行统一包装 @GetMapping("/list") public Listlist(){ return userService.list(); } @GetMapping("/success") public Result success(){ return Result.success(); } @GetMapping("/failure") public Result failure(){ return Result.failure("测试自定义失败信息"); } @GetMapping("/error") public Result error(){ return Result.error(ResultCode.REQUEST_PARAM_ERROR); } //测试字符串的包装 @GetMapping("/string") public String string(){ return "测试字符串包装"; } //测试不进行返回值包装注解的效果 @NotResultWrap @GetMapping("/not_wrap") public String notWrap(){ return "测试不包装注解"; }}
后面新加的两个接口的返回结果示例:
http://localhost:8080/user/string
这个接口返回的其实是String
格式,只不过这是个JSON
格式的字符串。 { "code":20000,"message":"成功","data":"测试字符串包装"}
http://localhost:8080/user/not_wrap
测试不包装注解
发表评论
最新留言
关于作者
