你真的會用HttpMessageConverter嗎?

前言

最近看了Spring MVC源碼,感覺特別有趣腰奋,像發(fā)現(xiàn)了新大陸一般裆赵,不能自拔双絮。


圖片來自互聯(lián)網(wǎng).png

圖片來自互聯(lián)網(wǎng)

闡述問題

最近發(fā)現(xiàn)一個關(guān)于FastJsonHttpMessageConverter特別有趣的一個點弛矛,它默認的supportMediaType竟然是MediaType.ALL够吩。

    /**
     * Can serialize/deserialize all types.
     */
    public FastJsonHttpMessageConverter() {

        super(MediaType.ALL);
    }

說明FastJsonHttpMessageConverter不配置supportMediaType,那么默認是MediaType.ALL汪诉。

FastJsonHttpMessageConverter應(yīng)該顯式配置其支持的消息格式是MediaType.APPLICATION_JSON_UTF8

    //使用阿里 FastJson 作為JSON MessageConverter
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();

        log.info("converters:" + converters.toString());
        List<MediaType> supportMediaTypeList = new ArrayList<>();
//        supportMediaTypeList.add(MediaType.TEXT_HTML);
          supportMediaTypeList.add(MediaType.APPLICATION_JSON_UTF8);
//        supportMediaTypeList.add(MediaType.IMAGE_GIF);
//        supportMediaTypeList.add(MediaType.IMAGE_JPEG);
//        supportMediaTypeList.add(MediaType.IMAGE_PNG);

        FastJsonConfig config = new FastJsonConfig();
//        config.setDateFormat("yyyy-MM-dd HH:mm:ss");
        config.setSerializerFeatures(
                SerializerFeature.WriteMapNullValue,//保留空的字段
                SerializerFeature.WriteNullStringAsEmpty,//String null -> ""
                SerializerFeature.WriteNullNumberAsZero,//Number null -> 0
                SerializerFeature.WriteNullListAsEmpty,//List null-> []
                SerializerFeature.WriteNullBooleanAsFalse);//Boolean null -> false
        converter.setFastJsonConfig(config);
        converter.setSupportedMediaTypes(supportMediaTypeList);
        converter.setDefaultCharset(Charset.forName("UTF-8"));
        converters.add(converter);
    }

我有一個業(yè)務(wù)場景废恋,需要緩存商品詳情頁谈秫,手動渲染Thymeleaf模板扒寄,在Controller中返回html片段鱼鼓。
配置如下:

    @ResponseBody
    public String list(MiaoshaUser miaoshaUser) throws IOException {
        modelMap.addAttribute("user", miaoshaUser);
        //取緩存
        String htmlCached = redisService.get(GoodsKey.getGoodsList, "", String.class);
        if (!StringUtils.isEmpty(htmlCached)) {
            return htmlCached;
        }
        List<GoodsVo> goodsVoList = goodsService.listGoodsVo();
        modelMap.addAttribute("goodsList", goodsVoList);
        SpringWebContext springWebContext = new SpringWebContext(request, response, request.getServletContext(),
                request.getLocale(), modelMap, applicationContext);
        String html = thymeleafViewResolver.getTemplateEngine().process("goods_list", springWebContext);

        if (!StringUtils.isEmpty(html)) {
            redisService.set(GoodsKey.getGoodsList, "", html);
        }
        return html;
    }

可是最終渲染的界面慘不忍睹,界面出現(xiàn)了大量的\n\t這樣的字符


慘不忍睹.png

如果我不用FastJsonHttpMessageConverter呢该编,界面卻顯示很正常迄本,這是為什么呢。


正常.png

雖然在Controller中可以通過手動打開輸出流课竣,設(shè)置ContentType嘉赎,把Thymeleaf模板輸出到Response的body,可以解決問題于樟。

        response.setCharacterEncoding("UTF-8");
        response.setHeader("Content-type", "application/json;charset=UTF-8");
        response.setStatus(200);

        try {
            response.getWriter().write(html);
        } catch (IOException ex) {
            log.error(ex.getMessage());
        }

但是本著阿Q精神公条,我Debug消息轉(zhuǎn)換器的流程,發(fā)現(xiàn)問題出在FastJsonHttpMessageConverter沒有配置MediaType.APPLICATION_JSON_UTF8,
導致其默認的MediaType.ALL影響Thymeleaf模板最后的輸出迂曲。


Debug之路

RequestResponseBodyProcessor實現(xiàn)了HandlerMethodReturnValueHandler靶橱、HandlerMethodAgumentResolver接口,實現(xiàn)了boolean supportsParamter()路捧、void resolveArgument()关霸、boolean supportsReturnType()void handleReturnValue()
所以具備了處理參數(shù)杰扫、處理返回值的能力队寇。官方解釋.RequestResponseBodyProcessor能夠解析用@RequestBody注解的參數(shù)和通過使用HttpMessageConverter讀取并寫入請求體或響應(yīng)來處理用@ResponseBody注解的方法的返回值。

/**
 * 處理返回值
 */
public interface HandlerMethodReturnValueHandler {

    /**
     * 方法返回值是否被處理程序支持
     */
    boolean supportsReturnType(MethodParameter returnType);

    /**
     * 通過向模型添加屬性并設(shè)置視圖或設(shè)置視圖來處理給定的返回值
     * {@link ModelAndViewContainer#setRequestHandled}標志到{@code true}章姓,表明響應(yīng)已被直接處理佳遣。
     */
    void handleReturnValue(Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}

/**
 * 解析請求中的參數(shù)值
 */
public interface HandlerMethodArgumentResolver {

    /**
     * 是否能支持解析{@linkplain MethodParameter參數(shù)}
     */
    boolean supportsParameter(MethodParameter parameter);

    /**
     * 將方法參數(shù)解析為來自給定請求的參數(shù)值。
     */
    Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;

}

public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {}

public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver
        implements HandlerMethodReturnValueHandler {}

public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver {}

handleReturnValue()獲取inputMessage,outMessage對象凡伊,調(diào)用writeWithMessageConverters()方法零渐,讓消息轉(zhuǎn)換器對消息進行下一步處理.

public interface HttpMessage {

    /**
     * Return the headers of this message.
     * @return a corresponding HttpHeaders object (never {@code null})
     */
    HttpHeaders getHeaders();

}
public interface HttpOutputMessage extends HttpMessage {

    /**
     * Return the body of the message as an output stream.
     * @return the output stream body (never {@code null})
     * @throws IOException in case of I/O Errors
     */
    OutputStream getBody() throws IOException;

}
public interface HttpInputMessage extends HttpMessage {

    /**
     * Return the body of the message as an input stream.
     * @return the input stream body (never {@code null})
     * @throws IOException in case of I/O Errors
     */
    InputStream getBody() throws IOException;

}
    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
            throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

        mavContainer.setRequestHandled(true);
        ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
        ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

        // Try even with null return value. ResponseBodyAdvice could get involved.
        writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
    }

AbstarctMessageConverterProcessorwriteWithMessageConverters()中,
根據(jù)HandlerMethod中的ReturnValueMethodParameter對象獲valueType(返回值類型),同時也能得到outputValue(返回值)窗声、decalredType(返回值實際類型)

        Object outputValue;
        Class<?> valueType;
        Type declaredType;

        if (value instanceof CharSequence) {
            outputValue = value.toString();
            valueType = String.class;
            declaredType = String.class;
        }
        else {
            outputValue = value;
            valueType = getReturnValueType(outputValue, returnType);
            declaredType = getGenericType(returnType);
        }

根據(jù)request對象中獲取請求中Accept的屬性值相恃,得requestedMediaTypes

    List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);

    private List<MediaType> getAcceptableMediaTypes(HttpServletRequest request) throws HttpMediaTypeNotAcceptableException {
        List<MediaType> mediaTypes = this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));
        return (mediaTypes.isEmpty() ? Collections.singletonList(MediaType.ALL) : mediaTypes);
    }


    @Override
    public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
        for (ContentNegotiationStrategy strategy : this.strategies) {
            List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
            if (mediaTypes.isEmpty() || mediaTypes.equals(MEDIA_TYPE_ALL)) {
                continue;
            }
            return mediaTypes;
        }
        return Collections.emptyList();
    }

根據(jù)HandlerMapping中的produces屬性獲得producibleMediaTypes。
如果沒有顯式設(shè)置produces屬性笨觅,我們只能通過遍歷所有的HttpMessageConverter拦耐,通過canWrite()方法找到支持解析Java對象的HttpMessageConverter,并且把其所支持的mediaType加入mediaTypes集合里面见剩。

List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);
    protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> valueClass, Type declaredType) {
        Set<MediaType> mediaTypes = (Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
        if (!CollectionUtils.isEmpty(mediaTypes)) {
            return new ArrayList<MediaType>(mediaTypes);
        }
        else if (!this.allSupportedMediaTypes.isEmpty()) {
            List<MediaType> result = new ArrayList<MediaType>();
            for (HttpMessageConverter<?> converter : this.messageConverters) {
                if (converter instanceof GenericHttpMessageConverter && declaredType != null) {
                    if (((GenericHttpMessageConverter<?>) converter).canWrite(declaredType, valueClass, null)) {
                        result.addAll(converter.getSupportedMediaTypes());
                    }
                }
                else if (converter.canWrite(valueClass, null)) {
                    result.addAll(converter.getSupportedMediaTypes());
                }
            }
            return result;
        }
        else {
            return Collections.singletonList(MediaType.ALL);
        }
    }

通過兩次for循環(huán)杀糯,比較requestedMediaTypes和producibleMediaTypes
得到compatiableMediaTypes。如果compatiableMediaTypes為空苍苞,會拋出HttpMediaTypeNotAcceptableException異常固翰。白話意思就是producibleMediaTypes沒有一個MediaType與requestedMediaTypes匹配狼纬,肯定無法執(zhí)行下一步了。

        if (outputValue != null && producibleMediaTypes.isEmpty()) {
            throw new IllegalArgumentException("No converter found for return value of type: " + valueType);
        }

        Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
        for (MediaType requestedType : requestedMediaTypes) {
            for (MediaType producibleType : producibleMediaTypes) {
                if (requestedType.isCompatibleWith(producibleType)) {
                    compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
                }
            }
        }
        if (compatibleMediaTypes.isEmpty()) {
            if (outputValue != null) {
                throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
            }
            return;
        }

排序骂际、for循環(huán)compatiableMediaTypes,通過isConcrete()判斷消息格式是否具體(類型和子類型是否為通配符*)疗琉,得到selectedMediaType(最終的MediaType)

        List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
        MediaType.sortBySpecificityAndQuality(mediaTypes);

        MediaType selectedMediaType = null;
        for (MediaType mediaType : mediaTypes) {
            if (mediaType.isConcrete()) {
                selectedMediaType = mediaType;
                break;
            }
            else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
                selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
                break;
            }
        }

for循環(huán)已配置所有的HttpMessageConverter,調(diào)用canWrite()方法歉铝,根據(jù)valueType(返回值類型)和selectedMediaType來判斷消息是否可以轉(zhuǎn)換盈简。

        if (selectedMediaType != null) {
            selectedMediaType = selectedMediaType.removeQualityValue();
            for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
                if (messageConverter instanceof GenericHttpMessageConverter) {
                    if (((GenericHttpMessageConverter) messageConverter).canWrite(
                            declaredType, valueType, selectedMediaType)) {
                        outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
                                (Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
                                inputMessage, outputMessage);
                        if (outputValue != null) {
                            addContentDispositionHeader(inputMessage, outputMessage);
                            ((GenericHttpMessageConverter) messageConverter).write(
                                    outputValue, declaredType, selectedMediaType, outputMessage);
                            if (logger.isDebugEnabled()) {
                                logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
                                        "\" using [" + messageConverter + "]");
                            }
                        }
                        return;
                    }
                }
                else if (messageConverter.canWrite(valueType, selectedMediaType)) {
                    outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
                            (Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
                            inputMessage, outputMessage);
                    if (outputValue != null) {
                        addContentDispositionHeader(inputMessage, outputMessage);
                        ((HttpMessageConverter) messageConverter).write(outputValue, selectedMediaType, outputMessage);
                        if (logger.isDebugEnabled()) {
                            logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
                                    "\" using [" + messageConverter + "]");
                        }
                    }
                    return;
                }
            }
        }

        if (outputValue != null) {
            throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
        }

如果沒有配置MediaType.APPLICATION_JSON_UTF8,默認值是MediaType.ALLFastJsonHttpMessageConverter會去處理消息格式為"text/html;charset=UTF-8"太示,會執(zhí)行這一段代碼

                if (messageConverter instanceof GenericHttpMessageConverter) {
                    if (((GenericHttpMessageConverter) messageConverter).canWrite(
                            declaredType, valueType, selectedMediaType)) {
                        outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
                                (Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
                                inputMessage, outputMessage);
                        if (outputValue != null) {
                            addContentDispositionHeader(inputMessage, outputMessage);
                            ((GenericHttpMessageConverter) messageConverter).write(
                                    outputValue, declaredType, selectedMediaType, outputMessage);
                            if (logger.isDebugEnabled()) {
                                logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
                                        "\" using [" + messageConverter + "]");
                            }
                        }
                        return;
                    }
                }

接著調(diào)用FastJsonHttpMessageConverter的write()方法寫入消息

    /*
     * @see org.springframework.http.converter.GenericHttpMessageConverter.write
     */
    public void write(Object o, Type type, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        super.write(o, contentType, outputMessage);// support StreamingHttpOutputMessage in spring4.0+
        //writeInternal(o, outputMessage);
    }

接著調(diào)用AbstaractHttpMessageConverter中的write()寫入響應(yīng)頭和消息內(nèi)容

    /**
     * This implementation sets the default headers by calling {@link #addDefaultHeaders},
     * and then calls {@link #writeInternal}.
     */
    @Override
    public final void write(final T t, MediaType contentType, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException {

        final HttpHeaders headers = outputMessage.getHeaders();
        addDefaultHeaders(headers, t, contentType);

        if (outputMessage instanceof StreamingHttpOutputMessage) {
            StreamingHttpOutputMessage streamingOutputMessage =
                    (StreamingHttpOutputMessage) outputMessage;
            streamingOutputMessage.setBody(new StreamingHttpOutputMessage.Body() {
                @Override
                public void writeTo(final OutputStream outputStream) throws IOException {
                    writeInternal(t, new HttpOutputMessage() {
                        @Override
                        public OutputStream getBody() throws IOException {
                            return outputStream;
                        }
                        @Override
                        public HttpHeaders getHeaders() {
                            return headers;
                        }
                    });
                }
            });
        }
        else {
            writeInternal(t, outputMessage);
            outputMessage.getBody().flush();
        }
    }

接著調(diào)用FastJsonHttpMessageConverter的writeInternal()進行消息轉(zhuǎn)換

    @Override
    protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {

        ByteArrayOutputStream outnew = new ByteArrayOutputStream();
        try {
            HttpHeaders headers = outputMessage.getHeaders();

            //獲取全局配置的filter
            SerializeFilter[] globalFilters = fastJsonConfig.getSerializeFilters();
            List<SerializeFilter> allFilters = new ArrayList<SerializeFilter>(Arrays.asList(globalFilters));

            boolean isJsonp = false;

            //不知道為什么會有這行代碼柠贤, 但是為了保持和原來的行為一致,還是保留下來
            Object value = strangeCodeForJackson(object);

            if (value instanceof FastJsonContainer) {
                FastJsonContainer fastJsonContainer = (FastJsonContainer) value;
                PropertyPreFilters filters = fastJsonContainer.getFilters();
                allFilters.addAll(filters.getFilters());
                value = fastJsonContainer.getValue();
            }

            //revise 2017-10-23 ,
            // 保持原有的MappingFastJsonValue對象的contentType不做修改 保持舊版兼容类缤。
            // 但是新的JSONPObject將返回標準的contentType:application/javascript 臼勉,不對是否有function進行判斷
            if (value instanceof MappingFastJsonValue) {
                if(!StringUtils.isEmpty(((MappingFastJsonValue) value).getJsonpFunction())){
                    isJsonp = true;
                }
            } else if (value instanceof JSONPObject) {
                isJsonp = true;
            }


            int len = JSON.writeJSONString(outnew, //
                    fastJsonConfig.getCharset(), //
                    value, //
                    fastJsonConfig.getSerializeConfig(), //
                    //fastJsonConfig.getSerializeFilters(), //
                    allFilters.toArray(new SerializeFilter[allFilters.size()]),
                    fastJsonConfig.getDateFormat(), //
                    JSON.DEFAULT_GENERATE_FEATURE, //
                    fastJsonConfig.getSerializerFeatures());

            if (isJsonp) {
                headers.setContentType(APPLICATION_JAVASCRIPT);
            }

            if (fastJsonConfig.isWriteContentLength()) {
                headers.setContentLength(len);
            }

            outnew.writeTo(outputMessage.getBody());

        } catch (JSONException ex) {
            throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
        } finally {
            outnew.close();
        }
    }

我們可以觀察到這個方法中有一個JSON.writeJSONString()方法,我點進去看一看

    public static final int writeJSONString(OutputStream os, // 
                                             Charset charset, // 
                                             Object object, // 
                                             SerializeConfig config, //
                                             SerializeFilter[] filters, //
                                             String dateFormat, //
                                             int defaultFeatures, //
                                             SerializerFeature... features) throws IOException {
        SerializeWriter writer = new SerializeWriter(null, defaultFeatures, features);

        try {
            JSONSerializer serializer = new JSONSerializer(writer, config);
            
            if (dateFormat != null && dateFormat.length() != 0) {
                serializer.setDateFormat(dateFormat);
                serializer.config(SerializerFeature.WriteDateUseDateFormat, true);
            }

            if (filters != null) {
                for (SerializeFilter filter : filters) {
                    serializer.addFilter(filter);
                }
            }
            
            serializer.write(object);
            
            int len = writer.writeToEx(os, charset);
            return len;
        } finally {
            writer.close();
        }
    }

一看嚇一跳餐弱。臥槽宴霸,把html序列化了,GG岸裙。終于找到問題的始作俑者了猖败。

如果配置了MediaType.APPLICATION_JSON_UTF8,FastJsonHttpMessageConverter
只能處理"application/json;charset=UTF-8"的消息,"text/html;charset=UTF-8"格式的消息被StringHttpMessageConverter得到了處理,會執(zhí)行這一段代碼

                else if (messageConverter.canWrite(valueType, selectedMediaType)) {
                    outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
                            (Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
                            inputMessage, outputMessage);
                    if (outputValue != null) {
                        addContentDispositionHeader(inputMessage, outputMessage);
                        ((HttpMessageConverter) messageConverter).write(outputValue, selectedMediaType, outputMessage);
                        if (logger.isDebugEnabled()) {
                            logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
                                    "\" using [" + messageConverter + "]");
                        }
                    }
                    return;
                }

接著調(diào)用AbstaractHttpMessageConverter中的write()寫入響應(yīng)頭和消息內(nèi)容

    @Override
    public final void write(final T t, MediaType contentType, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException {

        final HttpHeaders headers = outputMessage.getHeaders();
        addDefaultHeaders(headers, t, contentType);

        if (outputMessage instanceof StreamingHttpOutputMessage) {
            StreamingHttpOutputMessage streamingOutputMessage =
                    (StreamingHttpOutputMessage) outputMessage;
            streamingOutputMessage.setBody(new StreamingHttpOutputMessage.Body() {
                @Override
                public void writeTo(final OutputStream outputStream) throws IOException {
                    writeInternal(t, new HttpOutputMessage() {
                        @Override
                        public OutputStream getBody() throws IOException {
                            return outputStream;
                        }
                        @Override
                        public HttpHeaders getHeaders() {
                            return headers;
                        }
                    });
                }
            });
        }
        else {
            writeInternal(t, outputMessage);
            outputMessage.getBody().flush();
        }
    }

最終調(diào)用StringHttpMessageConverter的writeInternal寫入消息到outputMessage.getBody()中降允,輸出html片段恩闻。

    @Override
    protected void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException {
        if (this.writeAcceptCharset) {
            outputMessage.getHeaders().setAcceptCharset(getAcceptedCharsets());
        }
        Charset charset = getContentTypeCharset(outputMessage.getHeaders().getContentType());
        StreamUtils.copy(str, charset, outputMessage.getBody());
    }

關(guān)于HttpMessageConverter加載順序,可以在WebMvcConfigurationSupport看到端倪剧董。

    protected final List<HttpMessageConverter<?>> getMessageConverters() {
        if (this.messageConverters == null) {
            this.messageConverters = new ArrayList<HttpMessageConverter<?>>();
            configureMessageConverters(this.messageConverters);
            if (this.messageConverters.isEmpty()) {
                addDefaultHttpMessageConverters(this.messageConverters);
            }
            extendMessageConverters(this.messageConverters);
        }
        return this.messageConverters;
    }
    
        protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
        StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
        stringConverter.setWriteAcceptCharset(false);

        messageConverters.add(new ByteArrayHttpMessageConverter());
        messageConverters.add(stringConverter);
        messageConverters.add(new ResourceHttpMessageConverter());
        messageConverters.add(new SourceHttpMessageConverter<Source>());
        messageConverters.add(new AllEncompassingFormHttpMessageConverter());

        if (romePresent) {
            messageConverters.add(new AtomFeedHttpMessageConverter());
            messageConverters.add(new RssChannelHttpMessageConverter());
        }

        if (jackson2XmlPresent) {
            messageConverters.add(new MappingJackson2XmlHttpMessageConverter(
                    Jackson2ObjectMapperBuilder.xml().applicationContext(this.applicationContext).build()));
        }
        else if (jaxb2Present) {
            messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
        }

        if (jackson2Present) {
            messageConverters.add(new MappingJackson2HttpMessageConverter(
                    Jackson2ObjectMapperBuilder.json().applicationContext(this.applicationContext).build()));
        }
        else if (gsonPresent) {
            messageConverters.add(new GsonHttpMessageConverter());
        }
    }

我們其實是重寫configureMessageConverters()方法去配置FastJsonHttpMessageConverter,所以它是第一個幢尚。


尾言

等休息的時候,再寫Spring MVC源碼分析請求響應(yīng)流程翅楼,源碼分析RequestMappingHandlerMapping和RequestMappingHandlerAdapter尉剩。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市毅臊,隨后出現(xiàn)的幾起案子理茎,更是在濱河造成了極大的恐慌,老刑警劉巖管嬉,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件皂林,死亡現(xiàn)場離奇詭異,居然都是意外死亡蚯撩,警方通過查閱死者的電腦和手機础倍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來胎挎,“玉大人沟启,你說我怎么就攤上這事忆家。” “怎么了德迹?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵芽卿,是天一觀的道長。 經(jīng)常有香客問我浦辨,道長蹬竖,這世上最難降的妖魔是什么沼沈? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任流酬,我火速辦了婚禮,結(jié)果婚禮上列另,老公的妹妹穿的比我還像新娘芽腾。我一直安慰自己,他們只是感情好页衙,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布摊滔。 她就那樣靜靜地躺著,像睡著了一般店乐。 火紅的嫁衣襯著肌膚如雪艰躺。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天眨八,我揣著相機與錄音腺兴,去河邊找鬼。 笑死廉侧,一個胖子當著我的面吹牛页响,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播段誊,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼闰蚕,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了连舍?” 一聲冷哼從身側(cè)響起没陡,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎索赏,沒想到半個月后盼玄,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡参滴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年强岸,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片砾赔。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡蝌箍,死狀恐怖青灼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情妓盲,我是刑警寧澤杂拨,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站悯衬,受9級特大地震影響弹沽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜筋粗,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一策橘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧娜亿,春花似錦丽已、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至督赤,卻和暖如春嘁灯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背躲舌。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工丑婿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人孽糖。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓枯冈,卻偏偏與公主長得像,于是被迫代替她去往敵國和親办悟。 傳聞我的和親對象是個殘疾皇子尘奏,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)病蛉,斷路器炫加,智...
    卡卡羅2017閱讀 134,599評論 18 139
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong閱讀 22,313評論 1 92
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,748評論 6 342
  • 不怕千萬人阻擋铺然,只怕自己投降 曾對自己許下過無數(shù)的愿望俗孝,一定要怎樣怎樣,可是堅持魄健,放棄赋铝,放棄,堅持沽瘦,到最后一事無成...
    月非寒不寒閱讀 705評論 0 0
  • 不滿一歲八個月的小寶,這些天時不時會來上一句''我要去上學"的豪言壯語良哲! 每天盛卡,爸爸媽媽去上班,姐姐去上學筑凫,他卻只...
    July梅閱讀 250評論 0 2