如何优雅地包装controller的返回结果

现在项目情况都是前后端分离,后端人员为了能让前端人员能够看懂返回结果,同时还需要返回结果的格式一致,所以就有了固定的结果返回体。

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);
    }
}

这样子,我们可以很直接,有选择地包装对象啦。

你说,后面还有没有选择,当然有啦,优化流程怎么可能少得了缓存呢,不过,下次吧!

  • 7
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值