结果统一封装(优雅)

本文最后更新于:2023年8月24日 晚上

我们为了保证后端发送数据的一致性,都会在controller处进行封装,以下是我的封装思路

项目初始化

自定义返回的实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.fsan.throw_demo.entity;

import com.fsan.throw_demo.myEnum.RespResultEnum;
import lombok.Data;

@Data
public class RespResult {
private Integer code;
private String msg;
private Object data;

/**
* 有返回值的请求成功
*
* @param data 数据
* @return
*/
public static RespResult success(Object data) {
RespResult respResult = new RespResult();
respResult.setCode(RespResultEnum.SUCCESS.getCode());
respResult.setMsg(RespResultEnum.SUCCESS.getMsg());
respResult.setData(data);
return respResult;
}

/**
* 无返回值的请求成功
*
* @return
*/
public static RespResult success() {
RespResult respResult = new RespResult();
respResult.setCode(RespResultEnum.SUCCESS.getCode());
respResult.setMsg(RespResultEnum.SUCCESS.getMsg());
return respResult;
}
}

由于只记录统一封装,就只展示成功的封装过程

然后可以看到,我这里是用了一个枚举类的,为了后续方便增加状态对应,枚举类如下:

com.fsan.throw_demo.myEnum.RespResultEnum

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.fsan.throw_demo.myEnum;

public enum RespResultEnum {

SUCCESS(200, "成功!"),
ERROR(500, "服务器出现问题,请尽快联系管理员!"),
NOT_AUTHENTICATION(403, "权限认证失败!"),
NULL_USERNAME(1001, "用户名为空!非法请求!"),
NULL_PASSWORD(1002, "密码为空!非法请求!");


private Integer code;
private String msg;

RespResultEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}

public Integer getCode() {
return code;
}

public void setCode(Integer code) {
this.code = code;
}

public String getMsg() {
return msg;
}

public void setMsg(String msg) {
this.msg = msg;
}
}

controller演示

为了简单测试,也是写了一个User类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.fsan.throw_demo.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long userId;
private String username;
private String password;
}

controller使用如下:

1
2
3
4
@GetMapping("/getJson")
public RespResult getJSON() {
return RespResult.success(new User(1L,"FSAN","FSAN"));
}

可以看到,这样返回非常的不方便,如果所有的接口返回类型都是RespResult,没有业务含义,不利于文档的生成

所以就有了接下来的修改

结果统一封装

整体思路

  1. 自定义注解
  2. 定义请求拦截器
  3. 从请求拦截器中获取方法执行类对象和方法对象
  4. 判断方法有没有自定义的注解,有则向请求中添加属性
  5. 自定义响应处理类,在方法响应之前判断请求中有无指定属性
  6. 对数据封装返回

自定义注解

创建 myAnnotation > PackResult.java

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.fsan.throw_demo.myAnnotation;


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PackResult {

}

@Target 设置注解描述对象(设置在方法或类上使用)

@Retention 设置启动事件(设置运行时启动)

创建拦截器

创建 config > MvcConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.fsan.throw_demo.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;


@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptors()).addPathPatterns("/**");
}
}

这里我用了MyInterceptors类,这是我们下一步定义的拦截规则类

创建拦截规则

创建 config > MyInterceptors.java

这个拦截规则作用并不是拦截,而是判断方法或类上是否有注解,最后都是要返回true通过的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.fsan.throw_demo.config;

import com.fsan.throw_demo.myAnnotation.PackResult;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

public class MyInterceptors implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 判断handler是否可以转为HandlerMethod,是否是HandlerMethod的实例
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 获取请求的类对象和方法对象
Class<?> clazz = handlerMethod.getBeanType();
Method method = handlerMethod.getMethod();
// 判断类或方法中是否存在PackResult注解
if (clazz.isAnnotationPresent(PackResult.class)) {
request.setAttribute("havePack", clazz.getAnnotation(PackResult.class));
} else if (method.isAnnotationPresent(PackResult.class)) {
request.setAttribute("havePack", method.getAnnotation(PackResult.class));
}
}
return true;
}
}

getBeanType() 获取类对象,并不是getClass()

getMethod() 获取方法对象

注意 isAnnotationPresent并不是isAnnotation,前者是判断是否存在注解,后者是判断是否为注解

创建RestController和Controller的拦截类(增强返回)

创建 controller > RespResultControllerAdvice

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.fsan.throw_demo.controlller;

import com.alibaba.fastjson.JSONObject;
import com.fsan.throw_demo.entity.RespResult;
import com.fsan.throw_demo.myAnnotation.PackResult;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.servlet.http.HttpServletRequest;

@RestControllerAdvice
public class RespResultControllerAdvice implements ResponseBodyAdvice {

@Override
public boolean supports(MethodParameter methodParameter, Class aClass) {
// 先转为ServletRequestAttributes对象
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
// 获取请求信息
HttpServletRequest request = attributes.getRequest();
// 获取请求属性
PackResult result = (PackResult) request.getAttribute("havePack");
return result != null;
}

@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
// 如果上面为true则封装返回
return RespResult.success(o);
}
}

主要就是使用了RequestContextHolder对象获取到HttpServletRequest

这里通过请求获取havePack就是前面通过拦截器对包含PackResult注解的请求添加的,如果有就是需要封装,返回的PackResult就是注解类,也是同前面添加

这是直接返回而不用使用JSON转换的原因是controller中@RestController会封装

封装前后controller写法对比

统一封装前:

1
2
3
4
5
6
7
8
9
@GetMapping("/getJson")
public RespResult getJSON() {
return RespResult.success(new User(1L,"FSAN","FSAN"));
}

@GetMapping("/getBlo")
public RespResult getBlo() {
return RespResult.success(Boolean.TRUE);
}

统一封装后:

1
2
3
4
5
6
7
8
9
@GetMapping("/getJson")
public User getJSON() {
return new User(1L,"FSAN","FSAN");
}

@GetMapping("/getBlo")
public Boolean getBlo() {
return Boolean.TRUE;
}

返回类型明确,减少代码量,以上结果返回都为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"msg": "成功!",
"code": 200,
"data": {
"password": "FSAN",
"userId": 1,
"username": "FSAN"
}
}

{
"msg": "成功!",
"code": 200,
"data": true
}

未解决点

在controller中如果直接返回String类型,返回的时候会报错转换失败,如下:

1
2
3
4
@GetMapping("/stringText")
public Object stringText() {
return "字符串类型测试";
}

结果统一封装(优雅)
https://xin-fas.github.io/2022/05/14/统一封装返回(优雅)/
作者
Xin-FAS
发布于
2022年5月14日
更新于
2023年8月24日
许可协议