本文最后更新于: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;
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; }
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,没有业务含义,不利于文档的生成
所以就有了接下来的修改
结果统一封装
整体思路
- 自定义注解
- 定义请求拦截器
- 从请求拦截器中获取方法执行类对象和方法对象
- 判断方法有没有自定义的注解,有则向请求中添加属性
- 自定义响应处理类,在方法响应之前判断请求中有无指定属性
- 对数据封装返回
自定义注解
创建 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 { if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; Class<?> clazz = handlerMethod.getBeanType(); Method method = handlerMethod.getMethod(); 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 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) { 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 "字符串类型测试"; }
|