中亿财经网能帮助我们老百姓能养老虎吗吗?

Spring MVC 中的 REST 支持及使用 - 畅行天下 - ITeye技术网站
博客分类:
本部分提供了支持 RESTful web 服务的主要 Spring 功能(或注释)的概述。 @Controller
使用 @Controller 注释对将成为 MVC 中控制器的类进行注释并处理 HTTP 请求。 @RequestMapping
使用 @RequestMapping 注释对函数进行注释,该函数处理某些 HTTP 方法、URI 或 HTTP 头。此注释是 Spring REST 支持的关键。可以更改 method 参数以处理其他 HTTP 方法。
@RequestMapping(method=RequestMethod.GET, value="/emps",
headers="Accept=application/xml, application/json")
@PathVariable
使用 @PathVariable 注释可将 URI 中的路径变量作为参数插入。
@RequestMapping(method=RequestMethod.GET, value="/emp/{id}")
public ModelAndView getEmployee(@PathVariable String id) { … }
其他有用的注释
使用 @RequestParam 将 URL 参数插入方法中。
使用 @RequestHeader 将某一 HTTP 头插入方法中。
使用 @RequestBody 将 HTTP 请求正文插入方法中。
使用 @ResponseBody 将内容或对象作为 HTTP 响应正文返回。
使用 HttpEntity&T& 将它自动插入方法中,如果将它作为参数提供。
使用 ResponseEntity&T& 返回具有自定义状态或头的 HTTP 响应。
public @ResponseBody Employee getEmployeeBy(@RequestParam("name")
String name, @RequestHeader("Accept") String accept, @RequestBody String body) {…}
public ResponseEntity&String& method(HttpEntity&String& entity) {…}
参见 Spring 文档(参见 参考资料) 获得可插入方法中的支持注释或对象的完整列表。 多具象支持 使用不同 MIME 类型表示同一资源是 RESTful web 服务的一个重要方面。通常,可以使用具有不同 "accept" HTTP 头的同一 URI 提取具有不同表示的资源。还可以使用不同的 URI 或具有不同请求参数的 URI。 “使用 Spring 3 构建 RESTful web 服务”(参见 参考资料)介绍了 ContentNegotiatingViewResolver,可以挑选不同的视图解析器处理同一 URI(具有不同的 accept 头)。因此,ContentNegotiatingViewResolver 可用于生成多个具象。 还有另一种方式可生成多具象 — 将 HttpMessageConverter 和 c@ResponseBody 注释结合起来使用。使用这种方法无需使用视图技术。 回页首 HttpMessageConverter HTTP 请求和响应是基于文本的,意味着浏览器和服务器通过交换原始文本进行通信。但是,使用 Spring,controller 类中的方法返回纯 'String' 类型和域模型(或其他 Java 内建对象)。如何将对象序列化/反序列化为原始文本?这由 HttpMessageConverter 处理。Spring 具有捆绑实现,可满足常见需求。表 1 显示了一些示例。 表 1. HttpMessageConverter 示例 使用...... 您可以...... StringHttpMessageConverter 从请求和响应读取/编写字符串。默认情况下,它支持媒体类型 text/* 并使用文本/无格式内容类型编写。 FormHttpMessageConverter 从请求和响应读取/编写表单数据。默认情况下,它读取媒体类型 application/x-www-form-urlencoded 并将数据写入 MultiValueMap&String,String&。 MarshallingHttpMessageConverter 使用 Spring 的 marshaller/un-marshaller 读取/编写 XML 数据。它转换媒体类型为 application/xml 的数据。 MappingJacksonHttpMessageConverter 使用 Jackson 的 ObjectMapper 读取/编写 JSON 数据。它转换媒体类型为 application/json 的数据。 AtomFeedHttpMessageConverter 使用 ROME 的 Feed API 读取/编写 ATOM 源。它转换媒体类型为 application/atom+xml 的数据。 RssChannelHttpMessageConverter 使用 ROME 的 feed API 读取/编写 RSS 源。它转换媒体类型为 application/rss+xml 的数据。 回页首 构建 RESTful web 服务 在此部分中,学习构建可生成多个具象的简单 RESTful web 服务。示例应用程序中使用的一些资源在 “使用 Spring 3 构建 RESTful web 服务”(参见 参考资料)中构建。还可以 下载 示例代码。 首先,您必须配置 HttpMessageConverter。要生成多个具象,自定义几个 HttpMessageConverter 实例,以将对象转换为不同的媒体类型。此部分包括 JSON、ATOM 和 XML 媒体类型。 JSON 从最简单的示例开始。JSON 是一个轻量型的数据交换格式,人们可轻松地进行读取和编写。清单 1 显示了配置 JSON converter 的代码。 清单 1. 配置 rest-servlet.xml 中的 HttpMessageConverter &bean class="org.springframework.web.servlet.mvc.annotation .AnnotationMethodHandlerAdapter"&
&property name="messageConverters"&
&ref bean="jsonConverter" /&
&ref bean="marshallingConverter" /&
&ref bean="atomConverter" /&
&/property& &/bean& &bean id="jsonConverter"
class="org.springframework.http.converter.json .MappingJacksonHttpMessageConverter"&
&property name="supportedMediaTypes" value="application/json" /& &/bean& 在配置中,注册了 3 个转换程序。MappingJacksonHttpMessageConverter 用于将对象转换为 JSON,反之亦然。此内置转换程序使用 Jackson 的 ObjectMapper 将 JSON 映射到 JavaBean,因此您必须将下列 Jackson JAR 文件添加到类路径。
org.codehaus.jackson.jar
org.codehaus.jackson.mapper.jar 下一步是编写一个方法,处理请求 JSON 具象的请求。清单 2 显示了详细信息。 清单 2. 处理在 EmployeeController 中定义的 JSON 请求 @RequestMapping(method=RequestMethod.GET, value="/emp/{id}", headers="Accept=application/json") public @ResponseBody Employee getEmp(@PathVariable String id) { Employee e = employeeDS.get(Long.parseLong(id));
} @RequestMapping(method=RequestMethod.GET, value="/emps", headers="Accept=application/json") public @ResponseBody EmployeeListinggetAllEmp() { List&Employee& employees = employeeDS.getAll(); EmployeeListinglist = new EmployeeList(employees);
@ResponseBody 注释用于将返回对象(Employee 或 EmployeeList)变为响应的正文内容,将使用 MappingJacksonHttpMessageConverter 将其映射到 JSON。 使用 HttpMessageConverter 和 @ResponseBody,您可以实现多个具象,而无需包含 Spring 的视图技术 — 这是使用 ContentNegotiatingViewResolver 所不具有的一个优势。 现在您可以使用 CURL 或 REST Client Firefox 插件调用请求。记住添加一个 HTTP 头:Accept=application/json。清单 3 以 JSON 格式显示了所需的响应。 清单 3. getEmp() 和 getAllEmp() 的 JSON 结果 Response for /rest/service/emp/1 {"id":1,"name":"Huang Yi Ming","email":"huangyim@"} Response for /rest/service/emps {"count":2, "employees":[ {"id":1,"name":"Huang Yi Ming","email":"huangyim@"}, {"id":2,"name":"Wu Dong Fei","email":"wudongf@"} ]}
XML Spring 的内置转换程序 MarshallingHttpMessageConverter 用于在对象和 XML (OXM) 之间进行映射。本示例使用 JAXB 2 作为转换程序的 marshaller/un-marshaller。清单 4 显示了配置。 清单 4. 配置 MarshallingHttpMessageConverter &bean id="marshallingConverter" class="org.springframework.http.converter.xml .MarshallingHttpMessageConverter"& &constructor-arg ref="jaxbMarshaller" /&
&property name="supportedMediaTypes" value="application/xml"/&
&bean id="jaxbMarshaller"
class="org.springframework.oxm.jaxb.Jaxb2Marshaller"&
&property name="classesToBeBound"&
&value&dw.spring3.rest.bean.Employee&/value&
&value&dw.spring3.rest.bean.EmployeeList&/value&
&/property&
了解 JAXB 2 不能很好地支持 java.util.List&T& 到 XML 的映射很重要。常用实践是为对象集添加一个包装类。参见 “使用 Spring 3 构建 RESTful web 服务”(参见 参考资料)或 下载 源代码,了解此 JAXB 注释类的详细信息。 在处理请求的控制器中的方法如何?回顾一下 清单 2 中的代码。发现在此处不需要添加任何代码一点也不奇怪。您只需要在 Accept 头中添加另一个支持的媒体类型,如下所示。 headers=”Accept=application/json, application/xml”
转换程序将对象正确地映射到请求的类型(JSON 或 XML)。清单 5 显示了请求 application/xml 具象的理想结果。 清单 5. getEmp() 和 getAllEmp() 的 XML 结果 Response for /rest/service/emp/1 &?xml version="1.0" encoding="UTF-8" standalone="yes"?& &employee&
&email&huangyim@&/email&
&id&1&/id&
&name&Huang Yi Ming&/name& &/employee& Response for /rest/service/emps &?xml version="1.0" encoding="UTF-8" standalone="yes"?&
&employees&
&count&2&/count&
&employee&
&email&huangyim@&/email&
&id&1&/id&
&name&Huang Yi Ming&/name&
&/employee&
&employee&
&email&wudongf@&/email&
&id&2&/id&&name&Wu Dong Fei&/name&
&/employee& &/employees&
ATOM 源 ATOM 源是另一种在 RESTful web 服务中交换数据的常见格式。Atom 源文档是 Atom 源(包括有关源及与其相关的所有或部分项的元数据)的具象。其根是 atom:feed 元素。还有一个 ATOM Publish Protocol (APP) 定义交换格式和行为。(定义 ATOM 和 APP 格式不在本文的讨论范围内。参见 参考资料 了解更多信息。) 本示例使用 AtomFeedHttpMessageConverter 转换 ATOM 源,利用 ROME ATOM API。因此,您必须在类路径中包含 JAR 文件 sun.syndication.jar。清单 6 显示了此转换程序的配置。 清单 6. 配置 AtomFeedHttpMessageConverter &bean id="atomConverter" class="org.springframework.http.converter.feed .AtomFeedHttpMessageConverter"& &property name="supportedMediaTypes" value="application/atom+xml" /& &/bean&
清单 7 显示了处理 ATOM 请求和源生成的代码。 清单 7. EmployeeController & AtomUtil 类中的 getEmpFeed() @RequestMapping(method=RequestMethod.GET, value="/emps", headers="Accept=application/atom+xml") public @ResponseBody Feed getEmpFeed() { List&Employee& employees = employeeDS.getAll(); return AtomUtil.employeeFeed(employees, jaxb2Mashaller); } public static Feed employeeFeed( List&Employee& employees, Jaxb2Marshaller marshaller) { Feed feed = new Feed(); feed.setFeedType("atom_1.0"); feed.setTitle("Employee Atom Feed"); List&Entry& entries = new ArrayList&Entry&(); for(Employee e : employees) { StreamResult result = new StreamResult( new ByteArrayOutputStream()); marshaller.marshal(e, result); String xml = result.getOutputStream().toString(); Entry entry = new Entry(); entry.setId(Long.valueOf(e.getId()).toString()); entry.setTitle(e.getName()); Content content = new Content(); content.setType(Content.XML); content.setValue(xml); List&Content& contents = new ArrayList&Content&(); contents.add(content); entry.setContents(contents); entries.add(entry); } feed.setEntries(entries);
在上述代码中,注意:
getEmpFeed() 方法将同一 URI 处理为 getAllEmp(),但具有不同的 Accept 头。
使用 employeeFeed() 方法,您可以将 Employee 对象解析为 XML,然后将其添加到源项的 &content& 元素。 清单 8 显示了请求 URI /rest/service/emps 的 application/atom+xml 具象时的输出。 清单 8. 请求 application/atom+xml 时的 /rest/service/emps 输出 &?xml version="1.0" encoding="UTF-8"?& &feed xmlns="http://www.w3.org/2005/Atom"& &title&Employee Atom Feed&/title& &entry&
&title&Huang Yi Ming&/title&
&id&1&/id&
&content type="xml"&
&employee&
&email&huangyim@&/email&
&id&1&/id&
&name&Huang Yi Ming&/name&
&/employee&
&/content& &/entry&
&title&Wu Dong Fei&/title&
&id&2&/id&
&content type="xml"&
&employee&
&email&wudongf@&/email&
&id&2&/id&
&name&Wu Dong Fei&/name&
&/employee&
&/content& &/entry& &/feed& 实现 POST、PUT 和 DELETE 目前为止,示例已实现了几个处理 HTTP GET 方法的方法。清单 9 显示了 POST、PUT 和 DELETE 方法的实现。 清单 9. EmployeeController 中的 POST、PUT 和 DELETE 方法 @RequestMapping(method=RequestMethod.POST, value="/emp") public @ResponseBody Employee addEmp(@RequestBody Employee e) { employeeDS.add(e);
} @RequestMapping(method=RequestMethod.PUT, value="/emp/{id}") public @ResponseBody Employee updateEmp( @RequestBody Employee e, @PathVariable String id) { employeeDS.update(e);
} @RequestMapping(method=RequestMethod.DELETE, value="/emp/{id}") public @ResponseBody void removeEmp(@PathVariable String id) { employeeDS.remove(Long.parseLong(id)); }
@RequestBody 注释在 addEmp() 和 updateEmp() 方法中使用。它接收 HTTP 请求正文并试图使用注册的 HttpMessageConverter 将其转换为对象类。在下一部分中,您将使用 RestTemplate 与这些服务进行通信。 回页首 使用 RestTemplate 与 REST 服务进行通信 “使用 Spring 3 构建 RESTful web 服务”(参见 参考资料)介绍了如何使用 CURL 和 REST 客户端测试 REST 服务。从编程水平上讲,Jakarta Commons HttpClient 通常用于完成此测试(但这不在本文的讨论范围中)。您还可以使用名为 RestTemplate 的 Spring REST 客户端。从概念上讲,它与 Spring 中的其他模板类相似,比如 JdbcTemplate 和 JmsTemplate。 RestTemplate 还使用 HttpMessageConverter。您可以将对象类传入请求并使转换程序处理映射。 配置 RestTemplate 清单 10 显示了 RestTemplate 的配置。它还使用之前介绍的 3 个转换程序。 清单 10. 配置 RestTemplate &bean id="restTemplate" class="org.springframework.web.client.RestTemplate"& &property name="messageConverters"& &list& &ref bean="marshallingConverter" /& &ref bean="atomConverter"
/& &ref bean="jsonConverter" /& &/list& &/property& &/bean&
本文中的示例仅使用了一些可简化服务器之间通信的方法。RestTemplate支持其他方法,包括:
exchange:使用请求正文执行一些 HTTP 方法并获得响应。
getForObject:执行 HTTP GET 方法并将响应作为对象获得。
postForObject:使用特定请求正文执行 HTTP POST 方法。
put:使用特定请求正文执行 HTTP PUT 方法。
delete:执行 HTTP DELETE方法以获得特定 URI。 代码示例 下列代码示例帮助阐述如何使用 RestTemplate。参见 RestTemplate API(参见 参考资料)获得使用的 API 的详细说明。 清单 11 显示如何将头添加到请求中,然后调用请求。使用 MarshallingHttpMessageConverter 您可以获得响应并将其转换为类型类。可以使用不同的媒体类型测试其他具象。 清单 11. XML 具象请求 HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_XML); HttpEntity&String& entity = new HttpEntity&String&(headers); ResponseEntity&EmployeeList& response = restTemplate.exchange( "http://localhost:8080/rest/service/emps", HttpMethod.GET, entity, EmployeeList.class); EmployeeListingemployees = response.getBody(); // handle the employees
清单 12 显示了如何将新员工发布到服务器。服务器端服务 addEmp() 可接受媒体类型为 application/xml 和 application/json 的数据。 清单 12. 发布新员工 Employee newEmp = new Employee(99, "guest", ""); HttpEntity&Employee& entity = new HttpEntity&Employee&(newEmp); ResponseEntity&Employee& response = restTemplate.postForEntity( "http://localhost:8080/rest/service/emp", entity, Employee.class); Employee e = response.getBody(); // handle the employee
清单 13 显示了如何 PUT 修改的员工以更新旧员工。它还显示了可用作请求 URI 占位符({id})的功能。 清单 13. PUT 以更新员工 Employee newEmp = new Employee(99, "guest99", ""); HttpEntity&Employee& entity = new HttpEntity&Employee&(newEmp); restTemplate.put( "http://localhost:8080/rest/service/emp/{id}", entity, "99"); 清单 14 显示了如何 DELETE 现有员工。 清单 14. DELETE 现有员工 restTemplate.delete( "http://localhost:8080/rest/service/emp/{id}", "99"); 结束语 在本篇文章中,您学习了 Spring 3 中引入的 HttpMessageConverter。它提供了对多具象的客户端和服务器端支持。使用提供的 源代码,您可以探索本文中的 HttpMessageConverter 实现和使用 “使用 Spring 3 构建 RESTful web 服务” 中的 ContentNegotiatingViewResolver 实现之间的差异。
浏览: 145690 次
来自: 北京
1 楼的,赞!
javascript 阻止事件继续执行--冒泡行为控制 - h ...
只要修改java代码,就需要重新编译,发布,才能运行的啊hon ...
您好请教一下,关于springmvc 在控制层controll ...摘要:MVC模式早在上个世纪70年代就诞生了,直到今天它依然存在,可见生命力相当之强。MVC模式最早用于Smalltalk语言中,最后在其它许多开发语言中都得到了很好的应用,例如,Java中的Struts、Spring MVC等框架。
1. 理解MVC
MVC是一种经典的设计模式,全名为Model-View-Controller,即模型-视图-控制器。
其中,模型是用于封装数据的载体,例如,在中一般通过一个简单的POJO(Plain Ordinary Java Object)来表示,其本质是一个普通的Java Bean,包含一系列的成员变量及其getter/setter方法。对于视图而言,它更加偏重于展现,也就是说,视图决定了界面到底长什么样子,在Java中可通过JSP来充当视图,或者通过纯HTML的方式进行展现,而后者才是目前的主流。模型和视图需要通过控制器来进行粘合,例如,用户发送一个HTTP请求,此时该请求首先会进入控制器,然后控制器去获取数据并将其封装为模型,最后将模型传递到视图中进行展现。
综上所述,MVC的交互过程如图1所示。
2. MVC模式的优点与不足
MVC模式早在上个世纪70年代就诞生了,直到今天它依然存在,可见生命力相当之强。MVC模式最早用于Smalltalk语言中,最后在其它许多开发语言中都得到了很好的应用,例如,Java中的Struts、Spring MVC等框架。正是因为这些MVC框架的出现,才让MVC模式真正落地,让开发更加高效,让代码耦合度尽量减小,让应用程序各部分的职责更加清晰。
既然MVC模式这么好,难道它就没有不足的地方吗?我认为MVC至少有以下三点不足:
每次请求必须经过“控制器-&模型-&视图”这个流程,用户才能看到最终的展现的界面,这个过程似乎有些复杂。实际上视图是依赖于模型的,换句话说,如果没有模型,视图也无法呈现出最终的效果。渲染视图的过程是在服务端来完成的,最终呈现给浏览器的是带有模型的视图页面,性能无法得到很好的优化。
为了使数据展现过程更加直接,并且提供更好的用户体验,我们有必要对MVC模式进行改进。不妨这样来尝试,首先从浏览器发送AJAX请求,然后服务端接受该请求并返回JSON数据返回给浏览器,最后在浏览器中进行界面渲染。
改进后的MVC模式如图2所示。
也就是说,我们输入的是AJAX请求,输出的是JSON数据,市面上有这样的技术来实现这个功能吗?答案是REST。
REST全称是Representational State Transfer(表述性状态转移),它是Roy Fielding博士在2000年写的一篇关于软件风格的论文,此文一出,威震四方!国内外许多知名互联网公司纷纷开始采用这种轻量级的Web服务,大家习惯将其称为RESTful Web Services,或简称REST服务。]
如果将浏览器这一端视为前端,而服务器那一端视为后端的话,可以将以上改进后的MVC模式简化为以下前后端分离模式,如图3所示。
可见,有了REST服务,前端关注界面展现,后端关注业务逻辑,分工明确,职责清晰。那么,如何使用REST服务将应用程序进行前后端分离呢?我们接下来继续探讨,首先我们需要认识REST。
3. 认识REST
REST本质上是使用URL来访问资源种方式。众所周知,URL就是我们平常使用的请求地址了,其中包括两部分:请求方式与请求路径,比较常见的请求方式是GET与POST,但在REST中又提出了几种其它类型的请求方式,汇总起来有六种:GET、POST、PUT、DELETE、HEAD、OPTIONS。尤其是前四种,正好与CRUD(Create-Retrieve-Update-Delete,增删改查)四种操作相对应,例如,GET(查)、POST(增)、PUT(改)、DELETE(删),这正是REST与CRUD的异曲同工之妙!需要强调的是,REST是“面向资源”的,这里提到的资源,实际上就是我们常说的领域对象,在系统设计过程中,我们经常通过领域对象来进行数据建模。
REST是一个“无状态”的架构模式,因为在任何时候都可以由客户端发出请求到服务端,最终返回自己想要的数据,当前请求不会受到上次请求的影响。也就是说,服务端将内部资源发布REST服务,客户端通过URL来访问这些资源,这不就是SOA所提倡的“面向服务”的思想吗?所以,REST也被人们看做是一种“轻量级”的SOA实现技术,因此在企业级应用与互联网应用中都得到了广泛应用。
下面我们举几个例子对REST请求进行简单描述:
可见,请求路径相同,但请求方式不同,所代表的业务操作也不同,例如,/advertiser/1这个请求,带有GET、PUT、DELETE三种不同的请求方式,对应三种不同的业务操作。
虽然REST看起来还是很简单的,实际上我们往往需要提供一个REST框架,让其实现前后端分离架构,让开发人员将精力集中在业务上,而并非那些具体的技术细节。下面我们将使用Java技术来实现这个REST框架,整体框架会基于Spring进行开发。
4. 实现REST框架
4.1 统一响应结构
使用REST框架实现前后端分离架构,我们需要首先确定返回的JSON响应结构是统一的,也就是说,每个REST请求将返回相同结构的JSON响应结构。不妨定义一个相对通用的JSON响应结构,其中包含两部分:元数据与返回值,其中,元数据表示操作是否成功与返回值消息等,返回值对应服务端方法所返回的数据。该JSON响应结构如下:
"success": true,
"message": "ok"
"data": ...
}为了在框架中映射以上JSON响应结构,我们需要编写一个Response类与其对应:
public&class&Response&{&&&&&&&&private&static&final&String&OK&=&“ok”;&&&&&&private&static&final&String&ERROR&=&“error”;&&&&&&&&private&Meta&&&&&&&private&Object&&&&&&&&&public&Response&success()&{&&&&&&&&&&this.meta&=&new&Meta(true,&OK);&&&&&&&&&&return&this;&&&&&&}&&&&&&&&public&Response&success(Object&data)&{&&&&&&&&&&this.meta&=&new&Meta(true,&OK);&&&&&&&&&&this.data&=&&&&&&&&&&&return&this;&&&&&&}&&&&&&&&public&Response&failure()&{&&&&&&&&&&this.meta&=&new&Meta(false,&ERROR);&&&&&&&&&&return&this;&&&&&&}&&&&&&&&public&Response&failure(String&message)&{&&&&&&&&&&this.meta&=&new&Meta(false,&message);&&&&&&&&&&return&this;&&&&&&}&&&&&&&&public&Meta&getMeta()&{&&&&&&&&&&return&&&&&&&}&&&&&&&&public&Object&getData()&{&&&&&&&&&&return&&&&&&&}&&&&&&&&public&class&Meta&{&&&&&&&&&&&&private&boolean&&&&&&&&&&&private&String&&&&&&&&&&&&&public&Meta(boolean&success)&{&&&&&&&&&&&&&&this.success&=&&&&&&&&&&&}&&&&&&&&&&&&public&Meta(boolean&success,&String&message)&{&&&&&&&&&&&&&&this.success&=&&&&&&&&&&&&&&&this.message&=&&&&&&&&&&&}&&&&&&&&&&&&public&boolean&isSuccess()&{&&&&&&&&&&&&&&return&&&&&&&&&&&}&&&&&&&&&&&&public&String&getMessage()&{&&&&&&&&&&&&&&return&&&&&&&&&&&}&&&&&&}&&}&&
以上Response类包括两类通用返回值消息:ok与error,还包括两个常用的操作方法:success( )与failure( ),通过一个内部类来展现元数据结构,我们在下文中多次会使用该Response类。
实现该REST框架需要考虑许多问题,首当其冲的就是对象序列化问题。
4.2 实现对象序列化
想要解释什么是对象序列化?不妨通过一些例子进行说明。比如,通过浏览器发送了一个普通的HTTP请求,该请求携带了一个JSON格式的参数,在服务端需要将该JSON参数转换为普通的Java对象,这个转换过程称为序列化。再比如,在服务端获取了数据,此时该数据是一个普通的Java对象,然后需要将这个Java对象转换为JSON字符串,并将其返回到浏览器中进行渲染,这个转换过程称为反序列化。不管是序列化还是反序列化,我们一般都称为序列化。
实际上,Spring MVC已经为我们提供了这类序列化特性,只需在Controller的方法参数中使用@RequestBody注解定义需要反序列化的参数即可,如以下代码片段:
@Controller&&public&class&AdvertiserController&{&&&&&&&&@RequestMapping(value&=&“/advertiser”,&method&=&RequestMethod.POST)&&&&&&public&Response&createAdvertiser(@RequestBody&AdvertiserParam&advertiserParam)&{&&&&&&&&&&…&&&&&&}&&}&&
若需要对Controller的方法返回值进行序列化,则需要在该返回值上使用@ResponseBody注解来定义,如以下代码片段:
@Controller&&public&class&AdvertiserController&{&&&&&&&&@RequestMapping(value&=&“/advertiser/{id}”,&method&=&RequestMethod.GET)&&&&&&public&@ResponseBody&Response&getAdvertiser(@PathVariable(“id”)&String&advertiserId)&{&&&&&&&&&&…&&&&&&}&&}&&
当然,@ResponseBody注解也可以定义在类上,这样所有的方法都继承了该特性。由于经常会使用到@ResponseBody注解,所以Spring提供了一个名为@RestController的注解来取代以上的@Controller注解,这样我们就可以省略返回值前面的@ResponseBody注解了,但参数前面的@RequestBody注解是无法省略的。实际上,看看Spring中对应@RestController注解的源码便可知晓:
@Target({ElementType.TYPE})&&@Retention(RetentionPolicy.RUNTIME)&&@Documented&&@Controller&&@ResponseBody&&public&@interface&RestController&{&&&&&&&&String&value()&default&“”;&&}&&
可见,@RestController注解已经被@Controller与@ResponseBody注解定义过了,Spring框架会识别这类注解。需要注意的是,该特性在Spring 4.0中才引入。
因此,我们可将以上代码进行如下改写:
@RestController&&public&class&AdvertiserController&{&&&&&&&&@RequestMapping(value&=&“/advertiser”,&method&=&RequestMethod.POST)&&&&&&public&Response&createAdvertiser(@RequestBody&AdvertiserParam&advertiserParam)&{&&&&&&&&&&…&&&&&&}&&&&&&&&@RequestMapping(value&=&“/advertiser/{id}”,&method&=&RequestMethod.GET)&&&&&&public&Response&getAdvertiser(@PathVariable(“id”)&String&advertiserId)&{&&&&&&&&&&…&&&&&&}&&}&&
除了使用注解来定义序列化行为以外,我们还需要使用Jackson来提供JSON的序列化操作,在Spring配置文件中只需添加以下配置即可:
&&&&&&&&&&&&&&&&&class=“org.springframework.http.converter.json.MappingJackson2HttpMessageConverter”&&&&&&&&&&
若需要对Jackson的序列化行为进行定制,比如,排除值为空属性、进行缩进输出、将驼峰转为下划线、进行日期格式化等,这又如何实现呢?
首先,我们需要扩展Jackson提供的ObjectMapper类,代码如下:
public&class&CustomObjectMapper&extends&ObjectMapper&{&&&&&&&&private&boolean&camelCaseToLowerCaseWithUnderscores&=&false;&&&&&&private&String&dateFormatP&&&&&&&&public&void&setCamelCaseToLowerCaseWithUnderscores(boolean&camelCaseToLowerCaseWithUnderscores)&{&&&&&&&&&&this.camelCaseToLowerCaseWithUnderscores&=&camelCaseToLowerCaseWithU&&&&&&}&&&&&&&&public&void&setDateFormatPattern(String&dateFormatPattern)&{&&&&&&&&&&this.dateFormatPattern&=&dateFormatP&&&&&&}&&&&&&&&public&void&init()&{&&&&&&&&&&&&&&&&&&&&setSerializationInclusion(JsonInclude.Include.NON_NULL);&&&&&&&&&&&&&&&&&&&&configure(SerializationFeature.INDENT_OUTPUT,&true);&&&&&&&&&&&&&&&&&&&&if&(camelCaseToLowerCaseWithUnderscores)&{&&&&&&&&&&&&&&setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);&&&&&&&&&&}&&&&&&&&&&&&&&&&&&&&if&(StringUtil.isNotEmpty(dateFormatPattern))&{&&&&&&&&&&&&&&DateFormat&dateFormat&=&new&SimpleDateFormat(dateFormatPattern);&&&&&&&&&&&&&&setDateFormat(dateFormat);&&&&&&&&&&}&&&&&&}&&}&&
然后,将CustomObjectMapper注入到MappingJackson2HttpMessageConverter中,Spring配置如下:
&id=“objectMapper”&class=“com.xxx.api.json.CustomObjectMapper”&init-method=“init”&&&&&&&name=“camelCaseToLowerCaseWithUnderscores”&value=“true”&&&&&&&name=“dateFormatPattern”&value=“yyyy-MM-dd&HH:mm:ss”&&&&&&&&&&&&&&&&&&&&&&&class=“org.springframework.http.converter.json.MappingJackson2HttpMessageConverter”&&&&&&&&&&&&&&&name=“objectMapper”&ref=“objectMapper”&&&&&&&&&&&&&&&&&&&&
通过以上过程,我们已经完成了一个基于Spring MVC的REST框架,只不过该框架还非常单薄,还缺乏很多关键性特性,尤其是异常处理。
4.3 处理异常行为
在Spring MVC中,我们可以使用AOP技术,编写一个全局的异常处理切面类,用它来统一处理所有的异常行为,在Spring 3.2中才开始提供。使用法很简单,只需定义一个类,并通过@ControllerAdvice注解将其标注即可,同时需要使用@ResponseBody注解表示返回值可序列化为JSON字符串。代码如下:
@ControllerAdvice&&@ResponseBody&&public&class&ExceptionAdvice&{&&&&&&&&&&&&&&&&@ResponseStatus(HttpStatus.BAD_REQUEST)&&&&&&@ExceptionHandler(HttpMessageNotReadableException.class)&&&&&&public&Response&handleHttpMessageNotReadableException(HttpMessageNotReadableException&e)&{&&&&&&&&&&logger.error(”参数解析失败”,&e);&&&&&&&&&&return&new&Response().failure(“could_not_read_json”);&&&&&&}&&&&&&&&&&&&&&&&@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)&&&&&&@ExceptionHandler(HttpRequestMethodNotSupportedException.class)&&&&&&public&Response&handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException&e)&{&&&&&&&&&&logger.error(”不支持当前请求方法”,&e);&&&&&&&&&&return&new&Response().failure(“request_method_not_supported”);&&&&&&}&&&&&&&&&&&&&&&&@ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)&&&&&&@ExceptionHandler(HttpMediaTypeNotSupportedException.class)&&&&&&public&Response&handleHttpMediaTypeNotSupportedException(Exception&e)&{&&&&&&&&&&logger.error(”不支持当前媒体类型”,&e);&&&&&&&&&&return&new&Response().failure(“content_type_not_supported”);&&&&&&}&&&&&&&&&&&&&&&&@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)&&&&&&@ExceptionHandler(Exception.class)&&&&&&public&Response&handleException(Exception&e)&{&&&&&&&&&&logger.error(”服务运行异常”,&e);&&&&&&&&&&return&new&Response().failure(e.getMessage());&&&&&&}&&}&&
可见,在ExceptionAdvice类中包含一系列的异常处理方法,每个方法都通过@ResponseStatus注解定义了响应状态码,此外还通过@ExceptionHandler注解指定了具体需要拦截的异常类。以上过程只是包含了一部分的异常情况,若需处理其它异常,可添加方法具体的方法。需要注意的是,在运行时从上往下依次调用每个异常处理方法,匹配当前异常类型是否与@ExceptionHandler注解所定义的异常相匹配,若匹配,则执行该方法,同时忽略后续所有的异常处理方法,最终会返回经JSON序列化后的Response对象。
4.4 支持参数验证
我们回到上文所提到的示例,这里处理一个普通的POST请求,代码如下:
@RestController&&public&class&AdvertiserController&{&&&&&&&&@RequestMapping(value&=&“/advertiser”,&method&=&RequestMethod.POST)&&&&&&public&Response&createAdvertiser(@RequestBody&AdvertiserParam&advertiserParam)&{&&&&&&&&&&…&&&&&&}&&}&&
其中,AdvertiserParam参数包含若干属性,通过以下类结构可见,它是一个传统的POJO:
public&class&AdvertiserParam&{&&&&&&&&private&String&advertiserN&&&&&&&&&&&&private&String&&&&&&&&&&&}&&
如果业务上需要确保AdvertiserParam对象的advertiserName属性必填,如何实现呢?
若将这类参数验证的代码写死在Controller中,势必会与正常的业务逻辑搅在一起,导致责任不够单一,违背于“单一责任原则”。建议将其参数验证行为从Controller中剥离出来,放到另外的类中,这里仅提供一个@Valid注解来定义AdvertiserParam参数,并在AdvertiserParam类中通过@NotEmpty注解来定义advertiserName属性,就像下面这样:
@RestController&&public&class&AdvertiserController&{&&&&&&&&@RequestMapping(value&=&“/advertiser”,&method&=&RequestMethod.POST)&&&&&&public&Response&createAdvertiser(@RequestBody&@Valid&AdvertiserParam&advertiserParam)&{&&&&&&&&&&…&&&&&&}&&}&&&&public&class&AdvertiserParam&{&&&&&&&&@NotEmpty&&&&&&private&String&advertiserN&&&&&&&&&&&&private&String&&&&&&&&&&&}&&
这里的@Valid注解实际上是Validation Bean规范提供的注解,该规范已由Hibernate Validator框架实现,因此需要添加以下Maven依赖到pom.xml文件中:
&&&&&&org.hibernate&&&&&&hibernate-validator&&&&&&${hibernate-validator.version}&&&&
需要注意的是,Hibernate Validator与Hibernate没有任何依赖关系,唯一有联系的只是都属于JBoss公司的开源项目而已。
要实现@NotEmpty注解的功能,我们需要做以下几件事情。
首先,定义一个@NotEmpty注解类,代码如下:
@Documented&&@Target({ElementType.FIELD,&ElementType.PARAMETER})&&@Retention(RetentionPolicy.RUNTIME)&&@Constraint(validatedBy&=&NotEmptyValidator.class)&&public&@interface&NotEmpty&{&&&&&&&&String&message()&default&“not_empty”;&&&&&&&&Class&?&[]&groups()&default&{};&&&&&&&&Class&?&extends&Payload&[]&payload()&default&{};&&}&&
以上注解类必须包含message、groups、payload三个属性,因为这是规范所要求的,此外,需要通过@Constraint注解指定一个验证器类,这里对应的是NotEmptyValidator,其代码如下:
public&class&NotEmptyValidator&implements&ConstraintValidator&NotEmpty,&String&&{&&&&&&&&@Override&&&&&&public&void&initialize(NotEmpty&constraintAnnotation)&{&&&&&&}&&&&&&&&@Override&&&&&&public&boolean&isValid(String&value,&ConstraintValidatorContext&context)&{&&&&&&&&&&return&StringUtil.isNotEmpty(value);&&&&&&}&&}&&
以上验证器类实现了ConstraintValidator接口,并在该接口的isValid( )方法中完成了具体的参数验证逻辑。需要注意的是,实现接口时需要指定泛型,第一个参数表示验证注解类型(NotEmpty),第二个参数表示需要验证的参数类型(String)。
然后,我们需要在Spring配置文件中开启该特性,需添加如下配置:
&class=“org.springframework.validation.beanvalidation.MethodValidationPostProcessor”&&
最后,需要在全局异常处理类中添加参数验证处理方法,代码如下:
@ControllerAdvice&&@ResponseBody&&public&class&ExceptionAdvice&{&&&&&&&&&&&&&&&&@ResponseStatus(HttpStatus.BAD_REQUEST)&&&&&&@ExceptionHandler(ValidationException.class)&&&&&&public&Response&handleValidationException(ValidationException&e)&{&&&&&&&&&&logger.error(”参数验证失败”,&e);&&&&&&&&&&return&new&Response().failure(“validation_exception”);&&&&&&}&&}&&
至此,REST框架已集成了Bean Validation特性,我们可以使用各种注解来完成所需的参数验证行为了。
看似该框架可以在本地成功跑起来,整个架构包含两个应用,前端应用提供纯静态的HTML页面,后端应用发布REST API,前端需要通过AJAX调用后端发布的REST API,然而AJAX是不支持跨域访问的,也就是说,前后端两个应用必须在同一个域名下才能访问。这是非常严重的技术障碍,一定需要找到解决方案。
4.5 解决跨域问题
比如,前端应用为静态站点且部署在域下,后端应用发布REST API并部署在域下,如何使前端应用通过AJAX跨域访问后端应用呢?这需要使用到CORS技术来实现,这也是目前最好的解决方案了。
[CORS全称为Cross Origin Resource Sharing(跨域资源共享),服务端只需添加相关响应头信息,即可实现客户端发出AJAX跨域请求。]
CORS技术非常简单,易于实现,目前绝大多数浏览器均已支持该技术(IE8浏览器也支持了),服务端可通过任何编程语言来实现,只要能将CORS响应头写入response对象中即可。
下面我们继续扩展REST框架,通过CORS技术实现AJAX跨域访问。
首先,我们需要编写一个Filter,用于过滤所有的HTTP请求,并将CORS响应头写入response对象中,代码如下:
public&class&CorsFilter&implements&Filter&{&&&&&&&&private&String&allowO&&&&&&private&String&allowM&&&&&&private&String&allowC&&&&&&private&String&allowH&&&&&&private&String&exposeH&&&&&&&&@Override&&&&&&public&void&init(FilterConfig&filterConfig)&throws&ServletException&{&&&&&&&&&&allowOrigin&=&filterConfig.getInitParameter(”allowOrigin”);&&&&&&&&&&allowMethods&=&filterConfig.getInitParameter(”allowMethods”);&&&&&&&&&&allowCredentials&=&filterConfig.getInitParameter(”allowCredentials”);&&&&&&&&&&allowHeaders&=&filterConfig.getInitParameter(”allowHeaders”);&&&&&&&&&&exposeHeaders&=&filterConfig.getInitParameter(”exposeHeaders”);&&&&&&}&&&&&&&&@Override&&&&&&public&void&doFilter(ServletRequest&req,&ServletResponse&res,&FilterChain&chain)&throws&IOException,&ServletException&{&&&&&&&&&&HttpServletRequest&request&=&(HttpServletRequest)&&&&&&&&&&&HttpServletResponse&response&=&(HttpServletResponse)&&&&&&&&&&&if&(StringUtil.isNotEmpty(allowOrigin))&{&&&&&&&&&&&&&&List&String&&allowOriginList&=&Arrays.asList(allowOrigin.split(”,”));&&&&&&&&&&&&&&if&(CollectionUtil.isNotEmpty(allowOriginList))&{&&&&&&&&&&&&&&&&&&String&currentOrigin&=&request.getHeader(”Origin”);&&&&&&&&&&&&&&&&&&if&(allowOriginList.contains(currentOrigin))&{&&&&&&&&&&&&&&&&&&&&&&response.setHeader(”Access-Control-Allow-Origin”,&currentOrigin);&&&&&&&&&&&&&&&&&&}&&&&&&&&&&&&&&}&&&&&&&&&&}&&&&&&&&&&if&(StringUtil.isNotEmpty(allowMethods))&{&&&&&&&&&&&&&&response.setHeader(”Access-Control-Allow-Methods”,&allowMethods);&&&&&&&&&&}&&&&&&&&&&if&(StringUtil.isNotEmpty(allowCredentials))&{&&&&&&&&&&&&&&response.setHeader(”Access-Control-Allow-Credentials”,&allowCredentials);&&&&&&&&&&}&&&&&&&&&&if&(StringUtil.isNotEmpty(allowHeaders))&{&&&&&&&&&&&&&&response.setHeader(”Access-Control-Allow-Headers”,&allowHeaders);&&&&&&&&&&}&&&&&&&&&&if&(StringUtil.isNotEmpty(exposeHeaders))&{&&&&&&&&&&&&&&response.setHeader(”Access-Control-Expose-Headers”,&exposeHeaders);&&&&&&&&&&}&&&&&&&&&&chain.doFilter(req,&res);&&&&&&}&&&&&&&&@Override&&&&&&public&void&destroy()&{&&&&&&}&&}&&
以上CorsFilter将从web.xml中读取相关Filter初始化参数,并将在处理HTTP请求时将这些参数写入对应的CORS响应头中,下面大致描述一下这些CORS响应头的意义:
Access-Control-Allow-Origin:允许访问的客户端域名,例如:,若为*,则表示从任意域都能访问,即不做任何限制。Access-Control-Allow-Methods:允许访问的方法名,多个方法名用逗号分割,例如:GET,POST,PUT,DELETE,OPTIONS。Access-Control-Allow-Credentials:是否允许请求带有验证信息,若要获取客户端域下的cookie时,需要将其设置为true。Access-Control-Allow-Headers:允许服务端访问的客户端请求头,多个请求头用逗号分割,例如:Content-Type。Access-Control-Expose-Headers:允许客户端访问的服务端响应头,多个响应头用逗号分割。
需要注意的是,CORS规范中定义Access-Control-Allow-Origin只允许两种取值,要么为*,要么为具体的域名,也就是说,不支持同时配置多个域名。为了解决跨多个域的问题,需要在代码中做一些处理,这里将Filter初始化参数作为一个域名的集合(用逗号分隔),只需从当前请求中获取Origin请求头,就知道是从哪个域中发出的请求,若该请求在以上允许的域名集合中,则将其放入Access-Control-Allow-Origin响应头,这样跨多个域的问题就轻松解决了。
以下是web.xml中配置CorsFilter的方法:
&&&&&&corsFilter&&&&&&com.xxx.api.cors.CorsFilter&&&&&&&&&&&&&&&&allowOrigin&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&allowMethods&&&&&&&&&&GET,POST,PUT,DELETE,OPTIONS&&&&&&&&&&&&&&&&&&&&&&allowCredentials&&&&&&&&&&true&&&&&&&&&&&&&&&&&&&&&&allowHeaders&&&&&&&&&&Content-Type&&&&&&&&&&&&&&&&corsFilter&&&&&&/*&&&&
完成以上过程即可实现AJAX跨域功能了,但似乎还存在另外一个问题,由于REST是无状态的,后端应用发布的REST API可在用户未登录的情况下被任意调用,这显然是不安全的,如何解决这个问题呢?我们需要为REST请求提供安全机制。
4.6 提供安全机制
解决REST安全调用问题,可以做得很复杂,也可以做得特简单,可按照以下过程提供REST安全机制:
当用户登录成功后,在服务端生成一个token,并将其放入内存中(可放入JVM或Redis中),同时将该token返回到客户端。在客户端中将返回的token写入cookie中,并且每次请求时都将token随请求头一起发送到服务端。提供一个AOP切面,用于拦截所有的Controller方法,在切面中判断token的有效性。当登出时,只需清理掉cookie中的token即可,服务端token可设置过期时间,使其自行移除。
首先,我们需要定义一个用于管理token的接口,包括创建token与检查token有效性的功能。代码如下:
public&interface&TokenManager&{&&&&&&&&String&createToken(String&username);&&&&&&&&boolean&checkToken(String&token);&&}&&
然后,我们可提供一个简单的TokenManager实现类,将token存储到JVM内存中。代码如下:
public&class&DefaultTokenManager&implements&TokenManager&{&&&&&&&&private&static&Map&String,&String&&tokenMap&=&new&ConcurrentHashMap&&();&&&&&&&&@Override&&&&&&public&String&createToken(String&username)&{&&&&&&&&&&String&token&=&CodecUtil.createUUID();&&&&&&&&&&tokenMap.put(token,&username);&&&&&&&&&&return&&&&&&&}&&&&&&&&@Override&&&&&&public&boolean&checkToken(String&token)&{&&&&&&&&&&return&!StringUtil.isEmpty(token)&&&&tokenMap.containsKey(token);&&&&&&}&&}&&
需要注意的是,如果需要做到分布式集群,建议基于Redis提供一个实现类,将token存储到Redis中,并利用Redis与生俱来的特性,做到token的分布式一致性。
然后,我们可以基于Spring AOP写一个切面类,用于拦截Controller类的方法,并从请求头中获取token,最后对token有效性进行判断。代码如下:
public&class&SecurityAspect&{&&&&&&&&private&static&final&String&DEFAULT_TOKEN_NAME&=&“X-Token”;&&&&&&&&private&TokenManager&tokenM&&&&&&private&String&tokenN&&&&&&&&public&void&setTokenManager(TokenManager&tokenManager)&{&&&&&&&&&&this.tokenManager&=&tokenM&&&&&&}&&&&&&&&public&void&setTokenName(String&tokenName)&{&&&&&&&&&&if&(StringUtil.isEmpty(tokenName))&{&&&&&&&&&&&&&&tokenName&=&DEFAULT_TOKEN_NAME;&&&&&&&&&&}&&&&&&&&&&this.tokenName&=&tokenN&&&&&&}&&&&&&&&public&Object&execute(ProceedingJoinPoint&pjp)&throws&Throwable&{&&&&&&&&&&&&&&&&&&&&MethodSignature&methodSignature&=&(MethodSignature)&pjp.getSignature();&&&&&&&&&&Method&method&=&methodSignature.getMethod();&&&&&&&&&&&&&&&&&&&&if&(method.isAnnotationPresent(IgnoreSecurity.class))&{&&&&&&&&&&&&&&return&pjp.proceed();&&&&&&&&&&}&&&&&&&&&&&&&&&&&&&&String&token&=&WebContext.getRequest().getHeader(tokenName);&&&&&&&&&&&&&&&&&&&&if&(!tokenManager.checkToken(token))&{&&&&&&&&&&&&&&String&message&=&String.format(”token&[%s]&is&invalid”,&token);&&&&&&&&&&&&&&throw&new&TokenException(message);&&&&&&&&&&}&&&&&&&&&&&&&&&&&&&&return&pjp.proceed();&&&&&&}&&}&&
若要使SecurityAspect生效,则需要添加如下Spring 配置:
&id=“securityAspect”&class=“com.xxx.api.security.SecurityAspect”&&&&&&&name=“tokenManager”&ref=“tokenManager”&&&&&&&name=“tokenName”&value=“X-Token”&&&&&&&&&&&&&ref=“securityAspect”&&&&&&&&&&&method=“execute”&pointcut=“@annotation(org.springframework.web.bind.annotation.RequestMapping)”&&&&&&&&&&
最后,别忘了在web.xml中添加允许的X-Token响应头,配置如下:
&&&&&&allowHeaders&&&&&&Content-Type,X-Token&&&&
本文从经典的MVC模式开始,对MVC模式是什么以及该模式存在的不足进行了简述。然后引出了如何对MVC模式的改良,让其转变为前后端分离架构,以及解释了为何要进行前后端分离。最后通过REST服务将前后端进行解耦,并提供了一款基于Java的REST框架的主要实现过程,尤其是需要注意的核心技术问题及其解决方案。希望本文对正在探索前后端分离的读者们有所帮助,期待与大家共同探讨。
&&相关文章推荐
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:19893次
排名:千里之外
原创:33篇
转载:22篇
已解决的BUG为何总是重复出现?
已经定好的产品需求为何再次修改?
托管机房的服务器为何屡遭黑手?
SVN服务器上的代码为何突然消失?
线上服务器频频宕机,究竟是何人所为?
拒绝服务攻击夜夜出现,究竟是人是鬼?
生产数据库集群一夜之间不见踪影的背后又隐藏着什么?
这一切的背后,是技术的缺陷,还是管理的沦丧?
是黑客的爆发还是运维的无奈?
用技术点亮生命,用产品温暖人心,探讨coding真谛,感悟技术精神。
敬请关注《小永子的架构之路》让我们跟随着小永子走进一个程序员的内心世界……
(1)(2)(4)(1)(7)(24)(15)

我要回帖

更多关于 能为老百姓办事的热线 的文章

 

随机推荐