`

基于Spring MVC的Web应用开发(6) - Response

 
阅读更多

本文讲解Spring MVC的Response,深入了解一下@RequestMapping配合@ResponseBody的用法,同时介绍另外一个和Response有关的类ResponseEntity。

首先看看本文演示用到的类ResponseController:

package org.springframework.samples.mvc.response;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
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 ResponseController {

	@RequestMapping(value="/response/annotation", method=RequestMethod.GET)
	public @ResponseBody String responseBody() {
		return "The String ResponseBody";
	}

	@RequestMapping(value="/response/charset/accept", method=RequestMethod.GET)
	public @ResponseBody String responseAcceptHeaderCharset() {
		return "こんにちは世界! (\"Hello world!\" in Japanese)";
	}

	@RequestMapping(value="/response/charset/produce", method=RequestMethod.GET, produces="text/plain;charset=UTF-8")
	public @ResponseBody String responseProducesConditionCharset() {
		return "こんにちは世界! (\"Hello world!\" in Japanese)";
	}

	@RequestMapping(value="/response/entity/status", method=RequestMethod.GET)
	public ResponseEntity<String> responseEntityStatusCode() {
		return new ResponseEntity<String>("The String ResponseBody with custom status code (403 Forbidden - stephansun)",
				HttpStatus.FORBIDDEN);
	}

	@RequestMapping(value="/response/entity/headers", method=RequestMethod.GET)
	public ResponseEntity<String> responseEntityCustomHeaders() {
		HttpHeaders headers = new HttpHeaders();
		headers.setContentType(MediaType.TEXT_PLAIN);
		return new ResponseEntity<String>("The String ResponseBody with custom header Content-Type=text/plain",
				headers, HttpStatus.OK);
	}

}

 

访问http://localhost:8080/web/response/response/annotation,对应responseBody(),这个方法很典型,之前已经见过多次了,将字符串直接输出到浏览器。

 

访问http://localhost:8080/web/response/charset/accept,对应responseAcceptHeaderCharset(),该方法和responseBody()并没有什么不同,只是,返回的字符串中带有日文。浏览器显示"???????? ("Hello world!" in Japanese)",有乱码出现。

 

访问http://localhost:8080/web/response/charset/produce,对应responseProducesConditionCharset(),该方法跟responseAcceptHeaderCharset()相比,在@RequestMapping中增加了“produces="text/plain;charset=UTF-8"”,浏览器显示"こんにちは世界! ("Hello world!" in Japanese)",乱码没有了。

为了将这两者的区别说清楚,看看日志:

responseAcceptHeaderCharset():

DEBUG: org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor - Written [こんにちは世界! ("Hello world!" in Japanese)] as "text/html" using [org.springframework.http.converter.StringHttpMessageConverter@6b414655]

responseProducesConditionCharset():

DEBUG: org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor - Written [こんにちは世界! ("Hello world!" in Japanese)] as "text/plain;charset=UTF-8" using [org.springframework.http.converter.StringHttpMessageConverter@6b414655]

 

前者使用默认的"text/html",后者使用了特定的"text/plain;charset=UTF-8"。为什么以"text/html"形式输出日文(其实中文也是一样的)就会乱码的根本原因我还没透彻地搞清楚,但我注意到spring-web-3.1.0.REALEASE.jar中org.springframework.http.converter.StringHttpMessageConverter中有这样一段代码:

public static final Charset DEFAULT_CHARSET = Charset.forName("ISO-8859-1");

应该跟"ISO-8859-1"有关系吧。

 

后者通过指定@RequestMapping的produces属性为text/plain,字符集为UTF-8,可以避免乱码,这个是可以理解的

 

最后看看responseEntityStatusCode()和responseEntityCustomHeaders()方法,这两个方法和前面的方法的区别是没有@ResponseBody,返回的类型为ResponseEntity<String>。

responseEntityStatusCode()方法返回一个字符串,并附带了HTTP状态码为HttpStatus.FORBIDDEN(即403);

responseEntityCustomHeaders()除了返回一个字符串,HTTP状态码为HttpStatus.OK(即200),还指定了返回内容的content-type为text/plain;

这里最容易困惑我们的就是HTTP状态码了,因为从浏览器的输出看不到任何与HTTP状态码相关的东西。

 

访问http://localhost:8080/web/response/entity/statushttp://localhost:8080/web/response/entity/headers

均能正常的将字符串内容显示在网页上,那么HttpStatus.FORBIDDEN起什么作用呢?

ResponseEntity继承于HttpEntity类,HttpEntity类的源代码的注释说"HttpEntity主要是和RestTemplate组合起来使用,同样也可以在SpringMVC中作为@Controller方法的返回值使用"。ResponseEntity类的源代码注释说"ResponseEntity是HttpEntity的扩展,增加了一个HttpStutus的状态码,通常和RestEntity配合使用,当然也可以在SpringMVC中作为@Controller方法的返回值使用"。那么使用RestTemplate写个程序模拟一下吧(RestTemplate的具体用法见本文附录):

package org.springframework.samples.mvc.response;

import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

public class ResponseControllerTest {

	public static void main(String[] args) {
		RestTemplate template = new RestTemplate();
		ResponseEntity<String> entity = template.getForEntity(
				"http://localhost:8080/web/response/entity/status", String.class);
		String body = entity.getBody();
		MediaType contentType = entity.getHeaders().getContentType();
		HttpStatus statusCode = entity.getStatusCode();
		System.out.println("statusCode:[" + statusCode + "]");
	}
}

访问http://localhost:8080/web/response/entity/status,观察日志:

DEBUG: org.springframework.web.client.RestTemplate - Created GET request for "http://localhost:8080/web/response/entity/status"
DEBUG: org.springframework.web.client.RestTemplate - Setting request Accept header to [text/plain, */*]
WARN : org.springframework.web.client.RestTemplate - GET request for "http://localhost:8080/web/response/entity/status" resulted in 403 (Forbidden); invoking error handler
Exception in thread "main" org.springframework.web.client.HttpClientErrorException: 403 Forbidden
	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.getForEntity(RestTemplate.java:221)
	at org.springframework.samples.mvc.response.ResponseControllerTest.main(ResponseControllerTest.java:12) 

将URL换成http://localhost:8080/web/response/entity/headers,观察日志:

DEBUG: org.springframework.web.client.RestTemplate - Created GET request for "http://localhost:8080/web/response/entity/headers"
DEBUG: org.springframework.web.client.RestTemplate - Setting request Accept header to [text/plain, */*]
DEBUG: org.springframework.web.client.RestTemplate - GET request for "http://localhost:8080/web/response/entity/headers" resulted in 200 (OK)
DEBUG: org.springframework.web.client.RestTemplate - Reading [java.lang.String] as "text/plain" using [org.springframework.http.converter.StringHttpMessageConverter@2e297ffb]
statusCode:[200]

发现使用RestTemplate时,如果它发现返回的信息中HTTP状态码为403时,就抛出异常了,正好符合了我们的期望,至于为什么浏览器上没有体现,暂时还不不太明白(待补完)。

 

===================================================================

 

SpringSource的Team Blog上有一篇文章是关于@RequestMapping的produces属性的讨论。

 

===================================================================

 

附录Spring Reference Documentation中的相关内容:

 

[了解一下@RequesetMapping支持的返回值类型]

16.3.3.2 @RequestMapping注解方法支持的返回值类型

以下返回值的类型(return types)均支持:

  • ModelAndView对象,with the model implicitly enriched with command objects and the results of @ModelAttributes annotated reference data accessor methods.(恕我实在翻译不出 TAT)。
  • Model对象,with the view name implicitly determined through a RequestToViewNameTranslator and the model implicitly enriched with command objects and the results of @ModelAttribute annotated reference data accessor methods.
  • Map对象,for exposing a model, with the view name implicitly determined through a RequestToViewNameTranslator and the model implicitly enriched with command objects and the results of @ModelAttribute annotated reference data accessor methods.
  • View对象,with the model implicitly determined through command objects and @ModelAttribute annotated reference data accessor methods. The handler method may also programmatically enrich the model by declaring a Model argument (see above).
  • String,value that is interpreted as the logical view name, with the model implicitly determined through command objects and @ModelAttribute annotated reference data accessor methods. The handler method may also programmatically enrich the model by declaring a Model argument (see above).
  • void,if the method handles the response itself (by writing the response content directly, declaring an argument of type ServletResponse / HttpServletResponse for that purpose) or if the view name is supposed to be implicitly determined through a RequestToViewNameTranslator (not declaring a response argument in the handler method signature).
  • @ResponseBody,If the method is annotated with @ResponseBody, the return type is written to the response HTTP body. The return value will be converted to the declared method argument type using HttpMessageConverters. See Section 16.3.3.5, “Mapping the response body with the @ResponseBody annotation”.
  • HttpEntity或者ResponseEntityHttpEntity<?>或者ResponseEntity<?>对象可以取到Servlet的response的HTTP头信息(headers)和内容(contents)。这个实体(entity body)可以通过使用HttpMessageConverter类被转成Response流。See Section 16.3.3.6, “Using HttpEntity<?>”.
  • Any other return type is considered to be a single model attribute to be exposed to the view, using the attribute name specified through @ModelAttribute at the method level (or the default attribute name based on the return type class name). The model is implicitly enriched with command objects and the results of @ModelAttribute annotated reference data accessor methods.

 

[了解一下RestTemplate,下一篇文章有用]文档中有关RestTemplate的内容:

 

20.9 在客户端访问RESTful服务

RestTemplate是客户端访问RESTful服务的核心类。它在概念上同Spring中的其它模板类相似,如JdbcTemplate和JmsTemplate还有一些其它Spring portfolio工程中的模板类。RestTemplate提供了一个回调方法,使用HttpMessageConverter将对象marshal到HTTP请求体里,并且将response unmarshal成一个对象。使用XML作为消息格式是一个很普遍的做法,Spring提供了MarshallingHttpMessageConverter类,该类使用了Object-to-XML框架,该框架是org.springframe.oxm包的一部分。你有多种XML到Object映射技术可选。

本节介绍了如何使用RestTemplate以及和它相关的HttpMessageConverters。

20.9.1 RestTemplate

在Java中调用RESTful服务的一个经典的做法就是使用一个帮助类,如Jakarta Commons的HttpClient,对于通用的REST操作,HttpClient的实现代码比较底层,如下:

String uri = "http://example.com/hotels/1/bookings";

PostMethod post = new PostMethod(uri);
String request = // create booking request content
post.setRequestEntity(new StringRequestEntity(request));

httpClient.executeMethod(post);

if (HttpStatus.SC_CREATED == post.getStatusCode()) {
  Header location = post.getRequestHeader("Location");
  if (location != null) {
    System.out.println("Created new booking at :" + location.getValue());
  }
}

 RestTemplate对HTTP的主要六种提交方式提供了更高层的抽象,使得调用RESTful服务时代码量更少,并且使REST表现的更好。

表格20.1 RestTemplate方法预览

 

HTTP方法 RestTemplate方法

DELETE delete

GET getForObject getForEntity

HEAD headForHeaders(String url, String... urlVariables)

OPTIONS optionsForAllow(String url, String... urlVariables)

POST postForLocation(String url, Object request, String... urlVariables) postForObject(String url, Object request, Class<T> responsetype, String... uriVariables)

PUT put(String url, Object request, String... urlVariables)

 

RestTemplate的方法名称遵循着一个命名规则,第一部分说明调用的是什么HTTP方法,第二部分说明的是返回了什么。比如,getForObject()方法对应GET请求,将HTTP response的内容转成你需要的一种对象类型,并返回这个对象。postForLocation()方法对应POST请求,converting the given object into a HTTP request and return the response HTTP Location header where the newly created object can be found.如果在执行一个HTTP请求时出现异常,会抛出RestClientException异常;可以在RestTemplate自定义ResponseErrorHandler的实现来自定义这种异常。

这些方法通过HttpMessageConverter实例将传递的对象转成HTTP消息,并将得到的HTTP消息转成对象返回。给主要mime类型服务的转换器(converter)默认就注册了,但是你也可以编写自己的转换器并通过messageConverters()bean属性注册。该模板默认注册的转换器实例是ByteArrayHttpMessageConverter,StringHttpMessageConverter,FormHttpMessageConverter以及SourceHttpMessageConverter。你可以使用messageConverters()bean属性重写这些默认实现,比如在使用MarshallingHttpMessageConverter或者MappingJacksonHttpMessageConverter时你就需要这么做。

每个方法有有两种类型的参数形式,一种是可变长度的String变量,另一种是Map<String, String>,比如:

String result = restTemplate.getForObject("http://example.com/hotels/{hotel}/bookings/{booking}",
                                          String.class,"42", "21");

使用可变长度参数,

Map<String, String> vars = Collections.singletonMap("hotel", "42");
String result =
  restTemplate.getForObject("http://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars);

使用一个Map<String, String> 。

你可以直接使用RestTemplate的默认构造器创建一个RestTemplate的实例。它会使用jdk中java.net包的标准Java类创建HTTP请求。你也可以自己指定一个ClientHttpRequestFactory的实现。Spring提供了CommonsClientHttpRequestFactory的实现,该类使用了Jakarta Commons的HttpClient创建HTTP请求。CommonsClientHttpRequestFactory配置了org.apache.commons.httpclient.HttpClient的一个实例,该实例反过来被credentials information或者连接池配置。

前面那个使用了Jakarta Commons的HttpClient类的例子可以直接使用RestTemplate重写,如下:

uri = "http://example.com/hotels/{id}/bookings";

RestTemplate template = new RestTemplate();

Booking booking = // create booking object

URI location = template.postForLocation(uri, booking, "1");

通用的回调接口是RequestCallback,该接口在execute方法被调用时被调用。

public <T> T execute(String url, HttpMethod method, RequestCallback requestCallback,
                     ResponseExtractor<T> responseExtractor,
                     String... urlVariables)


// also has an overload with urlVariables as a Map<String, String>.

RequestCallback接口定义如下:

public interface RequestCallback {
 void doWithRequest(ClientHttpRequest request) throws IOException;
}

该接口允许你管控(manipulate)request的header并且象request体中写数据。当使用execute方法时,你不必担心任何资源管理问题的,模板总是会关闭request,并且处理任何error,你可以参考API文档来获得更多有关使用execute方法及该模板其它方法参数含义的信息。

20.9.1.1 同URI一起工作

对HTTP的主要方法,RestTemplate的第一个参数是可变的,你可以使用一个String类型的URI,也可以使用java.net.URI类型的类,

String类型的URI接受可变长度的String参数或者Map<String, String>,这个URL字符串也是假定没有被编码(encoded)并且需要被编码的(encoded)的。举例如下:

restTemplate.getForObject("http://example.com/hotel list", String.class);

这行代码使用GET方式请求http://example.com/hotel%20list。这意味着如果输入的URL字符串已经被编码了(encoded),它将被编码(encoded)两次 -- 比如:http://example.com/hotel%20list会变成http://example.com/hotel%2520list。如果这不是你所希望的,那就使用java.net.URI,它假定URL已经被编码(encoded)过了,如果你想要将一个单例的URI(fully expanded)复用多次的话,这个方法也是有用的。

UriComponentsBuilder类(我们已经见过了,不是吗? 译者)可以构建和编码一个包含了URI模板支持的URI,比如你可以从一个URL字符串开始:

UriComponents uriComponents = 
        UriComponentsBuilder.fromUriString("http://example.com/hotels/{hotel}/bookings/{booking}").build()
            .expand("42", "21")
            .encode();
            
URI uri = uriComponents.toUri();

 或者单独地指定每个URI组件:

UriComponents uriComponents = 
        UriComponentsBuilder.newInstance()
            .scheme("http").host("example.com").path("/hotels/{hotel}/bookings/{booking}").build()
            .expand("42", "21")
            .encode();

URI uri = uriComponents.toUri();

 

 20.9.1.2 处理request和response的头信息(headers)

除了前面已经说到的方法外,RestTemplate还有一个exchange()方法,该方法可以用在基于HttpEntity类的任意HTTP方法的执行上。

也许最重要的一点是,exchange()方法可以用来添加request头信息(headers)以及读response的头信息(headers)。举例:

HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("MyRequestHeader", "MyValue");
HttpEntity<?> requestEntity = new HttpEntity(requestHeaders);

HttpEntity<String> response = template.exchange("http://example.com/hotels/{hotel}",
  HttpMethod.GET, requestEntity, String.class, "42");

String responseHeader = response.getHeaders().getFirst("MyResponseHeader");
String body = response.getBody();

在上面这个例子中,我们首先准备了一个request实体(entity),该实体包含了MyRequestHeader头信息(header)。然后我们取回(retrieve)response,读到这个MyResponseHeader和返回体(body)。

20.9.2 HTTP Message Conversion (本小节跟本文关系不大,略去 译者)

 

===================================================================

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

分享到:
评论

相关推荐

    Spring MVC 入门实例

    基于 Spring 的 Web 应用程序接收到 http://localhost:8080/hello.do(事实上请求路径是 /hello.do) 的请求后, Spring 将这个请求交给一个名为 helloController 的程序进行处理, helloController 再调用 一个名为 ...

    springmvc demo

    即使用了MVC架构模式的思想,将web层进行职责解耦,基于请求驱动指的就是使用请求-响应模型,框架的目的就是帮助我们简化开发,Spring Web MVC也是要简化我们日常Web开发的。 Spring Web MVC也是服务到工作者模式的...

    spring-boot-ssm:springboot-ssm 是一个基于Spring Boot & Spring & Spring MVC & MyBatis的简单通用的项目,用于快速构建中小型API的后端服务系统

    spring-boot-ssm 是一个基于Spring Boot & Spring & Spring MVC & MyBatis的简单通用的项目,用于快速构建中小型API的后端服务系统. 可以做为一个种子项目,进行改造升级. 另外,还有个对应的Vue+ElementUI的前端项目,...

    spring_MVC源码

    14. &lt;bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" /&gt; 15. 16. &lt;!-- 对模型视图名称的解析,即在模型视图名称添加前后缀 --&gt; 17. &lt;bean class="org....

    Spring3.0.5扩展支持AOP获取HttpServletResponse

    Spring3.0.5支持AOP获取HttpServletResponse扩展资源:spring.web-3.0.5.jar和spring.webmvc-3.0.5.jar 是需要升级替换的。

    入门案例-SpringMVC技术架构图

    Spring MVC是Spring提供的构建Web应用程序的框架,该框架遵循了Servlet规范,负责接收并处理Servelt容器传递的请求,并将响应写回Response。Spring MVC以DispatcherServlet为核心,众多组件如HandlerMapping为辅助,...

    spring-boot-reference.pdf

    27.1. The “Spring Web MVC Framework” 27.1.1. Spring MVC Auto-configuration 27.1.2. HttpMessageConverters 27.1.3. Custom JSON Serializers and Deserializers 27.1.4. MessageCodesResolver 27.1.5. Static...

    轻量级java web MVC框架

    一个非常简单的MVC框架,实现了类似Spring MVC的基本功能。 1、包括自动扫描绑定映射路径,只要在web.xml中指定扫描包,系统启动后会将请求url绑定到指定的处理方法上。如: 在web.xml中定义如下: &lt;context-param&gt; ...

    Spring_Framework_ API_5.0.5 (CHM格式)

    Spring Web Reactive 在 spring-webmvc 模块中现有的(而且很流行)Spring Web MVC旁边的新的 spring-web-reactive 模块中。 请注意,在 Spring5 中,传统的 SpringMVC 支持 Servlet3.1 上运行,或者支持 JavaEE7 的...

    J2EE应用开发详解

    第1章 Java Web应用开发简介 1 1.1 Java EE应用概述 1 1.2 Java EE概念 1 1.2.1 Java EE多层模型 1 1.2.2 Java EE体系结构 2 1.3 Java EE的核心API与组件 4 1.4 Web服务器和应用服务器 13 1.5 小结 16 第2章 建立...

    building restful web services with spring 5 2e

    Building RESTful Web Services with Spring 5 – Second Edition: Leverage the power of Spring 5.0, Java SE 9, and Spring Boot 2.0 Find out how to implement the REST architecture to build resilient ...

    一个基于SpringBoot的微服务脚手架+源代码+文档说明

    - 集成spring-boot-devtools,提高本机WEB调试时的应用重加载速度 ## 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、...

    SPRING入门

    Spring MVC属于springFrameWork(spring)的产品,它是基于java的轻量级web框架,使用MVC架构模式,将web层进行解耦功能,前端控制器是DispatcherServlet;应用控制器其实拆为处理器映射器(Handler Mapping)进行处理器...

    SpringMvc源码

    即使用了MVC架构模式的思想,将web层进行职责解耦,基于请求驱动指的就是使用请求-响应模型,框架的目的就是帮助我们简化开发,Spring Web MVC也是要简化我们日常Web开发的。 另外还有一种基于组件的、事件驱动...

    MyStats-API-Web-Application:集成了QuickChart API并允许客户端创建自定义图表的Web应用程序。 按照MVC模式和RESTful架构实现CRUD请求。 使用Spring安全性维护一个安全的Web服务(使用LoginLogout功能)。 控制器允许Response返回邮递员请求的JSON和HTTP到Web浏览器

    集成了QuickChart API并允许客户端创建自定义图表的Web应用程序。 根据MVC模式和RESTful架构实现CRUD请求。使用Spring安全性来维护安全的Web服务(使用登录/注销功能)。 控制器允许Response向邮递员请求返回JSON,...

    Java Web编程宝典-十年典藏版.pdf.part2(共2个)

    主要包括Java Web开发环境、JSP语法、JSP内置对象、Java Bean技术、Servlet技术、EL与JSTL标签库、数据库应用开发、初识Struts2基础、揭密Struts2高级技术、Hib锄劬e技术入门、Hibernate高级应用、Spring核心之IoC、...

    spring-framework-reference-4.1.2

    3.7. General Web Improvements ............................................................................... 19 3.8. WebSocket, SockJS, and STOMP Messaging ..............................................

    Manning.Spring.in.Action.4th.Edition.2014.11.epub

    7.1. Alternate Spring MVC configuration 7.1.1. Customizing DispatcherServlet configuration 7.1.2. Adding additional servlets and filters 7.1.3. Declaring DispatcherServlet in web.xml 7.2. Processing ...

    跟我学SpringMVC

    即使用了MVC架 构模式的思想,将web层进行职责解耦,基于请求驱动指的就是使用请求-响应模型,框架的目的就是帮助我们简化开 发,Spring Web MVC也是要简化我们日常Web开发的。 另外还有一种基于组件的、事件驱动的...

    java微信公众号MVC开发框架

    jwx是开源的java公众号开发MVC框架,基于spring配置文件和微信消息或事件注解,通过微信上下文处理一个或多个微信公众号服务请求。目的主要有两个,其一生封装微信请求xml消息为java实体对象,将返回对象转换为xml...

Global site tag (gtag.js) - Google Analytics