首页 > 代码库 > SpringMVC关于json、xml自动转换的原理研究[附带源码分析]

SpringMVC关于json、xml自动转换的原理研究[附带源码分析]

本文讨论SpringMVC关于json、xml自动转换的原理。

实现这个功能只需要三个配置

1.springmvc配置文件 dispatcher-servlet.xml中的关键配置如下

<mvc:resources location="/static/" mapping="/static/**"/>

<!-- 配置包扫描器 -->
<context:component-scan base-package="com.winner.controller"/>

<!-- 配置注解驱动 -->
<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,会自动引入其他两个依赖。

ok。我们在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返回的对象 自动被转换成对应的json数据,在这里不得不感叹SpringMVC的强大。

我们好像也没看到具体的配置,唯一看到的就是dispatcher-servlet.xml中的一句配置:<mvc:annotation-driven/>。其实就是这个配置,导致了java对象自动转换成json对象的现象。

那么spring到底是如何实现java对象到json对象的自动转换的呢? 为什么转换成了json数据,如果想转换成xml数据,那该怎么办?

源码分析

在讲解<mvc:annotation-driven/>这个配置之前,我们先了解下Spring的消息转换机制。@ResponseBody这个注解就是使用消息转换机制,最终通过json的转换器转换成json数据的。

HttpMessageConverter接口就是Spring提供的http消息转换接口。有关这方面的知识大家可以参考"参考资料"中的第二条链接,里面讲的很清楚。

技术分享

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

这句代码在spring中的解析类是:

 技术分享

在AnnotationDrivenBeanDefinitionParser源码的152行parse方法中:

分别实例化了RequestMappingHandlerMapping,ConfigurableWebBindingInitializer,RequestMappingHandlerAdapter等诸多类。

其中RequestMappingHandlerMapping和RequestMappingHandlerAdapter这两个类比较重要。

RequestMappingHandlerMapping处理请求映射的,处理@RequestMapping跟请求地址之间的关系。

RequestMappingHandlerAdapter是请求处理的适配器,也就是请求之后处理具体逻辑的执行,关系到哪个类的哪个方法以及转换器等工作,这个类是我们讲的重点,其中它的属性messageConverters是本文要讲的重点。

 私有方法: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) {//<mvc:annotation-driven>节点有子节点message-converters
        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 (jaxb2Present) {
            messageConverters.add(createConverterDefinition(Jaxb2RootElementHttpMessageConverter.class, source));
        }

        if (jackson2Present) {
            messageConverters.add(createConverterDefinition(MappingJackson2HttpMessageConverter.class, source));
        }
        else if (jacksonPresent) {
            messageConverters.add(createConverterDefinition(
                    org.springframework.http.converter.json.MappingJacksonHttpMessageConverter.class, source));
        }
    }
    return messageConverters;
}

从代码中我们可以,RequestMappingHandlerAdapter设置messageConverters的逻辑:

1.如果<mvc:annotation-driven>节点有子节点message-converters,那么它的转换器属性messageConverters也由这些子节点组成。

message-converters的子节点配置如下:

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

2.message-converters子节点不存在或它的属性register-defaults为true的话,加入其他的转换器:ByteArrayHttpMessageConverter、StringHttpMessageConverter、ResourceHttpMessageConverter等。

我们看到这么一段:

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

if (jackson2Present) {
    messageConverters.add(createConverterDefinition(MappingJackson2HttpMessageConverter.class, source));
}
else if (jacksonPresent) {
    messageConverters.add(createConverterDefinition(
            org.springframework.http.converter.json.MappingJacksonHttpMessageConverter.class, source));
}

这些boolean属性是哪里来的呢,它们是AnnotationDrivenBeanDefinitionParser的静态变量。

class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {

    private static final boolean javaxValidationPresent = ClassUtils.isPresent(
            "javax.validation.Validator", 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 jacksonPresent =
            ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader()) &&
                    ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());

    private static boolean romePresent =
            ClassUtils.isPresent("com.sun.syndication.feed.WireFeed", 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是一个对返回值进行处理的策略接口,这个接口非常重要。关于这个接口的细节,请参考楼主的另外一篇博客:http://www.cnblogs.com/fangjian0423/p/springMVC-request-param-analysis.html。然后找到对应的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.
 *
 * @param returnValue the value to write to the output message
 * @param returnType the type of the value
 * @param inputMessage the input messages. Used to inspect the {@code Accept} header.
 * @param outputMessage the output message to write to
 * @throws IOException thrown in case of I/O errors
 * @throws HttpMediaTypeNotAcceptableException thrown when the conditions indicated by {@code Accept} header on
 * the request cannot be met by the message converters
 */
@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();
    List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(servletRequest);//获得请求信息的Accept信息
    List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass);//转换器中支持的Accept的MediaType数据

    Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
    for (MediaType r : requestedMediaTypes) {
        for (MediaType p : producibleMediaTypes) {
            if (r.isCompatibleWith(p)) {//转换器中支持的Accept数据是否兼容请求过来的Accept
                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);
}

至于为何是请求头部的Accept数据,读者可以进去debug这个getAcceptableMediaTypes方法看看。 我就不罗嗦了~~~

 ok。至此,我们走遍了所有的流程。

现在,回过头来看。为什么一开始的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

SpringMVC关于json、xml自动转换的原理研究[附带源码分析]