SpringMVC關於json、xml自動轉換的原理研究


本文討論Spring MVC關於json、xml自動轉換的原理。

實現這個功能只需要三個配置

1.Spring MVC配置文件dispatcher-servlet.xml中的關鍵配置如下

<!-- 配置注解驅動 -->
<mvc:annotation-driven/>

2.pom中需要有以下依賴(Spring依賴及其他依賴不顯示):

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.5.4</version>
</dependency>

這個依賴是json序列化的依賴,只需要一個jackson-databind,會自動引入其他兩個依賴。

3.我們在Controller中添加一個method:

@Controller public class MainController { @RequestMapping("/xmlOrJson") @ResponseBody public Map<String, Object> xmlOrJson() { Map<String, Object> map = new HashMap<String, Object>(); map.put("list", new User()); return map; } }

 直接訪問地址:

我們看到,短短幾行配置,使用@ResponseBody注解之后,Controller返回的對象(map)自動被轉換成對應的json數據。

其實就是dispatcher-servlet.xml中的一句配置:<mvc:annotation-driven/>導致了java對象自動轉換成json對象的發生。

那么Spring MVC到底是如何實現java對象到json對象的自動轉換的呢?如果想轉換成xml數據,那該怎么辦?

源碼分析

在講解<mvc:annotation-driven/>這個配置之前,我們先了解下Spring MVC的消息轉換機制。@ResponseBody這個注解就是使用消息轉換機制,最終通過json的轉換器轉換成json數據的。

HttpMessageConverter接口就是Spring MVC提供的http消息轉換接口。

HttpMessageConverter<T>接口定義以下幾個方法:

/**
 * Strategy interface that specifies a converter that can convert from and to HTTP requests and responses.
 */
public interface HttpMessageConverter<T> {

    /**
     * Indicates whether the given class can be read by this converter.
     */
    boolean canRead(Class<?> clazz, MediaType mediaType);

    /**
     * Indicates whether the given class can be written by this converter.
     */
    boolean canWrite(Class<?> clazz, MediaType mediaType);

    /**
     * Return the list of {@link MediaType} objects supported by this converter.
     */
    List<MediaType> getSupportedMediaTypes();

    /**
     * Read an object of the given type form the given input message, and returns it.
     */
    T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException;

    /**
     * Write an given object to the given output message.
     */
    void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException;

}

下面開始分析<mvc:annotation-driven/>這句配置:

這句代碼在spring中的解析類是:

 

在AnnotationDrivenBeanDefinitionParser源碼parse()方法中分別實例化了RequestMappingHandlerMapping,ConfigurableWebBindingInitializer,RequestMappingHandlerAdapter等諸多類。

RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
RootBeanDefinition bindingDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class);
RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
RootBeanDefinition exceptionHandlerExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);
parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, methodMappingName));
parserContext.registerComponent(new BeanComponentDefinition(handlerAdapterDef, handlerAdapterName));

其中RequestMappingHandlerMapping處理請求映射的,處理@RequestMapping跟請求地址之間的關系。

RequestMappingHandlerAdapter是請求處理的適配器,也就是請求之后處理具體邏輯的執行,關系到哪個類的哪個方法以及轉換器等工作,這個類是我們講的重點,其中它的屬性messageConverters是本文要講的重點。

ManagedList<?> messageConverters = getMessageConverters(element, source, parserContext);

RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
handlerAdapterDef.setSource(source);
handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
handlerAdapterDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
handlerAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
addResponseBodyAdvice(handlerAdapterDef);

getMessageConverters()方法的實現是什么樣的呢?

private ManagedList<?> getMessageConverters(Element element, Object source, ParserContext parserContext) {
    Element convertersElement = DomUtils.getChildElementByTagName(element, "message-converters");
    ManagedList<? super Object> messageConverters = new ManagedList<Object>();
    if (convertersElement != null) {
        messageConverters.setSource(source);
        for (Element beanElement : DomUtils.getChildElementsByTagName(convertersElement, "bean", "ref")) {
            Object object = parserContext.getDelegate().parsePropertySubElement(beanElement, null);
            messageConverters.add(object);
        }
    }

    if (convertersElement == null || Boolean.valueOf(convertersElement.getAttribute("register-defaults"))) {
        messageConverters.setSource(source);
        messageConverters.add(createConverterDefinition(ByteArrayHttpMessageConverter.class, source));

        RootBeanDefinition stringConverterDef = createConverterDefinition(StringHttpMessageConverter.class, source);
        stringConverterDef.getPropertyValues().add("writeAcceptCharset", false);
        messageConverters.add(stringConverterDef);

        messageConverters.add(createConverterDefinition(ResourceHttpMessageConverter.class, source));
        messageConverters.add(createConverterDefinition(SourceHttpMessageConverter.class, source));
        messageConverters.add(createConverterDefinition(AllEncompassingFormHttpMessageConverter.class, source));

        if (romePresent) {
            messageConverters.add(createConverterDefinition(AtomFeedHttpMessageConverter.class, source));
            messageConverters.add(createConverterDefinition(RssChannelHttpMessageConverter.class, source));
        }

        if (jackson2XmlPresent) {
            RootBeanDefinition jacksonConverterDef = createConverterDefinition(MappingJackson2XmlHttpMessageConverter.class, source);
            GenericBeanDefinition jacksonFactoryDef = createObjectMapperFactoryDefinition(source);
            jacksonFactoryDef.getPropertyValues().add("createXmlMapper", true);
            jacksonConverterDef.getConstructorArgumentValues().addIndexedArgumentValue(0, jacksonFactoryDef);
            messageConverters.add(jacksonConverterDef);
        }
        else if (jaxb2Present) {
            messageConverters.add(createConverterDefinition(Jaxb2RootElementHttpMessageConverter.class, source));
        }

        if (jackson2Present) {
            RootBeanDefinition jacksonConverterDef = createConverterDefinition(MappingJackson2HttpMessageConverter.class, source);
            GenericBeanDefinition jacksonFactoryDef = createObjectMapperFactoryDefinition(source);
            jacksonConverterDef.getConstructorArgumentValues().addIndexedArgumentValue(0, jacksonFactoryDef);
            messageConverters.add(jacksonConverterDef);
        }
        else if (gsonPresent) {
            messageConverters.add(createConverterDefinition(GsonHttpMessageConverter.class, source));
        }
    }
    return messageConverters;
}

從代碼中我們可以看到RequestMappingHandlerAdapter設置messageConverters的邏輯:

1.如果<mvc:annotation-driven>節點有子節點message-converters,messageConverters也由配置的轉換器組成。

Element convertersElement = DomUtils.getChildElementByTagName(element, "message-converters");
ManagedList<? super Object> messageConverters = new ManagedList<Object>();
if (convertersElement != null) {
    messageConverters.setSource(source);
    for (Element beanElement : DomUtils.getChildElementsByTagName(convertersElement, "bean", "ref")) {
        Object object = parserContext.getDelegate().parsePropertySubElement(beanElement, null);
        messageConverters.add(object);
    }
}

message-converters的子節點配置如下:

<mvc:annotation-driven>
  <mvc:message-converters>
    <bean class="org.winner.HttpMessageConverter1"/>
    <bean class="org.winner.HttpMessageConverter2"/>
  </mvc:message-converters>
</mvc:annotation-driven>

2.如果message-converters子節點不存在或它的屬性register-defaults為true的話,默認注冊若干轉換器

if (convertersElement == null || Boolean.valueOf(convertersElement.getAttribute("register-defaults"))) {
    messageConverters.setSource(source);
    messageConverters.add(createConverterDefinition(ByteArrayHttpMessageConverter.class, source));

    RootBeanDefinition stringConverterDef = createConverterDefinition(StringHttpMessageConverter.class, source);
    stringConverterDef.getPropertyValues().add("writeAcceptCharset", false);
    messageConverters.add(stringConverterDef);

    messageConverters.add(createConverterDefinition(ResourceHttpMessageConverter.class, source));
    messageConverters.add(createConverterDefinition(SourceHttpMessageConverter.class, source));
    messageConverters.add(createConverterDefinition(AllEncompassingFormHttpMessageConverter.class, source));
}

除此之外,有可能會加入另外幾種轉換器,加不加很明顯取決於這幾個bool值

if (romePresent) {
    messageConverters.add(createConverterDefinition(AtomFeedHttpMessageConverter.class, source));
    messageConverters.add(createConverterDefinition(RssChannelHttpMessageConverter.class, source));
}

if (jackson2XmlPresent) {
    RootBeanDefinition jacksonConverterDef = createConverterDefinition(MappingJackson2XmlHttpMessageConverter.class, source);
    GenericBeanDefinition jacksonFactoryDef = createObjectMapperFactoryDefinition(source);
    jacksonFactoryDef.getPropertyValues().add("createXmlMapper", true);
    jacksonConverterDef.getConstructorArgumentValues().addIndexedArgumentValue(0, jacksonFactoryDef);
    messageConverters.add(jacksonConverterDef);
}
else if (jaxb2Present) {
    messageConverters.add(createConverterDefinition(Jaxb2RootElementHttpMessageConverter.class, source));
}

if (jackson2Present) {
    RootBeanDefinition jacksonConverterDef = createConverterDefinition(MappingJackson2HttpMessageConverter.class, source);
    GenericBeanDefinition jacksonFactoryDef = createObjectMapperFactoryDefinition(source);
    jacksonConverterDef.getConstructorArgumentValues().addIndexedArgumentValue(0, jacksonFactoryDef);
    messageConverters.add(jacksonConverterDef);
}
else if (gsonPresent) {
    messageConverters.add(createConverterDefinition(GsonHttpMessageConverter.class, source));
}

這些boolean屬性是哪里來的呢,它們是AnnotationDrivenBeanDefinitionParser的靜態變量。

private static final boolean javaxValidationPresent =
        ClassUtils.isPresent("javax.validation.Validator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
private static boolean romePresent =
        ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
private static final boolean jaxb2Present =
        ClassUtils.isPresent("javax.xml.bind.Binder", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
private static final boolean jackson2Present =
        ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader()) &&
                ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
private static final boolean jackson2XmlPresent =
        ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
private static final boolean gsonPresent =
        ClassUtils.isPresent("com.google.gson.Gson", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());

其中ClassUtils中的isPresent方法如下:

public static boolean isPresent(String className, ClassLoader classLoader) { try { forName(className, classLoader); return true; } catch (Throwable var3) { return false; } }

看到這里,我們就知道為什么在pom文件中需要加入對應的jackson依賴,為了讓json轉換器jackson成為默認轉換器之一。

<mvc:annotation-driven>的作用也明白了。

下面我們看如何通過消息轉換器將java對象進行轉換的。

RequestMappingHandlerAdapter在進行handle處理的時候,會委托給HandlerMethod(具體由子類ServletInvocableHandlerMethod處理)的invokeAndHandle方法進行處理,這個方法又轉接給HandlerMethodReturnValueHandlerComposite處理。

HandlerMethodReturnValueHandlerComposite維護了一個HandlerMethodReturnValueHandler列表。HandlerMethodReturnValueHandler是一個對返回值進行處理的策略接口,這個接口非常重要。然后找到對應的HandlerMethodReturnValueHandler對結果值進行處理。

最終找到RequestResponseBodyMethodProcessor這個Handler(由於使用了@ResponseBody注解)。

RequestResponseBodyMethodProcessor的supportsReturnType方法:

@Override public boolean supportsReturnType(MethodParameter returnType) { return ((AnnotationUtils.findAnnotation(returnType.getContainingClass(), ResponseBody.class) != null) || (returnType.getMethodAnnotation(ResponseBody.class) != null)); }

然后使用handleReturnValue方法進行處理:

@Override public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException { mavContainer.setRequestHandled(true); if (returnValue != null) { writeWithMessageConverters(returnValue, returnType, webRequest); } }

我們看到,這里使用了轉換器。  

具體的轉換方法:

/**
 * Writes the given return type to the given output message.
 *
 */
@SuppressWarnings("unchecked")
protected <T> void writeWithMessageConverters(T returnValue,
                                            MethodParameter returnType,
                                            ServletServerHttpRequest inputMessage,
                                            ServletServerHttpResponse outputMessage)
        throws IOException, HttpMediaTypeNotAcceptableException {

    Class<?> returnValueClass = returnValue.getClass();

    HttpServletRequest servletRequest = inputMessage.getServletRequest();
    //獲得請求信息的Accept信息
    List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(servletRequest);
    //轉換器中支持的Accept的MediaType數據
    List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass);

    Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
    for (MediaType r : requestedMediaTypes) {
        for (MediaType p : producibleMediaTypes) {
            //轉換器中支持的Accept數據是否兼容請求過來的Accept
            if (r.isCompatibleWith(p)) {
                compatibleMediaTypes.add(getMostSpecificMediaType(r, p));
            }
        }
    }
    if (compatibleMediaTypes.isEmpty()) {
        throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
    }

    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;
        }
    }

    if (selectedMediaType != null) {
        selectedMediaType = selectedMediaType.removeQualityValue();
        for (HttpMessageConverter<?> messageConverter : messageConverters) {
            if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
                ((HttpMessageConverter<T>) messageConverter).write(returnValue, selectedMediaType, outputMessage);
                if (logger.isDebugEnabled()) {
                    logger.debug("Written [" + returnValue + "] as \"" + selectedMediaType + "\" using [" +
                            messageConverter + "]");//最終根據class和mediaType進行判斷
                }
                return;
            }
        }
    }
    throw new HttpMediaTypeNotAcceptableException(allSupportedMediaTypes);
}
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);
}
protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> returnValueClass) {
    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.canWrite(returnValueClass, null)) {
                result.addAll(converter.getSupportedMediaTypes());
            }
        }
        return result;
    }
    else {
        return Collections.singletonList(MediaType.ALL);
    }
}    

現在,回過頭來看。為什么一開始的demo輸出了json數據?

我們來分析吧。

由於我們只配置了<mvc:annotation-driven>,因此使用spring默認的那些轉換器。

 

很明顯,我們看到了2個xml和1個json轉換器。要看能不能轉換,得看HttpMessageConverter接口的public boolean canWrite(Class<?> clazz, MediaType mediaType)方法是否返回true來決定的。

我們先分析SourceHttpMessageConverter:

它的canWrite方法被父類AbstractHttpMessageConverter重寫了。

@Override public boolean canWrite(Class<?> clazz, MediaType mediaType) { return supports(clazz) && canWrite(mediaType); }
@Override public boolean supports(Class<?> clazz) { return SUPPORTED_CLASSES.contains(clazz); }
public class SourceHttpMessageConverter<T extends Source> extends AbstractHttpMessageConverter<T> { private static final Set<Class<?>> SUPPORTED_CLASSES = new HashSet<Class<?>>(5); static { SUPPORTED_CLASSES.add(DOMSource.class); SUPPORTED_CLASSES.add(SAXSource.class); SUPPORTED_CLASSES.add(StAXSource.class); SUPPORTED_CLASSES.add(StreamSource.class); SUPPORTED_CLASSES.add(Source.class); }

發現SUPPORTED_CLASSES中沒有Map類(本文demo返回的是Map類),因此不支持。

下面看Jaxb2RootElementHttpMessageConverter:

這個類直接重寫了canWrite方法。

@Override public boolean canRead(Class<?> clazz, MediaType mediaType) { return (clazz.isAnnotationPresent(XmlRootElement.class) || clazz.isAnnotationPresent(XmlType.class)) && canRead(mediaType); }

需要有XmlRootElement注解。 很明顯,Map類當然沒有。

最終MappingJackson2HttpMessageConverter匹配,進行json轉換。

@Override public boolean canWrite(Class<?> clazz, MediaType mediaType) { if (!jackson23Available || !logger.isWarnEnabled()) { return (this.objectMapper.canSerialize(clazz) && canWrite(mediaType)); } AtomicReference<Throwable> causeRef = new AtomicReference<Throwable>(); if (this.objectMapper.canSerialize(clazz, causeRef) && canWrite(mediaType)) { return true; } Throwable cause = causeRef.get(); if (cause != null) { String msg = "Failed to evaluate serialization for type [" + clazz + "]"; if (logger.isDebugEnabled()) { logger.warn(msg, cause); } else { logger.warn(msg + ": " + cause); } } return false; }

如果不想使用<mvc:annotation-driven/>中默認的RequestMappingHandlerAdapter的話,我們可以在重新定義這個bean,spring會覆蓋掉默認的RequestMappingHandlerAdapter。

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
  <property name="messageConverters">
    <list>
      <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
      <bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
      <bean class="org.springframework.http.converter.ResourceHttpMessageConverter"/>
    </list>
  </property>
</bean>

或者如果只想換messageConverters的話。

<mvc:annotation-driven>
  <mvc:message-converters>
    <bean class="org.example.MyHttpMessageConverter"/>
    <bean class="org.example.MyOtherHttpMessageConverter"/>
  </mvc:message-converters>
</mvc:annotation-driven>

參考:

http://www.cnblogs.com/fangjian0423/p/springMVC-xml-json-convert.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM