springboot之全局处理统一返回
本文内容纲要:
-springboot之全局处理统一返回
-简介
-配置
-总结
springboot之全局处理统一返回
简介
在REST风格的开发中,避免通常会告知前台返回是否成功以及状态码等信息。这里我们通常返回的时候做一次util
的包装处理工作,如:Result
类似的类,里面包含succ
、code
、msg
、data
等字段。
接口调用返回类似如下:
{
"succ":false,//是否成功
"ts":1566467628851,//时间戳
"data":null,//数据
"code":"CLOUD800",//错误类型
"msg":"业务异常",//错误描述
"fail":true
}
当然在每个接口里返回要通过Result
的工具类将这些信息给封装一下,这样导致业务和技术类的代码耦合在一起。
接口调用处理类似如下:
@GetMapping("hello")
publicResultlist(){
returnResult.ofSuccess("hello");
}
结果:
{
"succ":ture,//是否成功
"ts":1566467628851,//时间戳
"data":"hello",//数据
"code":null,//错误类型
"msg":null,//错误描述
"fail":true
}
我们将这些操抽出一个公共starter
包,各个服务依赖即可,做一层统一拦截处理的工作,进行技术解耦。
配置
unified-dispose-springboot-starter
这个模块里包含异常处理以及全局返回封装等功能,下面。
完整目录结构如下:
├──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
统一返回处理
按照一般的模式,我们都需要创建一个可以进行处理包装的工具类以及一个返回对象。
Result(返回类):
创建Result<T>
T
为data
的数据类型,这个类包含了前端常用的字段,还有一些常用的静态初始化Result
对象的方法。
/**
*返回统一数据结构
*
*@authorpurgeyao
*@since1.0
*/
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
publicclassResult<T>implementsSerializable{
/**
*是否成功
*/
privateBooleansucc;
/**
*服务器当前时间戳
*/
privateLongts=System.currentTimeMillis();
/**
*成功数据
*/
privateTdata;
/**
*错误码
*/
privateStringcode;
/**
*错误描述
*/
privateStringmsg;
publicstaticResultofSuccess(){
Resultresult=newResult();
result.succ=true;
returnresult;
}
publicstaticResultofSuccess(Objectdata){
Resultresult=newResult();
result.succ=true;
result.setData(data);
returnresult;
}
publicstaticResultofFail(Stringcode,Stringmsg){
Resultresult=newResult();
result.succ=false;
result.code=code;
result.msg=msg;
returnresult;
}
publicstaticResultofFail(Stringcode,Stringmsg,Objectdata){
Resultresult=newResult();
result.succ=false;
result.code=code;
result.msg=msg;
result.setData(data);
returnresult;
}
publicstaticResultofFail(CommonErrorCoderesultEnum){
Resultresult=newResult();
result.succ=false;
result.code=resultEnum.getCode();
result.msg=resultEnum.getMessage();
returnresult;
}
/**
*获取json
*/
publicStringbuildResultJson(){
JSONObjectjsonObject=newJSONObject();
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);
returnJSON.toJSONString(jsonObject,SerializerFeature.DisableCircularReferenceDetect);
}
}
这样已经满足一般返回处理的需求了,在接口可以这样使用:
@GetMapping("hello")
publicResultlist(){
returnResult.ofSuccess("hello");
}
当然这样是耦合的使用,每次都需要调用Result
里的包装方法。
ResponseBodyAdvice
返回统一拦截处理
ResponseBodyAdvice
在spring4.1新加入的一个接口,在消息体被HttpMessageConverter
写入之前允许Controller
中@ResponseBody
修饰的方法或ResponseEntity
调整响应中的内容,比如做一些返回处理。
ResponseBodyAdvice
接口里一共包含了两个方法
supports
:该组件是否支持给定的控制器方法返回类型和选择的{@codeHttpMessageConverter}类型beforeBodyWrite
:在选择{@codeHttpMessageConverter}之后调用,在调用其写方法之前调用。
那么我们就可以在这两个方法做一些手脚。
supports
用于判断是否需要做处理。beforeBodyWrite
用于做返回处理。
CommonResponseDataAdvice
类实现ResponseBodyAdvice
两个方法。
filter(MethodParametermethodParameter)
私有方法里进行判断是否要进行拦截统一返回处理。
如:
- 添加自定义注解
@IgnorReponseAdvice
忽略拦截。 - 判断某些类不进行拦截.
- 判断某些包下所有类不进行拦截。如
swagger
的springfox.documentation
包下的接口忽略拦截等。
filter方法:
判断为false就不需要进行拦截处理。
privateBooleanfilter(MethodParametermethodParameter){
Class<?>declaringClass=methodParameter.getDeclaringClass();
//检查过滤包路径
longcount=globalDefaultProperties.getAdviceFilterPackage().stream()
.filter(l->declaringClass.getName().contains(l)).count();
if(count>0){
returnfalse;
}
//检查<类>过滤列表
if(globalDefaultProperties.getAdviceFilterClass().contains(declaringClass.getName())){
returnfalse;
}
//检查注解是否存在
if(methodParameter.getDeclaringClass().isAnnotationPresent(IgnorReponseAdvice.class)){
returnfalse;
}
if(methodParameter.getMethod().isAnnotationPresent(IgnorReponseAdvice.class)){
returnfalse;
}
returntrue;
}
CommonResponseDataAdvice类:
最核心的就在beforeBodyWrite
方法处理里。
-
判断
Objecto
是否为null
,为null
构建Result
对象进行返回。 -
判断
Objecto
是否是Result
子类或其本身,该情况下,可能是接口返回时创建了Result
,为了避免再次封装一次,判断是Result
子类或其本身就返回Objecto
本身。 -
判断
Objecto
是否是为String
,在测试的过程中发现String
的特殊情况,在这里做了一次判断操作,如果为String
就进行JSON.toJSON(Result.ofSuccess(o)).toString()
序列号操作。 -
其他情况默认返回
Result.ofSuccess(o)
进行包装处理。/**
- {@linkIgnorReponseAdvice}处理解析{@linkResponseBodyAdvice}统一返回包装器
- @authorpurgeyao
- @since1.0 */ @RestControllerAdvice publicclassCommonResponseDataAdviceimplementsResponseBodyAdvice
privateGlobalDefaultPropertiesglobalDefaultProperties;
publicCommonResponseDataAdvice(GlobalDefaultPropertiesglobalDefaultProperties){ this.globalDefaultProperties=globalDefaultProperties; }
@Override @SuppressWarnings("all") publicbooleansupports(MethodParametermethodParameter, Class>aClass){ returnfilter(methodParameter); }
@Nullable @Override @SuppressWarnings("all") publicObjectbeforeBodyWrite(Objecto,MethodParametermethodParameter,MediaTypemediaType, Class>aClass,ServerHttpRequestserverHttpRequest, ServerHttpResponseserverHttpResponse){
//oisnull->returnresponse if(o==null){ returnResult.ofSuccess(); } //oisinstanceofConmmonResponse->returno if(oinstanceofResult){ return(Result<Object>)o; } //string特殊处理 if(oinstanceofString){ returnJSON.toJSON(Result.ofSuccess(o)).toString(); } returnResult.ofSuccess(o);
}
privateBooleanfilter(MethodParametermethodParameter){ ···略 }
}
这样基本完成了核心的处理工作。当然还少了上文提到的@IgnorReponseAdvice
注解。
@IgnorReponseAdvice:
比较简单点,只作为一个标识的作用。
/**
*统一返回包装标识注解
*
*@authorpurgeyao
*@since1.0
*/
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public@interfaceIgnorReponseAdvice{
}
加入spring容器
最后将GlobalDefaultExceptionHandler
以bean
的方式注入spring
容器。
@Configuration
@EnableConfigurationProperties(GlobalDefaultProperties.class)
@PropertySource(value="classpath:dispose.properties",encoding="UTF-8")
publicclassGlobalDefaultConfiguration{
···略
@Bean
publicCommonResponseDataAdvicecommonResponseDataAdvice(GlobalDefaultPropertiesglobalDefaultProperties){
returnnewCommonResponseDataAdvice(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@interfaceEnableGlobalDispose{
}
使用@Import
将GlobalDefaultConfiguration
导入即可。
使用
添加依赖
<dependency>
<groupId>com.purgeteam</groupId>
<artifactId>unified-dispose-deepblueai-starter</artifactId>
<version>0.1.1.RELEASE</version>
</dependency>
启动类开启@EnableGlobalDispose
注解即可。
- 业务使用
接口:
@GetMapping("test")
publicStringtest(){
return"test";
}
返回
{
"succ":true,//是否成功
"ts":1566386951005,//时间戳
"data":"test",//数据
"code":null,//错误类型
"msg":null,//错误描述
"fail":false
}
- 忽略封装注解:@IgnorReponseAdvice
@IgnorReponseAdvice
允许范围为:类+方法,标识在类上这个类下的说有方法的返回都将忽略返回封装。
接口:
@IgnorReponseAdvice//忽略数据包装可添加到类、方法上
@GetMapping("test")
publicStringtest(){
return"test";
}
返回test
总结
项目里很多重复的code,我们可以通过一定的方式去简化,以达到一定目的减少开发量。
示例代码地址:unified-dispose-springboot
作者GitHub:
Purgeyao欢迎关注
本文内容总结:springboot之全局处理统一返回,简介,配置,总结,
原文链接:https://www.cnblogs.com/Purgeyao/p/11599810.html