作者微信 bishe2022

代码功能演示视频在页面下方,请先观看;如需定制开发,联系页面右侧客服
SpringMVC中使用@RequestBody,@ResponseBody注解实现Java对象和XML/JSON数据自动转换(上)

Custom Tab

     Spring3.1开始使用新的HandlerMapping 和 HandlerAdapter 来支持@Contoller 和@RequestMapping注解处理:处理器映射RequestMappingHandlerMapping和处理器适配器RequestMappingHandlerAdapter组合来代替Spring2.5 开始的处理器映射DefaultAnnotationHandlerMapping和处理器适配器AnnotationMethodHandlerAdapter。

        HandlerMapping:请求到处理器的映射,如果映射成功返回一个HandlerExecutionChain 对象(包含一个Handler处理器(页面控制器)对象、多个HandlerInterceptor 拦截器)对象;

        HandlerAdapter:HandlerAdapter 将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器。

        配合@ResponseBody注解,以及HTTP Request Header中的Accept属性,Controller返回的Java对象可以自动被转换成对应的XML或者JSON数据。

        先看一个例子,只需要简单的几步,就可以返回XML数据(此例使用的Spring版本4.1.1)。

UserDTO.java

Java代码  收藏代码

package com.bijian.study.controller.dto;  
  
public class UserDTO {  
  
    private String name;  
    private int age;  
      
    public UserDTO() {  
          
    }  
      
    public UserDTO(String name, int age) {  
        super();  
        this.name = name;  
        this.age = age;  
    }  
      
    public String getName() {  
        return name;  
    }  
    public void setName(String name) {  
        this.name = name;  
    }  
    public int getAge() {  
        return age;  
    }  
    public void setAge(int age) {  
        this.age = age;  
    }  
      
    @Override  
    public String toString() {  
        return "UserDTO [name=" + name + ", age=" + age + "]";  
    }  
}

UserDTOX.java

Java代码  收藏代码

package com.bijian.study.controller.dto;  
  
import javax.xml.bind.annotation.XmlRootElement;  
  
@XmlRootElement  
public class UserDTOX extends UserDTO {  
      
    public UserDTOX() {  
        super();  
    }  
      
    public UserDTOX(String name, int age) {  
        super(name, age);  
    }  
}

HelloController.java

Java代码  收藏代码

package com.bijian.study.controller;  
  
import javax.servlet.http.HttpServletRequest;  
  
import org.springframework.stereotype.Controller;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.ResponseBody;  
  
import com.bijian.study.controller.dto.UserDTO;  
import com.bijian.study.controller.dto.UserDTOX;  
  
@Controller  
public class HelloController {  
  
    @ResponseBody  
    @RequestMapping(value="/process")  
    public UserDTO process(HttpServletRequest request) {  
          
        return new UserDTOX("bijian", 18);  
    }  
}

   在Eclipse中启动Tomcat Server,然后在浏览器中访问:

bbbbbbbbbbb.png

 非常简单!Spring是怎么实现这个转换的呢?我们先了解下Spring的消息转换机制。

        在SpringMVC中,可以使用@RequestBody和@ResponseBody两个注解,分别完成请求报文到对象和对象到响应报文的转换,底层这种灵活的消息转换机制,就是Spring3.x中新引入的HttpMessageConverter即消息转换器机制。

        我们可以用下面的图,简单描述一下这个过程。

nnnnnnnnnnnnnnn.png

   这里最关键的就是<mvc:annotation-driven/>,加了这句配置,Spring会调用org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser来解析。

        在这个类的parse(Element, ParserContext)方法中,分别实例化了RequestMappingHandlerMapping,RequestMappingHandlerAdapter等诸多类。

        RequestMappingHandlerAdapter是请求处理的适配器,我们重点关注它的messageConverters属性。

        1).RequestMappingHandlerAdapter在调用handle()的时候,会委托给ServletInvocableHandlerMethod的invokeAndHandle()方法进行处理,这个方法又调用HandlerMethodReturnValueHandlerComposite类进行处理。

        HandlerMethodReturnValueHandlerComposite维护了一个HandlerMethodReturnValueHandler列表。

        由于我们使用了@ResponseBody注解,getReturnValueHandler就会返回RequestResponseBodyMethodProcessor的实例。

mmmmmmmmmmmmm.png


       2).之后RequestResponseBodyMethodProcessor.handleReturnValue()方法会被调用。此方法会调用AbstractMessageConverterMethodProcessor.writeWithMessageConverters()。它会根据request header中的Accept属性来选择合适的message converter。

6666666666666.png

   3).messageConverters中有如下的6个converter. 它们是从哪里来的呢?前面提到,AnnotationDrivenBeanDefinitionParser.parse(Element, ParserContext)方法中,分别实例化了RequestMappingHandlerMapping,RequestMappingHandlerAdapter以及messageConverters属性。

需要关注org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter这个类,就是它实现了返回对象到XML的转换。

111111111111111111111.png

   4).看一下getMessageConverters()中的处理。有5个message converter是一定会加进来的。

 555555555555555555555555555555555555555555.png

 然后再看,这里jaxb2Present为true, 因此Jaxb2RootElementHttpMessageConverter被添加到messageConverters中。

Java代码  收藏代码

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) {  
        messageConverters.add(createConverterDefinition(MappingJackson2XmlHttpMessageConverter.class, source));  
    }  
    else if (jaxb2Present) {  
        messageConverters.add(createConverterDefinition(Jaxb2RootElementHttpMessageConverter.class, source));  
    }  
  
    if (jackson2Present) {  
        messageConverters.add(createConverterDefinition(MappingJackson2HttpMessageConverter.class, source));  
    }  
    else if (gsonPresent) {  
        messageConverters.add(createConverterDefinition(GsonHttpMessageConverter.class, source));  
    }  
}

   5).看一下jaxb2Present的定义,原来javax.xml.bind.Binder这个类是JDK中包含的类,所以jaxb2Present=true。

1122.png

   6).我们看一下Jaxb2RootElementHttpMessageConverter的canWrite()方法。返回true的条件有两个

        a).返回对象的类具有XmlRootElement注解;

        b).请求头中的Accept属性包含application/xml。

oooooooooooo.png

   7).在chrome中打开开发者工具,可以看到请求头中确实包含了Accept=application/xml

3344.png


 接下来我们看看如果想要返回JSON数据,应该怎么做?

        根据上面的分析,首先我们需要添加一个支持JSON的message converter。前面分析getMessageConverters()代码的时候,看到

Java代码  收藏代码

if (jackson2Present) {  
    messageConverters.add(createConverterDefinition(MappingJackson2HttpMessageConverter.class, source));  
}  
else if (gsonPresent) {  
    messageConverters.add(createConverterDefinition(GsonHttpMessageConverter.class, source));  
}

     然后再来看看jackson2Present和gsonPresent的定义。

Java代码  收藏代码

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());

Java代码  收藏代码

private static final boolean gsonPresent =  
    ClassUtils.isPresent("com.google.gson.Gson", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());

  所以我们只要把Jackson2或者GSON加入工程的class path,Spring就会自动把GsonHttpMessageConverter加进来。

5566.png

 并在HelloController.java中添加getEmployeeJson()方法

Java代码  收藏代码

@ResponseBody  
@RequestMapping(value="/process2")  
public UserDTO process2(HttpServletRequest request) {  
      
    return new UserDTO("bijian", 18);  
}

     和process()相比,这里唯一的不同是返回对象变成了UserDTO,因为UserDTO类上没有@XmlRootElement注解,所以Spring不会选择Jaxb2RootElementHttpMessageConverter。又因为Accept属性中包含了*/*,表示接受任意格式返回数据,所以GsonHttpMessageConverter的canWrite()方法返回true.这样Spring就会选择MappingJackson2HttpMessageConverter或者GsonHttpMessageConverter来进行数据转换。

7788.png

至此,我们知道请求头中的Accept属性是一个很关键的东西,我们可以根据这个在Controller中写一个方法,根据Accept的值自动返回XML或者JSON数据。

Java代码  收藏代码

@ResponseBody  
@RequestMapping(value="/process")  
public UserDTO process(HttpServletRequest request) {  
      
    return new UserDTOX("bijian", 18);  
}

因为浏览器的Accept值不方便修改,我们自己写客户端来调用。

Java代码  收藏代码

package com.bijian.study.controller.test;  
  
import java.io.IOException;  
import java.net.URISyntaxException;  
  
import org.junit.Test;  
import org.springframework.http.HttpEntity;  
import org.springframework.http.HttpHeaders;  
import org.springframework.http.HttpMethod;  
import org.springframework.web.client.RestTemplate;  
  
public class XmlOrJasonControllerTest {  
  
    @Test  
    public void testJsonResponse() throws IOException, URISyntaxException {  
          
        String url = "http://localhost:8080/SpringMVC/process";  
        HttpHeaders requestHeaders = new HttpHeaders();  
        requestHeaders.set("Accept", "application/json");  
          
        RestTemplate restTemplate = new RestTemplate();  
        HttpEntity<Object> httpEntity = new HttpEntity<Object>(requestHeaders);  
        String jsonData = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class).getBody();  
          
        System.out.println(jsonData);  
    }  
  
    @Test  
    public void testXmlResponse() throws IOException, URISyntaxException {  
          
        String url = "http://localhost:8080/SpringMVC/process";  
        HttpHeaders requestHeaders = new HttpHeaders();  
        requestHeaders.set("Accept", "application/xml");  
  
        RestTemplate restTemplate = new RestTemplate();  
        HttpEntity<Object> httpEntity = new HttpEntity<Object>(requestHeaders);  
        String xmlData = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class).getBody();  
  
        System.out.println(xmlData);  
    }  
}

转载自:http://bijian1013.iteye.com/blog/2310236

Home