包装SpringBoot Controller返回值

一个项目使用了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。

就这样。

##########

原文 

http://www.zhyea.com/2017/10/10/wrap-spring-boot-controller-response.html

PS:如果您想和业内技术大牛交流的话,请加qq群(527933790)或者关注微信公众 号(AskHarries),谢谢!

转载请注明原文出处:Harries Blog™ » 包装SpringBoot Controller返回值

赞 (0)

分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址