springboot返回值處理

上一次說到了springboot中對於參數處理的擴展,當然主要是說到如何針對特有的請求數據進行轉換,並非站在spring的基礎上看spring如何完成這一邏輯,而關於spring內部處理或者說內置的處理將在sping源碼中詳細去理解。

今天將要說到的是springboot中如何處理方法返回值,使用spring較多的人會看到,對於方法的返回一般會有兩種模式,一種是頁面,一種是數據,分別對應了ModelAndView和被@ResponseBody標註的方法。下面簡單的看下這兩種方式的實現過程,同時如何擴展自己的實現。

對於springmvc,所有的請求入口則是通過DispatcherServlet.doDispatch處理,不妨從這裏開始看下處理過程,對於此次不關注的點回直接略過。

首先定義一個簡單的Controller,然後在doDispatch打個斷點

主要關注ha,在spring中有很多的HandlerAdapter,我們用的比較多的當然是RequestMappingHandlerAdapter,相應的進入到handleInternal方法中,

下一步則會調用invokeHandlerMethod,內容比較多,但是不用關注太多,

這裏可以看到,使用了一個invocableMethod,看代碼可以看到其類型爲ServletInvocableHandlerMethod,

這裏關注下mavContainer.setRequestHandled(true),這也就是表明返回的ModelAndView是否會被視圖解析器解析。returnValueHandlers也就是HandlerMethodReturnValueHandlerComposite,

此時會找到對應的returnValueHandler去處理,調試會發現RequestResponseBodyMethodProcessor,

這裏將結果交由MessageConverter處理,由於方法writeWithMessageConverters篇幅比較長,看下關鍵點

最終就會定位到我們熟悉的StringHttpMessageConverter、MappingJackson2HttpMessageConverter。

其實通過調試基本可以看到主要的邏輯流程。今天說到的返回值處理主要如何通過HandlerMethodReturnValueHandler接口進行擴展,通過上面的調試過程,我們可以看到,對改接口的使用時在RequestMappingHandlerAdapter出現 ,具體spring對該接口有哪些實現,這些實現是如何注入到RequestMappingHandlerAdapter中,這個不做深入。上面說到這裏會選擇 RequestResponseBodyMethodProcessor,可以看下大致的實現,方便之後的擴展。

這也解釋了爲什麼通過@ResponseBody標記的類或方法能夠被處理了。

上面看到的,所以我們在擴展的時候也是一個道理。

下面看一個示例,將請求結果寫到文件中並返回:

定義一個註解,類似@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進行適配,一段發現有匹配的處理,則會調用,看下面的代碼你會發現,自定義的處理器位置較後,如果默認的處理器有能夠適配的,那麼我們定義的就不會執行了,所有要注意。

最後就是測試了:

    @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來處理,同樣需要注意注入順序。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章