在Controller中的方法若用此方法注解:
@ResponseBody
@RequestMapping ( "/pay/tenpay" )
public String tenpayReturnUrl(HttpServletRequest request, HttpServletResponse response) throws Exception {
unpackCookie(request, response);
payReturnUrl.payReturnUrl(request, response);
return "pay/success" ;
}
用此办法注解将会在访问的页面上输出 字符串:strHtml,而不会跳转页面至pay/success.jsp页面
原因是:
@ResponseBody
作用:
该注解用于将Controller的方法返回的对象,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区。
使用时机:
返回的数据不是html标签的页面,而是其他某种格式的数据时(如json、xml等)使用;
那么只需删除注解: @ResponseBody 便可以返回页面 pay/success.jsp。而且达到了与客户端后台交互的效果。
配合@ResponseBody注解,以及HTTP Request Header中的Accept属性,Controller返回的Java对象可以自动被转换成对应的XML或者JSON数据。 先看一个例子,只需要简单的几步,就可以返回XML数据。(本文使用Spring版本 4.1.6,并使用maven做项目构建)
1)在配置文件中添加
< context:component-scan base-package = "learning.webapp.controller" />
< mvc:annotation-driven />
2)添加以下几个java类
package learning.webapp.model;
public class Employee {
private String name;
private int salary;
public Employee() {
}
public Employee(String name, int salary) {
this .name = name;
this .salary = salary;
}
public String getName() {
return name;
}
public int getSalary() {
return salary;
}
public void setName(String name) {
this .name = name;
}
public void setSalary( int salary) {
this .salary = salary;
}
}
package learning.webapp.model;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class EmployeeX extends Employee {
public EmployeeX() {
super ();
}
public EmployeeX(String name, int salary) {
super (name, salary);
}
}
package learning.webapp.controller;
import learning.webapp.model.Employee;
import learning.webapp.model.EmployeeX;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping ( "/employees" )
public class XmlOrJsonController {
@RequestMapping (value= "/xml/{name}" , method=RequestMethod.GET)
@ResponseBody
public Employee getEmployeeXml( @PathVariable String name) {
return new EmployeeX(name, 16000 );
}
}
3) 在Eclipse中使用Jetty插件启动Web Server,然后在浏览器中访问:
非常简单!Spring是怎么实现这个转换的呢?我们先了解下Spring的消息转换机制。 在SpringMVC中,可以使用@RequestBody和@ResponseBody两个注解,分别完成请求报文到对象和对象到响应报文的转换,底层这种灵活的消息转换机制,就是Spring3.x中新引入的HttpMessageConverter即消息转换器机制。 我们可以用下面的图,简单描述一下这个过程。
这里最关键的就是 ,加了这句配置,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的实例。
2)之后RequestResponseBodyMethodProcessor.handleReturnValue()方法会被调用。此方法会调用AbstractMessageConverterMethodProcessor.writeWithMessageConverters()。它会根据request header中的Accept属性来选择合适的message converter.
3) messageConverters中有如下的6个converter. 它们是从哪里来的呢?前面提到,AnnotationDrivenBeanDefinitionParser.parse(Element, ParserContext) 方法中,分别实例化了RequestMappingHandlerMapping,RequestMappingHandlerAdapter以及messageConverters 属性。 需要关注org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter这个类,就是它实现了返回对象到XML的转换。
4)看一下getMessageConverters()中的处理。有5个message converter是一定会加进来的。
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));
然后再看,这里jaxb2Present为true, 因此Jaxb2RootElementHttpMessageConverter被添加到messageConverters中。
5)看一下jaxb2Present的定义,原来javax.xml.bind.Binder这个类是JDK中包含的类,所以jaxb2Present=true。
6)我们看一下Jaxb2RootElementHttpMessageConverter的canWrite()方法。返回true的条件有两个
a) 返回对象的类具有XmlRootElement注解; b) 请求头中的Accept属性包含application/xml。
@Override
public boolean canWrite(Class clazz, MediaType mediaType) {
return (AnnotationUtils.findAnnotation(clazz, XmlRootElement. class ) != null && canWrite(mediaType));
}
7) 在chrome中打开开发者工具,可以看到请求头中确实包含了Accept=application/xml
接下来我们看看如果想要返回JSON数据,应该怎么做?
根据上面的分析,首先我们需要添加一个支持JSON的message converter. 前面分析getMessageConverters()代码的时候,看到
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));
}
然后再来看看jackson2Present和gsonPresent的定义。
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 gsonPresent =
ClassUtils.isPresent("com.google.gson.Gson" , AnnotationDrivenBeanDefinitionParser. class .getClassLoader());
所以我们只要把Jackson2或者GSON加入工程的class path,Spring就会自动把GsonHttpMessageConverter加进来。
1)我们在POM中添加以下依赖
< dependency >
< groupId > com.fasterxml.jackson.core groupId >
< artifactId > jackson-databind artifactId >
< version > 2.6.1 version >
dependency >
或者
< dependency >
< groupId > com.google.code.gson groupId >
< artifactId > gson artifactId >
< version > 2.3.1 version >
dependency >
2)在XmlOrJsonController.java中添加getEmployeeJson()方法
@RequestMapping (value= "/json/{name}" , method=RequestMethod.GET)
@ResponseBody
public Employee getEmployeeJson( @PathVariable String name) {
return new Employee(name, 16000 );
}
和getEmployeeXml()相比,这里唯一的不同是返回对象变成了Employee,因为Employee类上没有@XmlRootElement注解,所以Spring不会选择Jaxb2RootElementHttpMessageConverter。又因为Accept属性中包含了*/*,表示接受任意格式返回数据,所以GsonHttpMessageConverter的canWrite()方法返回true.这样Spring就会选择MappingJackson2HttpMessageConverter或者GsonHttpMessageConverter来进行数据转换。
至此,我们知道请求头中的Accept属性是一个很关键的东西,我们可以根据这个在Controller中写一个方法,根据Accept的值自动返回XML或者JSON数据。
@RequestMapping (value= "/{name}" , method=RequestMethod.GET)
@ResponseBody
public Employee getEmployee( @PathVariable String name) {
return new EmployeeX(name, 16000 );
}
因为浏览器的Accept值不方便修改,我们自己写客户端来调用。
package learning.webapp;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
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.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
public class XmlOrJasonControllerTest {
@Test
public void testJsonResponse() throws IOException, URISyntaxException {
String url = "http://localhost:8080/employees/Jack" ;
ClientHttpRequest request = new SimpleClientHttpRequestFactory().createRequest( new URI(url), HttpMethod.GET);
request.getHeaders().set("Accept" , "application/json" );
ClientHttpResponse response = request.execute();
InputStream is = response.getBody();
byte bytes[] = new byte [( int ) response.getHeaders().getContentLength()];
is.read(bytes);
String jsonData = new String(bytes);
System.out.println(jsonData);
}
@Test
public void testXmlResponse() throws IOException, URISyntaxException {
String url = "http://localhost:8080/employees/Jack" ;
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("Accept" , "application/xml" );
RestTemplate restTemplate = new RestTemplate();
HttpEntity httpEntity = new HttpEntity(requestHeaders);
String xmlData = restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class ).getBody();
System.out.println(xmlData);
}
}