本文接上一篇文章,介绍@RequestMapping中的headers属性,并进一步研究produces属性以及和它配对的consumes属性。
首先看看讲解用到的类:
package org.springframework.samples.mvc.simple;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class SimpleControllerRevisited {
@RequestMapping(value="/simple/revisited", method=RequestMethod.GET, headers="Accept=text/plain")
public @ResponseBody String simple() {
return "Hello world revisited!";
}
}
header是HTTP协议中的一个概念,一般翻译成头域,为了不陷入讲解理论的泥潭,我们使用Firefox的firebug看一下header到底是什么,还记得HelloWorld那一篇文章的例子么?访问http://localhost:8080/web/simple,浏览器显示Hello World!,与本例唯一的区别就是@RequestMapping没有加headers, 打开firebug,在"网络"这一栏看到有一行记录URL "GET simple",Status "200 OK",Domain "localhost:8080"等信息,展开,有三列数据,分别为Headers,Response,HTML,看Headers这列,
Request Headers:
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Encoding: gzip, deflate Accept-Language: en-us,en;q=0.5 Connection: keep-alive Host: localhost:8080 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:10.0.2) Gecko/20100101 Firefox/10.0.2
Response Headers:
Content-Length: 12 Content-Type: text/html Server: Jetty(7.2.0.v20101020)
Request Headers中有个Accept,它是由浏览器发出的,表示浏览器认为它可以支持的格式,并不是真的只能支持这些格式,而Response Headers中有个Content-Type,这个值是服务端(如Servlet)封装到Respose的Headers中的。
访问例子程序的URL:http://localhost:8080/web/simple/revisited,
Request Headers:
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Encoding: gzip, deflate Accept-Language: en-us,en;q=0.5 Connection: keep-alive Host: localhost:8080 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:10.0.2) Gecko/20100101 Firefox/10.0.2
Response Headers:
Content-Length: 22 Content-Type: text/plain Server: Jetty(7.2.0.v20101020)
Response Headers中的Content-Type变成了text/plain,因为RequestMapping的headers设置了Accept=text/plain。
注意这个Accept和Request Headers中的Accept没有半点关系,它跟Response Headers的Content-Type有关。
我们已经数次提及Content-Type了,其实@RequestMapping的headers属性除了Accept外也有Content-Type:
@RequestMapping(value="/simple/revisited2", method=RequestMethod.GET, headers="Content-Type=text/plain")
public @ResponseBody String simple2() {
return "Hello world 2 revisited!";
}
访问http://localhost:8080/web/simple/revisited2,发现浏览器上返回HTTP状态码为415的出错页面:
HTTP ERROR 415
Problem accessing /web/simple/revisited2. Reason:
Unsupported Media Type
Powered by Jetty://
这是因为headers="Content-Type=text/plain"的含义是
它必须要求Request Headers里的Content-Type为"text/plain"才能执行该方法,
看firebug的Request Headers:
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Encoding: gzip, deflate Accept-Language: en-us,en;q=0.5 Connection: keep-alive Host: localhost:8080 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:10.0.2) Gecko/20100101 Firefox/10.0.2
没有Content-Type属性,不光这个,前面的那个Request Headers也没有Content-Type,其实GET方式访问的URL的Request Header里压根就没有Content-Type(我原来也不知道,直到我搜索到了这篇文章)! 所以我们只能使用POST方式测试这个特性,很遗憾,我暂时没有找到如何通过浏览器地址栏的方式来POST提交,但是,想到了上一篇文章附录中的RestTemplate,可以写个Java小程序模拟一下,在SimpleControllerRevisited中增加一个方法:
@RequestMapping(value="/simple/revisited3", method=RequestMethod.POST, headers="Content-Type=text/plain")
public @ResponseBody String simple3() {
return "Hello world 3 revisited!";
}
写个RestTemplate的小程序:
package org.springframework.samples.mvc.simple;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
public class SimpleControllerRevisitedTest {
public static void main(String[] args) {
testPost();
}
public static void testPost() {
RestTemplate template = new RestTemplate();
ResponseEntity<String> entity = template.postForEntity(
"http://localhost:8080/web/simple/revisited3", null, String.class);
String body = entity.getBody();
MediaType contentType = entity.getHeaders().getContentType();
System.out.println("contentType:[" + contentType + "]");
HttpStatus statusCode = entity.getStatusCode();
System.out.println("statusCode:[" + statusCode + "]");
}
}
执行,报错:
DEBUG: org.springframework.web.client.RestTemplate - Created POST request for "http://localhost:8080/web/simple/revisited3" DEBUG: org.springframework.web.client.RestTemplate - Setting request Accept header to [text/plain, */*] WARN : org.springframework.web.client.RestTemplate - POST request for "http://localhost:8080/web/simple/revisited3" resulted in 415 (Unsupported Media Type); invoking error handler Exception in thread "main" org.springframework.web.client.HttpClientErrorException: 415 Unsupported Media Type at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:76) at org.springframework.web.client.RestTemplate.handleResponseError(RestTemplate.java:486) at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:443) at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:401) at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:302) at org.springframework.samples.mvc.simple.SimpleControllerRevisitedTest.testPost(SimpleControllerRevisitedTest.java:16) at org.springframework.samples.mvc.simple.SimpleControllerRevisitedTest.main(SimpleControllerRevisitedTest.java:11)
还是报415 Unsupported Media Type,
这说明Content-Type并不是text-plain,那么到底是什么呢?新写一个方法,去掉headers属性,仅保留POST:
@RequestMapping(value="/simple/revisited4", method=RequestMethod.POST)
public @ResponseBody String simple4() {
return "Hello world 4 revisited!";
}
将SimpleControllerRevisitedTest中的URL改为http://localhost:8080/web/simple/revisited4, 查看服务端打印的日志(注意访问前一个URL时,服务端是不打印此日志的,因为Request Header中的Content-Type就不满足要求,直接拒掉了):
DEBUG: org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor - Reading [[B] as "application/x-www-form-urlencoded" using [org.springframework.http.converter.ByteArrayHttpMessageConverter@1d05c9a1]
发现使用POST方式,Request Headers中的Content-Type为"application/x-www-form-urlencoded",
那么再增加一个方法:
@RequestMapping(value="/simple/revisited5", method=RequestMethod.POST, headers="Content-Type=application/x-www-form-urlencoded")
public @ResponseBody String simple5() {
return "Hello world 5 revisited!";
}
将SimpleControllerRevisitedTest中的URL改为http://localhost:8080/web/simple/revisited5,
DEBUG: org.springframework.web.client.RestTemplate - Created POST request for "http://localhost:8080/web/simple/revisited5" DEBUG: org.springframework.web.client.RestTemplate - Setting request Accept header to [text/plain, */*] DEBUG: org.springframework.web.client.RestTemplate - POST request for "http://localhost:8080/web/simple/revisited5" resulted in 200 (OK) DEBUG: org.springframework.web.client.RestTemplate - Reading [java.lang.String] as "text/plain;charset=ISO-8859-1" using [org.springframework.http.converter.StringHttpMessageConverter@5f326484] contentType:[text/plain;charset=ISO-8859-1] statusCode:[200]
从程序运行结果看,一切OK(注意不要被这个日志中的contentType弄混了,这个contentType是Response的Headers里的)。
总结一下,@RequestMapping中的headers有两种写法,
一种是:headers="Accept=text/plain",
另一种是:headers="Content-Type=application/x-www-form-urlencoded",
Accept指定的格式为Response Headers的Content-Type的格式,
Content-Type指定的格式为Reqeust Headers必须要满足的格式,
否则将被SpringMVC直接拒绝掉,并返回415的HTTP状态码。
大家可能会想,@RequestMapping的headers属性搞这么复杂干嘛?Spring团队应该是听到了社区群众的呼声,或者他们自己都觉得晕?因此在Spring 3.1 M2发布的时候,他们做了改进,SpringSource的Team Blog上有一篇文章 SPRING 3.1 M2: SPRING MVC ENHANCEMENTS,说道了Spring 3.1 M2版本针对以前版本的编程模型改进(programming model improvement),将第四点和第五点简而言之就是:
@RequestMapping中
headers="Content-Type=application/json"被consumes="application/json"替换了,
headers="Accept=application/json"被produces="application/json"替换了。
现在应该对上一篇文章中,我写道
理解了,任何一个框架都是不断的在发展和完善的,如果能在学习的过程中,知道一点框架的变迁史,将会对我们的理解产生莫大的好处。