一个项目使用了SpringBoot,需要对Controller的返回值进行二次包装。包装类结构大致如下:
import org.springframework.http.HttpStatus; public class Result { private int status = HttpStatus.OK.value(); private Object content; public Result(Object content) { this.content = content; } public Result(int status, String content) { this(content); this.content = content; } public int getStatus() { return status; } public void setStatus(int status) { this.status = status; } public Object getContent() { return content; } public void setContent(Object content) { this.content = content; } }
通过查找资料,找到了两种封装方式。
第一种方式是替换掉RequestResponseBodyMethodProcessor,这需要使用一个MethodReturnValueHandler的装饰类:
import org.springframework.core.MethodParameter; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.method.support.ModelAndViewContainer; public class ResponseBodyWrapHandler implements HandlerMethodReturnValueHandler { private final HandlerMethodReturnValueHandler delegate; public ResponseBodyWrapHandler(HandlerMethodReturnValueHandler delegate) { this.delegate = delegate; } @Override public boolean supportsReturnType(MethodParameter returnType) { return delegate.supportsReturnType(returnType); } @Override public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { Result result = new Result(returnValue); delegate.handleReturnValue(result, returnType, mavContainer, webRequest); } }
在装饰类中使用一个Result类的实例替换了returnValue。而后在InitializingBean中基于原来的RequestResponseBodyMethodProcessor的实例创建一个ResponseBodyWrapHandler的实例来完成替换:
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor; import java.util.ArrayList; import java.util.List; public class ResponseBodyWrapFactoryBean implements InitializingBean { @Autowired private RequestMappingHandlerAdapter adapter; @Override public void afterPropertiesSet() throws Exception { List<HandlerMethodReturnValueHandler> returnValueHandlers = adapter.getReturnValueHandlers(); List<HandlerMethodReturnValueHandler> handlers = new ArrayList(returnValueHandlers); decorateHandlers(handlers); adapter.setReturnValueHandlers(handlers); } private void decorateHandlers(List<HandlerMethodReturnValueHandler> handlers) { for (HandlerMethodReturnValueHandler handler : handlers) { if (handler instanceof RequestResponseBodyMethodProcessor) { ResponseBodyWrapHandler decorator = new ResponseBodyWrapHandler(handler); int index = handlers.indexOf(handler); handlers.set(index, decorator); break; } } } }
使用ResponseBodyWrapFactoryBean,完成afterProperties方法的调用,只需要创建一个ResponseBodyWrapFactoryBean的实例即可:
@Bean public ResponseBodyWrapFactoryBean getResponseBodyWrap() { return new ResponseBodyWrapFactoryBean(); }
这行代码可以放在启动类中。
第二种方式基于ControllerAdvice和HttpMessageConverter实现。
首先用一个ResponseBodyAdvice类的实现包装Controller的返回值:
import com.zhyea.spring.ext.Result; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; @ControllerAdvice public class ResponseAdvisor implements ResponseBodyAdvice<Object> { @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { return true; } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { String requestPath = request.getURI().getPath(); if (!requestPath.startsWith("/ug")) { return body; } if (body instanceof Result) { return body; } return new Result(body); } }
如果Controller类的返回值没有String类型的,仅有上面这个类就够了。如果有String类型的返回值,就有可能遇到类型不匹配的问题。HttpMessageConverter是根据Controller的原始返回值类型进行处理的,而我们在ResponseAdvisor中改变了返回值的类型。如果HttpMessageConverter处理的目标类型是Object还好说,如果是其它类型就会出现问题,其中最容易出现问题的就是String类型,因为在所有的HttpMessageConverter实例集合中,StringHttpMessageConverter要比其它的Converter排得靠前一些。我们需要尝试将处理Object类型的HttpMessageConverter放得靠前一些,这可以在一个Configuration类中完成:
@Configuration public class WebConfig extends DelegatingWebMvcConfiguration { @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { converters.add(0, new MappingJackson2HttpMessageConverter()); super.configureMessageConverters(converters); } }
在这个方案中,如需要对异常做些特别处理,还可以创建一个ExceptionAdvisor类来完成:
import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; @ControllerAdvice public class ExceptionAdvisor { @ResponseBody @ExceptionHandler(value = Exception.class) @ResponseStatus public Result exceptionHandler(Exception e) { return new Result(HttpStatus.BAD_REQUEST.value(), e.getMessage()); } @ResponseBody @ExceptionHandler(value = RuntimeException.class) @ResponseStatus(code = HttpStatus.BAD_REQUEST) public Result formatCheckExceptionHandler(RuntimeException e) { return new Result(HttpStatus.BAD_REQUEST.value(), e.getMessage()); } }
这样还可以根据异常类型来设置返回时的HttpStatus。
就这样。
有朋友在评论区指出问题了,做了些调整,也写了一份示例程序上传到CSDN。有兴趣的可以下载来看看。下载地址如下: 点击此处下载 。
这是许久之前刚用springboot时写的,现在适应springboot的最新版本存在一些问题。所以稍稍重新调整了下,并将之加入到了最近正在尝试进行的一个系列里面。
新版本的代码可以在 GitHub / zhyea 上看到。
##########