修改Springboot 响应过程,包装返回值

2,585 阅读3分钟

问题

  • 前端一直不太认同http status(状态码)表达操作成功与否的方式,强烈要求所有接口都返回200,在响应体中增加code来表示操作是否成功。

比如当使用POST方式请求接口/api/user/2 时
当没有操作权限时两种响应 请求接口: /api/user/2 请求方法: POST 请求体:......(json格式的参数)

原响应格式: http status: 403 响应体:

{
    msg: '没有操作权限'
}

前端想要的响应格式: http status: 200 响应体:


{
    code: 403,
    msg: '没有操作权限'
}

办法

1. 修改每个接口的返回值

最简单粗暴的方式:定义一个类如下:


@Data
public class Res{
    private Integer code;
    private String msg;
    private Object content;
}

将每个service的返回值都set给content,set相应的code和msg,然后controller将该对象返回给前端。

也可以每个接口都返回JSONObject的实例json

@GetMapping("/{id}")
public JSONObject getOne((@PathVariable Integer id){
    Person person = personService.findById(id);

    JSONObject json = new JSONObject();
    json.put("code", 200);
    json.put("msg", "成功");
    json.put("content", person);
    return json;
}

功能完成,但是看到满屏的Res或者JSONObject操作,这么多重复代码,实在不应该。

2. 改进,修改SpringBoot原有流程

SpringBoot的请求处理过程如下:

882980-20170729131340722-226542134.png 请求处理大致流程 4702716-793d55323dd8cd66.jpg 请求处理具体流程

在第二张图中可以看到HandlerMethodReturnValueHandler在处理所有响应的内容,只要干预他的处理过程就能将原有数据包装起来 下面是该类的源码

public interface HandlerMethodReturnValueHandler {

	/**
	 * Whether the given {@linkplain MethodParameter method return type} is
	 * supported by this handler.
	 * @param returnType the method return type to check
	 * @return {@code true} if this handler supports the supplied return type;
	 * {@code false} otherwise
	 */
	boolean supportsReturnType(MethodParameter returnType);

	/**
	 * Handle the given return value by adding attributes to the model and
	 * setting a view or setting the
	 * {@link ModelAndViewContainer#setRequestHandled} flag to {@code true}
	 * to indicate the response has been handled directly.
	 * @param returnValue the value returned from the handler method
	 * @param returnType the type of the return value. This type must have
	 * previously been passed to {@link #supportsReturnType} which must
	 * have returned {@code true}.
	 * @param mavContainer the ModelAndViewContainer for the current request
	 * @param webRequest the current request
	 * @throws Exception if the return value handling results in an error
	 */
	void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;

}

实现该类的两个方法就能自定义处理过程。

  • 首先还是要定义一个工具类:
public class Res {

//    protected final static Res OK = new Res( HttpStatus.OK.value(),"成功","");

    private Integer code;
    private String msg;
    private String message;
    /**
     * 返回正常信息时,将数据放入该字段
     */
    private Object content;

    /**
     * 成功响应并需要返回数据时,使用该构造方法
     * @param data
     */
    protected Res(Object data) {
        this.code = 200;
        this.msg = "成功";
        this.message = "success";
        this.content = data;
    }

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

    public Integer getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }

    public String getMessage() {
        return message;
    }

    public Object getContent() {
        return content;
    }

}
  • 然后实现 HandlerMethodReturnValueHandler
public class WrapReturnValueHandler implements HandlerMethodReturnValueHandler {

    private Res ok = new Res(null);

    private RequestResponseBodyMethodProcessor target;

    public WrapReturnValueHandler(RequestResponseBodyMethodProcessor target) {
        this.target = target;
    }

    @Override
    public boolean supportsReturnType(MethodParameter methodParameter) {
        return true;
    }

    @Override
    public void handleReturnValue(Object o, MethodParameter methodParameter,
                                  ModelAndViewContainer modelAndViewContainer,
                                  NativeWebRequest nativeWebRequest) throws Exception {
        Executable executable = methodParameter.getExecutable();

        Method method = (Method) executable;
        Class<?> returnType = method.getReturnType();
        if (returnType.equals(Void.class)) {
            target.handleReturnValue(ok, methodParameter, modelAndViewContainer, nativeWebRequest);
        } else if ( returnType.equals( Res.class )) {
            target.handleReturnValue(o, methodParameter, modelAndViewContainer, nativeWebRequest);
         else {
            target.handleReturnValue(new Res(o), methodParameter, modelAndViewContainer, nativeWebRequest);
        }
    }
}
  • 最后将这个实现类加入到SpringBoot流程中
@Configuration
public class InitializingAdvice implements InitializingBean {

    private final static Logger log = LoggerFactory.getLogger( InitializingAdvice.class );

    @Resource
    private RequestMappingHandlerAdapter adapter;
    
    @Override
    public void afterPropertiesSet() {
        List<HandlerMethodReturnValueHandler> returnValueHandlers = adapter.getReturnValueHandlers();
        List<HandlerMethodReturnValueHandler> handlers = new ArrayList(returnValueHandlers);
        this.decorateHandlers(handlers);
        adapter.setReturnValueHandlers(handlers);
    }


    private void decorateHandlers(List<HandlerMethodReturnValueHandler> handlers) {
        for (HandlerMethodReturnValueHandler handler : handlers) {
            if (handler instanceof RequestResponseBodyMethodProcessor) {
                WrapReturnValueHandler decorator = new WrapReturnValueHandler(
                        (RequestResponseBodyMethodProcessor) handler);
                int index = handlers.indexOf(handler);
                handlers.set(index, decorator);
                break;
            }
        }
    }
}

这样所有的响应在转化为流之前就能将内容包装成预期格式 当然抛出异常可以使用注解@ControllerAdvice捕获控制,以控制http status,然后主动将响应构造成Res实例