一个项目使用了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;
}
}
通过查找资料,找到了两种封装方式。
第一种方式是替换掉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类中完成:
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import java.util.List;
@Configuration
@EnableWebMvc
@EnableTransactionManagement
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
super.configureMessageConverters(converters);
converters.add(0, new MappingJackson2HttpMessageConverter());
}
}
在这个方案中,如需要对异常做些特别处理,还可以创建一个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。
就这样。
##########