zoukankan      html  css  js  c++  java
  • SpringMVC源码阅读:Json,Xml自动转换

    1.前言

    SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧

    本文将通过源码(基于Spring4.3.7)分析,弄清楚SpringMVC如何实现Json,Xml的转换

    2.源码分析

    测试方法,浏览器输入http://localhost:8080/springmvcdemo/employee/xmlOrJson

        @RequestMapping(value="/xmlOrJson",produces={"application/json; charset=UTF-8"})
        @ResponseBody
        public Map<String, Object> xmlOrJson() {
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("list", employeeService.list());
            return map;
        }

    Demo点击这里获取,根据SpringMVC源码阅读:Controller中参数解析我们知道,RequestResponseBodyMethodProcessor支持Json类型数据的转换,我们上回遇到了消息转换器MessageConverter,我没有解释它是什么,这篇文章我们将会揭开它的面纱

    那么,我们就从RequestResponseBodyMethodProcessor开始进行分析,在handleReturnValue方法169行打断点,当有@ResponseBody注解时会进入

    170行获取请求路径、请求信息

    171行获取Content-Type、响应信息

    打开writeWithMessageConverters方法,进入AbstractMessageConverterMethodProcessor类

    167行声明outputValue用来接收Controller返回值

    168行声明valueType接收返回对象类型

    183行requestMediaTypes获取Accept-Type

    184行producibleMediaTypes获取Content-Type,正是我们在@RequestMapping中配置的produces

    190行声明compatibleMediaTypes的Set来获取匹配的MediaTypes,那么它是如何匹配到"application/json"的呢?

    191~197行对requestMediaTypes和producibleMediaTypes循环遍历,进行匹配,得到compatibleMediaTypes

    我们看看requestMediaTypes

    第一到第三个都不是"application/json",第四个使用了终极大招,"*/*"表示所有类型,所以producibleMediaTypes总有类型能与requestMediaTypes匹配上

    继续分析writeWithMessageConverters方法

    221行获取选中的MediaType

    222行遍历HttpMessageConverter

    223行判断当前HttpMessageConverter是不是GenericHttpMessageConverter类型

    GenericHttpMessageConverter是一个接口,它的实现类如下

    根据官网资料,我们知道各种HttpMessageConverter的作用,而MappingJackson2HttpMessageConverter是我们需要的,用以解析Json

    我们需要Jackson2.x jar包来支持MappingJackson2HttpMessageConverter

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

    224行检验当前GenericHttpMessageConverter是否可以被Converter写入

    现在我们要弄清楚,HttpMessageConverter从哪里来,我们点击AbstractMessageConverterMethodProcessor类191行this.messageConverters跳转到了AbstractMessageConverterMethodArgumentResolver,AbstractMessageConverterMethodArgumentResolver是AbstractMessageConverterMethodProcessor的父类,messageConverters是AbstractMessageConverterMethodArgumentResolver的属性,ctrl+f搜索,我们找到了AbstractMessageConverterMethodArgumentResolver的构造方法初始化了HttpMessageConverter

    HttpMessageConverter如下

    来自于我们在dispatcher-servlet.xml自定义的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.json.MappingJackson2HttpMessageConverter"/>
                    <bean class="org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter"/>
                    <bean class="org.springframework.http.converter.ResourceHttpMessageConverter"/>
                </list>
            </property>
        </bean>

     messageConverters是RequestMappingHandlerAdapter的一个list属性,在RequestMappingHandlerAdapter我们配置了五种HttpMessageConverter,包装成list,并注入到Spring

     RequestMappingHandler构造方法给我们加入了默认的HttpMessageConverter,在setMessaageConverters会被我们自定义messageConverters覆盖

     

    this.messageConverters是构造方法加入,messageConverters是我们传入的参数,set方法后于构造方法执行,故覆盖之

    再回到AbstractMessageConverterMethodProcessor类writeWithMessageConverters方法,看下224行canWrite做了什么

    对canWrite ctrl+alt+b,根据父类继承关系,我们锁定AbstractGenericHttpMessageConverter

    继续点击canWrite方法

    在AbstractGenericHttpMessageConverter的父类AbstractHttpMessageConverter里给出了具体实现

    根据官网我们知道MappingJackson2HttpMessageConverter负责转换Json,有必要看下该类的canWrite方法

    打断点我发现,确实进入了该类的canWrite方法,但是并没有做什么事,真正的逻辑在它的父类AbstractHttpMessageConverter处理,刚才我们已经分析过

    Json部分我已经分析完毕,我现在来分析下解析Xml,分析步骤和Json一致,除了解析类不一样

    根据官网我们知道,Jaxb2RootElementHttpMessageConverter和MappingJackson2XMLHttpMessageConverter可以转换Xml

    我们先来看看Jaxb2RootElementHttpMessageConverter的canWrite方法

    显然,想使用Jaxb2RootElementHttpMessageConverter解析Xml需要@XmlRootElement的支持

    我们再来看看MappingJackson2XMLHttpMessageConverter,该类在Spring4.1版本引入,实现了HttpMessageConverter,需要Jackson2.6以上的版本支持

    MappingJackson2XMLHttpMessageConverter在初始化会进入其方法

    50行MappingJackson2XMLHttpMessageConverter无参构造函数负责build ObjectMapper,实质上是build了XmlMapper(ObjectMapper子类)

    60行MappingJackson2XMLHttpMessageConverter有参构造函数继承父类AbstractJackson2HttpMessageConverter构造函数,实例化支持Xml的MediaType

    63行判断ObjectMapper是否是XmlMapper

    MappingJackson2XMLHttpMessageConverter类继承图如下

    我奇怪地发现,MappingJackson2XMLHttpMessageConverter为什么没有canWrite方法,原来它直接用父类AbstractGenericHttpMessageConverter的canWrite,AbstractGenericHttpMessageConverter再调用自身的父类AbstractHttpMessageConverter的canWrite,和我刚才分析Json解析逻辑是一致的

    XmlMapper类可以读取和写入Xml,是一个工具类,我就不叙述了

    最后再说下dispatcher-servlet.xml中<mvc:annotation-driven/>是个什么东西

    查阅官方文档,<mvc:annotation-driven/>自动帮我们注册了

    1. RequestMappingHandlerMapping
    2. RequestMappingHandlerAdapter
    3. ExceptionHandlerExceptionResolver

    RequestMappingHandlerMapping处理请求映射

    RequestMappingHandlerAdapter处理参数和返回值

    ExceptionHandlerExceptionResolver处理异常解析

    参考https://blog.csdn.net/lqzkcx3/article/details/78159708,MVC的前缀由MvcNamespaceHandler解析

    AnnotationDrivenBeanDefinitionParser负责解析annotation-driven注解,AnnotationDrivenBeanDefinitionParser实现了BeanDefinitionParser,我们重点看下parse方法

    188行定义RequestMappingHandlerMapping的Bean

    228行定义RequestMappingHandlerAdapter的Bean

    281行定义ExceptionHandlerExceptionResolver的Bean

    312行注册RequestMappingHandlerMapping

    313行注册RequestMappingHandlerAdapter

    315行注册ExceptionHandlerExceptionResolver

    3.实例

    3.1 测试MappingJackson2HttpMessageConverter解析Json

    前文说过

        @RequestMapping(value="/returnJson",produces={"application/json; charset=UTF-8"})
        @ResponseBody
        public Map<String, Object> xmlOrJson() {
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("list", employeeService.list());
            return map;
        }

    3.2 测试Jaxb2RootElementHttpMessageConverter解析Xml

    使用Jaxb2RootElementHttpMessageConverter除了使用自定义RequestMappingHandlerAdapter,也可以使用<mvc:annotation-driven/>,它会为你自动注入Jaxb2RootElementHttpMessageConverter

    我注释掉我在dispatcher-servlet.xml自定义的RequestMappingHandlerAdapter

    在RequestMappingHandlerAdapter的afterPropertiesSet方法打断点,可以看到,有AllEncompassingFormHttpMessageConverter

     AllEncompassingFormHttpMessageConverter为我们加入了Jaxb2RootElementHttpMessageConverter

    在Employee实体类中加入注解

    @Entity
    @Table(name="t_employee")
    @XmlRootElement
    @XmlAccessorType(XmlAccessType.NONE)
    public class Employee {
        @XmlElement
        private Integer id;
        @XmlElement
        private String name;
        @XmlElement
        private Integer age;
        @XmlElement
        private Dept dept;
    
        @GeneratedValue
        @Id
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        @ManyToOne
        public Dept getDept() {
            return dept;
        }
    
        public void setDept(Dept dept) {
            this.dept = dept;
        }
    }

    我这里封装了一个Xml解析类,用来规范Xml输出格式

    @XmlRootElement(name = "xml")
    @XmlAccessorType(XmlAccessType.NONE)
    public class XmlActionResult<T> extends BaseXmlResult{
    
        @XmlElements({
                @XmlElement(name="employee",type = Employee.class)
        })
        private T data;
    
        public String getCode() {
            return code;
        }
    
        public void setCode(String code) {
            this.code = code;
        }
    
        public String getMessage() {
            return message;
        }
    
        public void setMessage(String message) {
            this.message = message;
        }
    
        public T getData() {
            return data;
        }
    
        public void setData(T data) {
            this.data = data;
        }
    
    
    }

    测试方法:

        @RequestMapping(value="/testCustomObj", produces={"application/xml; charset=UTF-8"},method = RequestMethod.GET)
        @ResponseBody
        public XmlActionResult<Employee> testCustomObj(@RequestParam(value = "id") int id,
                                                       @RequestParam(value = "name") String name) {
            XmlActionResult<Employee> actionResult = new XmlActionResult<Employee>();
            Employee e = new Employee();
            e.setId(id);
            e.setName(name);
            e.setAge(20);
            e.setDept(new Dept(2,"部门"));
            actionResult.setCode("200");
            actionResult.setMessage("Success with XML");
            actionResult.setData(e);
            return actionResult;
        }

    返回结果如下

    和预期一致

    3.3 测试MappingJackson2XMLHttpMessageConverter解析Xml

    demo来自于Arvind Rai,我在百度没有搜到合适的使用MappingJackson2XMLHttpMessageConverter的demo,大部分网友使用Jaxb2RootElementHttpMessageConverter,遂Google了下。

    所需的jar包

            <dependency>
                <groupId>com.fasterxml.jackson.dataformat</groupId>
                <artifactId>jackson-dataformat-xml</artifactId>
                <version>2.8.7</version>
            </dependency>

    在dispatcher-servlet.xml自定义RequestMappingHandlerAdapter的messageConverters加入MappingJackson2XMLHttpMessageConverter

    <bean class="org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter"/>

    新建一个实体类,使用@JacksonXmlRootElement,用法和@XmlRootElement类似

    @JacksonXmlRootElement(localName="company-info", namespace="com.concretepage")
    public class Company {
        @JacksonXmlProperty(localName="id", isAttribute=true)
        private Integer id;
        @JacksonXmlProperty(localName="company-name")
        private String companyName;
        @JacksonXmlProperty(localName="ceo-name")
        private String ceoName;
        @JacksonXmlProperty(localName="no-emp")
        private Integer noEmp;
        public Integer getId() {
            return id;
        }
        public void setId(Integer id) {
            this.id = id;
        }
        public String getCompanyName() {
            return companyName;
        }
        public void setCompanyName(String companyName) {
            this.companyName = companyName;
        }
        public String getCeoName() {
            return ceoName;
        }
        public void setCeoName(String ceoName) {
            this.ceoName = ceoName;
        }
        public Integer getNoEmp() {
            return noEmp;
        }
        public void setNoEmp(Integer noEmp) {
            this.noEmp = noEmp;
        }
    }

    测试方法

        @RequestMapping(value= "/fetch/{id}", produces = MediaType.APPLICATION_XML_VALUE)
        @ResponseBody
        public Company getForObjectXMLDemo(@PathVariable(value = "id") Integer id) {
            Company comp = new Company();
            comp.setId(id);
            comp.setCompanyName("XYZ");
            comp.setCeoName("ABCD");
            comp.setNoEmp(100);
            return comp;
        }

    运行结果如下

     符合预期

    4.总结

    <mvc:annotation-driven>使spring为我们配置默认的MessageConverter

    <mvc:annotation-driven>的解析类在BeanDefinitionParser,实现类为AnnotationDrivenBeanDefinitionParser,getMessageConverters方法获取MessageConverter,parse方法解析元素

     

    AbstractMessageConverterMethodArgumentResolver的构造方法初始化了HttpMessageConverter

    RequestMappingHandler加入了HttpMessageConverter

    AbstractHttpMessageConverter的canWrite方法判断是否支持MediaType

    如果解析Xml用Jaxb2RootElementHttpMessageConverter类,Jaxb2RootElementHttpMessageConverter的canWrite会判断是否有注解支持

    AbstractMessageConverterMethodProcessor类writeWithMessageConverters方法根据MediaType选取合适的HttpMessageConverter解析数据成Xml/Json数据

    RequestResponseBodyMethodProcessor的handleReturnValue处理返回值

    5.参考

    文中难免有不足之处,烦请指正

    https://docs.spring.io/spring/docs/4.3.7.RELEASE/spring-framework-reference/htmlsingle/#mvc-ann-responsebody

    https://blog.csdn.net/lqzkcx3/article/details/78159708

  • 相关阅读:
    插入排序
    排序算法结构表
    两个数字交换的四种方法
    LRU算法实现
    虚拟用户的配置
    【转】Linux查看CPU和内存使用情况
    Linux 多线程开发
    【转】RTSP流理解
    【转】DynDNS使用随笔
    【转】使用 udev 高效、动态地管理 Linux 设备文件
  • 原文地址:https://www.cnblogs.com/Java-Starter/p/10342909.html
Copyright © 2011-2022 走看看