现在项目情况都是前后端分离,后端人员为了能让前端人员能够看懂返回结果,同时还需要返回结果的格式一致,所以就有了固定的结果返回体。
Controller控制器返回给前端常见的的格式有三种:
- 自定义返回体Result
- 自定义Map
- ResponseEntity
今天,我们用到的是第一种情况吗,自定义返回体Result。
1、自定义返回体Result
通常情况下,需要前端需要的信息如下,JSON格式:一般会存在3个Key:code,msg,data
{
code: 1,
msg: "",
data: null
}
所以我们就会在Java中定义类如下:
public class Result implements Serializable {
private Integer code;
private String message;
private Object data;
public Result(Integer code, String message, Object data) {
this.code = code;
this.message = message;
this.data = data;
}
...
}
我们来看下3个字段的意思:
- code:状态码
- message:请求情况,例如请求成功,用户名不存在,密码错误等
- data:数据
一般情况情况下,code和message是一起的,所以我们可以用上枚举来记录存在的状态码和信息。
2、Code和Msg的枚举类
我们定义的枚举可以如下:
public enum ResultCode {
SUCCESS(1,"成功"),
PARAM_IS_INVALID(1001,"参数无效"),
PARAM_IS_BLANK(1002,"参数为空"),
USER_NOT_LOGGED_IN(2001,"用户未登录"),
USER_LOGIN_ERROR(2002,"用户不存在或密码错误"),
USER_NOT_EXISITED(2003,"用户不存在");
private Integer code;
private String message;
public Integer getCode() {
return code;
}
public String getMessage() {
return message;
}
ResultCode(Integer code, String message) {
this.code = code;
this.message = message;
}
}
我们将Result可用的构造方法添加上去,与ResultCode枚举类联动起来。
public Result(ResultCode resultCode){
this.code = resultCode.getCode();
this.message = resultCode.getMessage();
}
public Result(ResultCode resultCode,Object data) {
this.code = resultCode.getCode();
this.message = resultCode.getMessage();
this.data = data;
}
但是这样子还不是很香,构造起来看的脑瓜疼,每一个类我们都需要手动去构造,所以我们在Result中加上静态方法。
new Result(ResultCode.SUCCESS,data);
3、静态方法
方法如下:
public static Result success(){
Result result = new Result(ResultCode.SUCCESS);
return result;
}
public static Result success(Object data){
Result result = new Result(ResultCode.SUCCESS, data);
return result;
}
public static Result failure(ResultCode resultCode){
Result result = new Result(resultCode);
return result;
}
public static Result failure(ResultCode resultCode,Object data){
Result result = new Result(resultCode, data);
return result;
}
这样子,我们只需要调用Result.success(data);就好了,但是还是脑瓜疼,我想直接返回数据,于是我们加上ResponseBodyAdvice
。
4、ResponseBodyAdvice
ResponseBodyAdvice接口的实现类作用在controller将请求处理好返回结果,但是在将结果返回给客户端之前。
ResponseBodyAdvice接口详情如下:共有两个接口
- supports:判断是否需要执行beforeBodyWrite方法,返回结果为true执行,为false则不执行。
- beforeBodyWrite:对response具体的处理方法
public interface ResponseBodyAdvice<T> {
//判断是否要执行beforeBodyWrite方法,true为执行,false不执行
boolean supports(MethodParameter var1, Class<? extends HttpMessageConverter<?>> var2);
//对response处理的执行方法
@Nullable
T beforeBodyWrite(@Nullable T var1, MethodParameter var2, MediaType var3, Class<? extends HttpMessageConverter<?>> var4, ServerHttpRequest var5, ServerHttpResponse var6);
}
所以我们这样子,在beforeBodyWrite方法中加入我们想要封装的方式。
@RestControllerAdvice("com.order.food.delivery.controller")
public class ResponseResultHandler implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
return Result.success(body);
}
}
看,通过自定ResponseBodyAdvice来决定返回结果的封装方式,我们就可以直接返回数据了。
@ControllerAdvice就是表示我们需要对哪个controller的结果需要包装,但是我有一个不需要包装了,怎么办?于是我们自定义注解+拦截器添加标记,在supports方法中检验是否有标记来决定是否执行beforeBodyWrite方法。
5、自定义注解和拦截器
我们先来个空注解
/**
* 返回结果处理标记注解
*
* @author :Mr.Garlic
* @date : 2021/1/10
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
@Documented
public @interface ResponseResult {
}
我们再来个拦截器并把它配置进容器,我们起个标记,有注解的话就加个标记到request中。
/**
* ResponseResult注解拦截器
*
* @author: Mr.Garlic
* @date: 2021/1/10
*/
public class ResponseResultInterceptor implements HandlerInterceptor {
/**
* 标记名称
*/
public static final String RESPONSE_NEED_RESULT = "RESPONSE_NEED_RESULT";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//请求方法
if(handler instanceof HandlerMethod){
final HandlerMethod handlerMethod = (HandlerMethod)handler;
// 拿到的是方法所在的controller类的class类
final Class<?> clazz = handlerMethod.getBeanType();
// 拿到method对象
final Method method = handlerMethod.getMethod();
// 判断是否在类对象上面加了注解
if(clazz.isAnnotationPresent(ResponseResult.class)){
// 设置此请求返回体
request.setAttribute(RESPONSE_NEED_RESULT,
clazz.getAnnotation(ResponseResult.class));
}else if(method.isAnnotationPresent(ResponseResult.class)){
// 设置此请求返回体
request.setAttribute(RESPONSE_NEED_RESULT,
method.getAnnotation(ResponseResult.class));
}
}
return true;
}
}
/**
* web配置类
*
* @author :Mr.Garlic
* @date : 2021/1/11
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new ResponseResultInterceptor()).addPathPatterns("/**");
}
}
然后我们再来修改下自定义ResponseBodyAdvice里面的逻辑
/**
* controller返回结果包装
*
* @author: Mr.Garlic
* @date: 2021/1/11
*/
@RestControllerAdvice("com.order.food.delivery.controller")
public class ResponseResultHandler implements ResponseBodyAdvice<Object> {
/**
* 标记名称
*/
public static final String RESPONSE_NEED_RESULT = "RESPONSE_NEED_RESULT";
// 判断是否需要执行beforeBodyWrite
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
HttpServletRequest request = servletRequestAttributes.getRequest();
ResponseResult responseResultAnn = (ResponseResult)request.getAttribute(RESPONSE_NEED_RESULT);
return responseResultAnn == null?false:true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
return Result.success(body);
}
}
这样子,我们可以很直接,有选择地包装对象啦。
你说,后面还有没有选择,当然有啦,优化流程怎么可能少得了缓存呢,不过,下次吧!