springmvc的@ResponseBody返回類型的底層處理和亂碼問題解決

使用過springmvc都知道,我們只需編寫Handler的邏輯和返回結果,而返回的結果可以是任意類型的,springmvc是如何處理這些類型的呢。

一 首先了解兩個概念:

1 返回值處理器(HandlerMethodReturnValueHandler):對于請求訪問,不同的請求,需要不用的Handler,同樣的道理,對于Handler的不同返回值,也需要不同的返回值處理器(HandlerMethodReturnValueHandler)來處理。HandlerMethodReturnValueHandler集合配置在適配器的List<HandlerMethodReturnValueHandler> customReturnValueHandlers屬性里面。
默認的返回值處理器有:

private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
        List<HandlerMethodReturnValueHandler> handlers = new ArrayList();
        handlers.add(new ModelAndViewMethodReturnValueHandler());
        handlers.add(new ModelMethodProcessor());
        handlers.add(new ViewMethodReturnValueHandler());
        handlers.add(new ResponseBodyEmitterReturnValueHandler(this.getMessageConverters(), this.reactiveAdapterRegistry, this.taskExecutor, this.contentNegotiationManager));
        handlers.add(new StreamingResponseBodyReturnValueHandler());
        handlers.add(new HttpEntityMethodProcessor(this.getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice));
        handlers.add(new HttpHeadersReturnValueHandler());
        handlers.add(new CallableMethodReturnValueHandler());
        handlers.add(new DeferredResultMethodReturnValueHandler());
        handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));
        handlers.add(new ModelAttributeMethodProcessor(false));
        handlers.add(new RequestResponseBodyMethodProcessor(this.getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice));
        handlers.add(new ViewNameMethodReturnValueHandler());
        handlers.add(new MapMethodProcessor());
        if (this.getCustomReturnValueHandlers() != null) {
            handlers.addAll(this.getCustomReturnValueHandlers());
        }

        if (!CollectionUtils.isEmpty(this.getModelAndViewResolvers())) {
            handlers.add(new ModelAndViewResolverMethodReturnValueHandler(this.getModelAndViewResolvers()));
        } else {
            handlers.add(new ModelAttributeMethodProcessor(true));
        }

        return handlers;
    }
image.png

常用的有:

  • ModelAndViewMethodReturnValueHandler:支持返回值是ModelAndView類型的。
  • ModelMethodProcessor:支持返回值是Model的。
  • ViewMethodReturnValueHandler:支持返回值是View的。
  • HttpEntityMethodProcessor:支持返回值是HttpEntity的。
  • RequestResponseBodyMethodProcess:支持類上或者方法上含有@ResponseBody注解的(這里主要講解這個返回值處理器)。
  • ViewNameMethodReturnValueHandler:支持返回類型是void或者String。

獲取返回值處理器的規(guī)則是從上到下返回第一個匹配的處理器。

2 返回值轉換器(HttpMessageConverter):當找到合適的返回值處理器(HandlerMethodReturnValueHandler)后,處理器還需要調(diào)用對應的返回值轉換器來對返回值進行轉換,然后才輸出到響應體里面。需要使用默認轉換器的主要是ResponseBodyEmitterReturnValueHandler、HttpEntityMethodProcessor、RequestResponseBodyMethodProcessor這三個返回值處理器。

特別提示:返回值轉換器配置在適配器RequestMappingHandlerAdapter的private List<HttpMessageConverter<?>> messageConverters屬性里面。

默認的返回值轉換器有:

public RequestMappingHandlerAdapter() {
        StringHttpMessageConverter stringHttpMessageConverter 
                        = new StringHttpMessageConverter();
        stringHttpMessageConverter.setWriteAcceptCharset(false);
        this.messageConverters = new ArrayList(4);
        this.messageConverters.add(new ByteArrayHttpMessageConverter());
        this.messageConverters.add(stringHttpMessageConverter);

        try {
            this.messageConverters.add(new SourceHttpMessageConverter());
        } catch (Error var3) {
        }

        this.messageConverters
        .add(new AllEncompassingFormHttpMessageConverter());
    }

轉換器轉換規(guī)則--遍歷返回值轉換器集合,使用返回值轉換器的canWrite(Class<?> clazz, @Nullable MediaType mediaType)方法判斷是否匹配,在里面主要會進行類型是否支持和媒體是否支持的判斷,兩者同時滿足表示匹配成功,使用該轉換器處理返回值,canWrite(Class<?> clazz, @Nullable MediaType mediaType)如下所示。

public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) {
        //this.supports(clazz) 判斷類型是否匹配,
        //this.canWrite(mediaType)判斷媒體是否匹配
        return this.supports(clazz) && this.canWrite(mediaType);
    }

常用類型轉換器匹配規(guī)則:

  • ByteArrayHttpMessageConverter:匹配 byte[]數(shù)組,默認匹配媒體類型有 [application/octet-stream, */*]
  • StringHttpMessageConverter:匹配String類型,默認匹配媒體類型集合有 [text/plain, */*]
  • SourceHttpMessageConverter:匹配DOMSource、SAXSource、StAXSource、StreamSource、Source類型,默認匹配媒體類型集合有[application/xml, text/xml, application/*+xml]
  • AllEncompassingFormHttpMessageConverter:匹配MultiValueMap類型或是其子類,匹配媒體類型集合有[application/x-www-form-urlencoded, multipart/form-data]

注意:媒體mediaType有兩個屬性,分別是type和subtype,比如媒體類型application/octet-stream的type=application、subtype=octet-stream。兩個媒體的type和subtype屬性相等即匹配成功。另外,*/*表示通配任意type與subtype的值,所以默認匹配媒體類型如果有*/* ,那么媒體類型肯定可以匹配成功,我們只需關注this.supports(clazz)即類型判斷就行了。還有就是,媒體類型首先是從response對象里面獲取content-type的header值,如果沒有獲取到,即響應沒有設置content-type的header值,springmvc會自己嘗試給定一個可接受的媒體值用于媒體判斷。當然,如果不想使用默認媒體類型,還可以自定義媒體類型,只需在自定義返回值轉換器的時候指定supportedMediaTypes即可。

注意:返回值轉換器默認的編碼是ISO-8859-1,在將返回值寫入響應體里面時會使用該編碼方式編碼后再寫入所以會出現(xiàn)亂碼問題。什么意思,意思就是假如你的返回值是utf-8編碼,然后也告訴了瀏覽器返回值的編碼方式是utf-8(通過response對象告訴瀏覽器),最后發(fā)現(xiàn)顯示時任然是亂碼,就是因為返回值轉換器在將返回值寫入響應體之前還要對其進行一次編碼,而默認使用的是ISO-8859-1編碼方式,導致亂碼問題。
配置返回值轉換器的媒體類型與編碼方式(utf-8):

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <property name="messageConverters">
            <list>
                <bean class="org.springframework.http.converter.StringHttpMessageConverter" >
                    <property name="defaultCharset" value="utf-8"></property>
                    <property name="supportedMediaTypes">
                        <list>
                            <value>text/html</value>
                            <value>*/*</value>
                        </list>
                    </property>
                </bean>
            </list>
        </property>
    </bean>

二 @ResponseBody字符串中文亂碼問題解決

由以上知道,使用了@ResponseBody的handler使用的是RequestResponseBodyMethodProcess返回值處理器,該處理器需要使用默認返回值轉換器集合中的轉換器來處理返回值。
知道了返回值的處理規(guī)則后,這里來說一下如何處理返回字符串中文亂碼問題。首先我們要知道,出現(xiàn)亂碼就是因為從返回值到瀏覽器顯示時編碼方式不一致導致的,就是因為轉換器的默認編碼方式為iso-8859-1。知道原因過后修改轉換器的編碼方式就行了。
字符串返回值使用的是StringHttpMessageConverter轉換器,并且我們知道轉換器集合配置在適配器的private List<HttpMessageConverter<?>> messageConverters屬性里面,解決辦法就是自己配置messageConverters里面的轉換器,并為轉換器的defaultCharset屬性配置編碼為utf-8,需要注意的是自己配置后以前默認的轉換器就不存在了:

<!--配置方式一-->
    <mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="defaultCharset" value="utf-8"></property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
<!--配置方式二-->
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <property name="messageConverters">
            <list>
                <bean class="org.springframework.http.converter.StringHttpMessageConverter" >
                    <property name="defaultCharset" value="utf-8"></property>
                </bean>
            </list>
        </property>
    </bean>

兩種配置是根據(jù)引入適配器與映射器方式的不同而不同,但是原理都一樣,都是配置適配器的messageConverters屬性。

三 @ResponseBody返回json字符串

我們知道,springmvc默認的返回值轉換器只能匹配有限類型,其中就不包括我們定義的一些實體類,如果返回值是一些實體類型,會拋出沒有對應的返回值轉換器異常。

org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class com.dahuici.zyb.entity.User
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:233)  org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:180)
org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:82)
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:119)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:800)
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)    org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1038)
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942) org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897)
javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)
javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)

springmvc為我們準備了很多轉換器,如下圖所示


image.png

這里我們使用的是json里面的MappingJackson2HttpMessageConverter


image.png

MappingJackson2HttpMessageConverter 默認匹配的媒體類型有 [application/json, application/*+json],至于對于返回值類型來說,唯一的要求好像就是需要有set與get方法,當然,如果是String類型不需要set與get(這一段的類型判斷超出技術水平了)。
另外,MappingJackson2HttpMessageConverter使用到了其他的包(jackson-databind,jackson-core最少要導入這兩個),所以需要導入如下依賴:
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.9.5</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>2.9.5</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>2.9.5</version>
    </dependency>

然后將其配置給適配器的messageConverters屬性即可,需要注意的是也要配置編碼方式為utf-8,不然返回的json字符串會出現(xiàn)亂碼。

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <property name="messageConverters">
            <list>
                <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" >
                    <property name="defaultCharset" value="utf-8"></property>
                </bean>
            </list>
        </property>
    </bean>
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容