上一次说到了springboot中对于参数处理的扩展,当然主要是说到如何针对特有的请求数据进行转换,并非站在spring的基础上看spring如何完成这一逻辑,而关于spring内部处理或者说内置的处理将在sping源码中详细去理解。

今天将要说到的是springboot中如何处理方法返回值,使用spring较多的人会看到,对于方法的返回一般会有两种模式,一种是页面,一种是数据,分别对应了ModelAndView和被@ResponseBody标注的方法。下面简单的看下这两种方式的实现过程,同时如何扩展自己的实现。

对于springmvc,所有的请求入口则是通过DispatcherServlet.doDispatch处理,不妨从这里开始看下处理过程,对于此次不关注的点回直接略过。

首先定义一个简单的Controller,然后在doDispatch打个断点

springboot返回bigdecimal springboot返回modelandview_HttpMessageConverter

主要关注ha,在spring中有很多的HandlerAdapter,我们用的比较多的当然是RequestMappingHandlerAdapter,相应的进入到handleInternal方法中,

springboot返回bigdecimal springboot返回modelandview_ide_02

下一步则会调用invokeHandlerMethod,内容比较多,但是不用关注太多,

springboot返回bigdecimal springboot返回modelandview_自定义_03

这里可以看到,使用了一个invocableMethod,看代码可以看到其类型为ServletInvocableHandlerMethod,

springboot返回bigdecimal springboot返回modelandview_ide_04

这里关注下mavContainer.setRequestHandled(true),这也就是表明返回的ModelAndView是否会被视图解析器解析。returnValueHandlers也就是HandlerMethodReturnValueHandlerComposite,

springboot返回bigdecimal springboot返回modelandview_spring_05

此时会找到对应的returnValueHandler去处理,调试会发现RequestResponseBodyMethodProcessor,

springboot返回bigdecimal springboot返回modelandview_ide_06

这里将结果交由MessageConverter处理,由于方法writeWithMessageConverters篇幅比较长,看下关键点

springboot返回bigdecimal springboot返回modelandview_HttpMessageConverter_07

最终就会定位到我们熟悉的StringHttpMessageConverter、MappingJackson2HttpMessageConverter。

其实通过调试基本可以看到主要的逻辑流程。今天说到的返回值处理主要如何通过HandlerMethodReturnValueHandler接口进行扩展,通过上面的调试过程,我们可以看到,对改接口的使用时在RequestMappingHandlerAdapter出现 ,具体spring对该接口有哪些实现,这些实现是如何注入到RequestMappingHandlerAdapter中,这个不做深入。上面说到这里会选择 RequestResponseBodyMethodProcessor,可以看下大致的实现,方便之后的扩展。

springboot返回bigdecimal springboot返回modelandview_spring_08

这也解释了为什么通过@ResponseBody标记的类或方法能够被处理了。

springboot返回bigdecimal springboot返回modelandview_HttpMessageConverter_09

上面看到的,所以我们在扩展的时候也是一个道理。

下面看一个示例,将请求结果写到文件中并返回:

定义一个注解,类似@ResponseBody,ResponseFile :

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface ResponseFile {

    String type() default "txt";
}

定义HandlerMethodReturnValueHandler实现类FileHandlerMethodReturnValueHandler:

public class FileHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler,Ordered {
    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseFile.class) ||
                returnType.hasMethodAnnotation(ResponseFile.class));
    }

    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        mavContainer.setRequestHandled(true);

        ResponseFile methodResponseFile = returnType.getMethodAnnotation(ResponseFile.class);
        if(methodResponseFile == null){
            methodResponseFile = returnType.getContainingClass().getAnnotation(ResponseFile.class);
        }
        Assert.notNull(methodResponseFile,"no ResponseFile at method :"+returnType.getMethod().getName());

        String fileType = methodResponseFile.type();

        HttpServletResponse response = getServletResponse(webRequest);

        response.setHeader("content-type", "application/octet-stream");
        response.setContentType("application/octet-stream");
        response.setHeader("Content-Disposition", "attachment;filename="+UUID.randomUUID().toString()+"."+fileType);

        ServletOutputStream out = response.getOutputStream();

        if(returnValue  instanceof ResponseFileEntity){
            FileCopyUtils.copy(((ResponseFileEntity) returnValue).getIn(),out);
        }else{
            FileCopyUtils.copy(new ByteArrayInputStream("non".getBytes()),out);
        }

    }

    public HttpServletResponse getServletResponse(NativeWebRequest webRequest){
        HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
        return response;
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE-10;
    }
}

将HandlerMethodReturnValueHandler注册到RequestMappingHandlerAdapter中:

@Configuration
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {

    @Override
    public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
        super.addReturnValueHandlers(returnValueHandlers);
        returnValueHandlers.add(new FileHandlerMethodReturnValueHandler());
    }
}

,根据源码你会发现,这个会注册到RequestMappingHandlerAdapter的customReturnValueHandlers中,但是一定要注意,默认情况下会有很多HandlerMethodReturnValueHandler,而具体使用哪一个是根据supportsReturnType进行适配,一段发现有匹配的处理,则会调用,看下面的代码你会发现,自定义的处理器位置较后,如果默认的处理器有能够适配的,那么我们定义的就不会执行了,所有要注意。

springboot返回bigdecimal springboot返回modelandview_spring_10

最后就是测试了:

@ResponseFile
    @GetMapping("/file")
    public ResponseFileEntity toFile(){
        ResponseFileEntity responseFileEntity = new ResponseFileEntity();
        responseFileEntity.setIn(new ByteArrayInputStream("这是一段测试文字".getBytes()));
        return responseFileEntity;
    }

当发送对应请求时,会返回一个文件。

 

前面说到,我们一般情况下的返回值主要都是通过@ResponseBody处理,最后将返回值转换成json类型并返回,所有处理逻辑在RequestResponseBodyMethodProcessor中完成,前面也说到,主要是通过HttpMessageConverter完成,这个东西比较眼熟,同样,在spring中默认有很多HttpMessageConverter,比如常见的StringHttpMessageConverter、MappingJackson2HttpMessageConverter等。

下面通过自定义一个HttpMessageConverter来针对@ResponseBody进行扩展:

public class XmlHttpMessageConverter extends AbstractHttpMessageConverter<Message> {
    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    public XmlHttpMessageConverter(){
        this(DEFAULT_CHARSET);
    }

    public XmlHttpMessageConverter(Charset charset){
        super(charset,MediaType.ALL);
    }

    @Override
    protected boolean supports(Class<?> clazz) {
        return clazz == Message.class;
    }

    @Override
    protected Message readInternal(Class<? extends Message> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return null;
    }

    @Override
    protected void writeInternal(Message message, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        XStream xStream = new XStream();
        xStream.alias("message",Message.class);
//        StreamUtils.copy(xStream.toXML(message), DEFAULT_CHARSET, outputMessage.getBody());
        xStream.toXML(message,outputMessage.getBody());
    }
}

注册到容器:

@Configuration
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        super.configureMessageConverters(converters);
    }

    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        super.extendMessageConverters(converters);
        converters.add(0,new XmlHttpMessageConverter());
    }
}

写个测试:

@ResponseBody
    @GetMapping("/message")
    public Message message(){
        Message message = new Message();
        message.setId(UUID.randomUUID().toString());
        message.setDate(new Date());
        message.setInfo("消息");
        return message;
    }

 

总结:

1、主要了解了springboot处理返回值的简单逻辑,返回对象最后都转换为ModelAndView,但是是否由视图解析器处理需要根据

ModelAndViewContainer.RequestHandled判断

2、通过实现 HandlerMethodReturnValueHandler自定义返回,可以将返回值转换成指定的类型,但要注意与默认配置的顺序,防止自定义的被覆盖

3、在@ResponseBody的基础上进行扩展,通过实现自定义的HttpMessageConverter来处理,同样需要注意注入顺序。