zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    「持续集成实践系列」Jenkins 2.x 搭建CI需要掌握的硬核要点
    常用的20个在线工具类网站清单
    推荐一款Python神器,5 行 Python 代码 实现一键批量扣图
    一套测试用例如何实现支持多个环境运行
    推荐一款Python数据可视化神器
    全网独家:成长经历分享 & 我为什么要写书?
    整理一份程序员常用的各类工具、技术站点
    关于《自动化测试实战宝典:Robot Framework + Python从小工到专家》
    重磅新书 |《自动化测试实战宝典:Robot Framework + Python从小工到专家》上市了!
    献给即将35岁的初学者,焦虑 or 出路?
  • 原文地址:https://www.cnblogs.com/winner-0715/p/6512840.html
Copyright © 2011-2022 走看看