在REST风格的开发中,避免一般会告知前台返回是否成功以及状态码等信息。这里咱们一般返回的时候作一次util
的包装处理工做,如:Result
相似的类,里面包含succ
、code
、msg
、data
等字段。前端
接口调用返回相似以下:java
{ "succ": false, // 是否成功 "ts": 1566467628851, // 时间戳 "data": null, // 数据 "code": "CLOUD800", // 错误类型 "msg": "业务异常", // 错误描述 "fail": true }
固然在每一个接口里返回要经过Result
的工具类将这些信息给封装一下,这样致使业务和技术类的代码耦合在一块儿。git
接口调用处理相似以下:github
@GetMapping("hello") public Result list(){ return Result.ofSuccess("hello"); }
结果:spring
{ "succ": ture, // 是否成功 "ts": 1566467628851, // 时间戳 "data": "hello", // 数据 "code": null, // 错误类型 "msg": null, // 错误描述 "fail": true }
咱们将这些操抽出一个公共starter
包,各个服务依赖便可,作一层统一拦截处理的工做,进行技术解耦。json
unified-dispose-springboot-starterspringboot
这个模块里包含异常处理以及全局返回封装等功能,下面。服务器
完整目录结构以下:数据结构
├── pom.xml ├── src │ ├── main │ │ ├── java │ │ │ └── com │ │ │ └── purgetiem │ │ │ └── starter │ │ │ └── dispose │ │ │ ├── GlobalDefaultConfiguration.java │ │ │ ├── GlobalDefaultProperties.java │ │ │ ├── Interceptors.java │ │ │ ├── Result.java │ │ │ ├── advice │ │ │ │ └── CommonResponseDataAdvice.java │ │ │ ├── annotation │ │ │ │ ├── EnableGlobalDispose.java │ │ │ │ └── IgnorReponseAdvice.java │ │ │ └── exception │ │ │ ├── GlobalDefaultExceptionHandler.java │ │ │ ├── category │ │ │ │ └── BusinessException.java │ │ │ └── error │ │ │ ├── CommonErrorCode.java │ │ │ └── details │ │ │ └── BusinessErrorCode.java │ │ └── resources │ │ ├── META-INF │ │ │ └── spring.factories │ │ └── dispose.properties │ └── test │ └── java
统一返回处理app
按照通常的模式,咱们都须要建立一个能够进行处理包装的工具类以及一个返回对象。
Result(返回类):
建立Result<T>
T
为data
的数据类型,这个类包含了前端经常使用的字段,还有一些经常使用的静态初始化Result
对象的方法。
/** * 返回统一数据结构 * * @author purgeyao * @since 1.0 */ @Data @ToString @NoArgsConstructor @AllArgsConstructor public class Result<T> implements Serializable { /** * 是否成功 */ private Boolean succ; /** * 服务器当前时间戳 */ private Long ts = System.currentTimeMillis(); /** * 成功数据 */ private T data; /** * 错误码 */ private String code; /** * 错误描述 */ private String msg; public static Result ofSuccess() { Result result = new Result(); result.succ = true; return result; } public static Result ofSuccess(Object data) { Result result = new Result(); result.succ = true; result.setData(data); return result; } public static Result ofFail(String code, String msg) { Result result = new Result(); result.succ = false; result.code = code; result.msg = msg; return result; } public static Result ofFail(String code, String msg, Object data) { Result result = new Result(); result.succ = false; result.code = code; result.msg = msg; result.setData(data); return result; } public static Result ofFail(CommonErrorCode resultEnum) { Result result = new Result(); result.succ = false; result.code = resultEnum.getCode(); result.msg = resultEnum.getMessage(); return result; } /** * 获取 json */ public String buildResultJson(){ JSONObject jsonObject = new JSONObject(); jsonObject.put("succ", this.succ); jsonObject.put("code", this.code); jsonObject.put("ts", this.ts); jsonObject.put("msg", this.msg); jsonObject.put("data", this.data); return JSON.toJSONString(jsonObject, SerializerFeature.DisableCircularReferenceDetect); } }
这样已经知足通常返回处理的需求了,在接口能够这样使用:
@GetMapping("hello") public Result list(){ return Result.ofSuccess("hello"); }
固然这样是耦合的使用,每次都须要调用Result
里的包装方法。
ResponseBodyAdvice
返回统一拦截处理
ResponseBodyAdvice
在 spring 4.1 新加入的一个接口,在消息体被HttpMessageConverter
写入以前容许Controller
中 @ResponseBody
修饰的方法或ResponseEntity
调整响应中的内容,好比作一些返回处理。
ResponseBodyAdvice
接口里一共包含了两个方法
supports
:该组件是否支持给定的控制器方法返回类型和选择的{@code HttpMessageConverter}类型
beforeBodyWrite
:在选择{@code HttpMessageConverter}以后调用,在调用其写方法以前调用。
那么咱们就能够在这两个方法作一些手脚。
supports
用于判断是否须要作处理。
beforeBodyWrite
用于作返回处理。
CommonResponseDataAdvice
类实现ResponseBodyAdvice
两个方法。
filter(MethodParameter methodParameter)
私有方法里进行判断是否要进行拦截统一返回处理。
如:
@IgnorReponseAdvice
忽略拦截。swagger
的springfox.documentation
包下的接口忽略拦截等。filter方法:
判断为false就不须要进行拦截处理。
private Boolean filter(MethodParameter methodParameter) { Class<?> declaringClass = methodParameter.getDeclaringClass(); // 检查过滤包路径 long count = globalDefaultProperties.getAdviceFilterPackage().stream() .filter(l -> declaringClass.getName().contains(l)).count(); if (count > 0) { return false; } // 检查<类>过滤列表 if (globalDefaultProperties.getAdviceFilterClass().contains(declaringClass.getName())) { return false; } // 检查注解是否存在 if (methodParameter.getDeclaringClass().isAnnotationPresent(IgnorReponseAdvice.class)) { return false; } if (methodParameter.getMethod().isAnnotationPresent(IgnorReponseAdvice.class)) { return false; } return true; }
CommonResponseDataAdvice类:
最核心的就在beforeBodyWrite
方法处理里。
Object o
是否为null
,为null
构建Result
对象进行返回。Object o
是不是Result
子类或其自己,该状况下,多是接口返回时建立了Result
,为了不再次封装一次,判断是Result
子类或其自己就返回Object o
自己。Object o
是不是为String
,在测试的过程当中发现String
的特殊状况,在这里作了一次判断操做,若是为String
就进行JSON.toJSON(Result.ofSuccess(o)).toString()
序列号操做。Result.ofSuccess(o)
进行包装处理。/** * {@link IgnorReponseAdvice} 处理解析 {@link ResponseBodyAdvice} 统一返回包装器 * * @author purgeyao * @since 1.0 */ @RestControllerAdvice public class CommonResponseDataAdvice implements ResponseBodyAdvice<Object> { private GlobalDefaultProperties globalDefaultProperties; public CommonResponseDataAdvice(GlobalDefaultProperties globalDefaultProperties) { this.globalDefaultProperties = globalDefaultProperties; } @Override @SuppressWarnings("all") public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) { return filter(methodParameter); } @Nullable @Override @SuppressWarnings("all") public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { // o is null -> return response if (o == null) { return Result.ofSuccess(); } // o is instanceof ConmmonResponse -> return o if (o instanceof Result) { return (Result<Object>) o; } // string 特殊处理 if (o instanceof String) { return JSON.toJSON(Result.ofSuccess(o)).toString(); } return Result.ofSuccess(o); } private Boolean filter(MethodParameter methodParameter) { ···略 } }
这样基本完成了核心的处理工做。固然还少了上文提到的@IgnorReponseAdvice
注解。
@IgnorReponseAdvice:
比较简单点,只做为一个标识的做用。
/** * 统一返回包装标识注解 * * @author purgeyao * @since 1.0 */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface IgnorReponseAdvice { }
加入spring容器
最后将GlobalDefaultExceptionHandler
以bean
的方式注入spring
容器。
@Configuration @EnableConfigurationProperties(GlobalDefaultProperties.class) @PropertySource(value = "classpath:dispose.properties", encoding = "UTF-8") public class GlobalDefaultConfiguration { ···略 @Bean public CommonResponseDataAdvice commonResponseDataAdvice(GlobalDefaultProperties globalDefaultProperties){ return new CommonResponseDataAdvice(globalDefaultProperties); } }
将GlobalDefaultConfiguration
在resources/META-INF/spring.factories
文件下加载。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.purgetime.starter.dispose.GlobalDefaultConfiguration
不过咱们此次使用注解方式开启。其余项目依赖包后,须要添加@EnableGlobalDispose
才能够将全局拦截的特性开启。
将刚刚建立的spring.factories
注释掉,建立EnableGlobalDispose
注解。
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Import(GlobalDefaultConfiguration.class) public @interface EnableGlobalDispose { }
使用@Import
将GlobalDefaultConfiguration
导入便可。
使用
添加依赖
<dependency> <groupId>com.purgeteam</groupId> <artifactId>unified-dispose-deepblueai-starter</artifactId> <version>0.1.1.RELEASE</version> </dependency>
启动类开启@EnableGlobalDispose
注解便可。
- 业务使用
接口:
@GetMapping("test") public String test(){ return "test"; }
返回
{ "succ": true, // 是否成功 "ts": 1566386951005, // 时间戳 "data": "test", // 数据 "code": null, // 错误类型 "msg": null, // 错误描述 "fail": false }
- 忽略封装注解:@IgnorReponseAdvice
@IgnorReponseAdvice
容许范围为:类 + 方法,标识在类上这个类下的说有方法的返回都将忽略返回封装。
接口:
@IgnorReponseAdvice // 忽略数据包装 可添加到类、方法上 @GetMapping("test") public String test(){ return "test"; }
返回 test
项目里不少重复的code,咱们能够经过必定的方式去简化,以达到必定目的减小开发量。
示例代码地址:unified-dispose-springboot
做者GitHub:
Purgeyao 欢迎关注