RESTful为“Representational State Transfer”的缩写,中文释义为“表现层状态转换”。RESTful不是一种标准,而是一种设计风格。RESTful本质上是一种分布式系统的应用层解决方案。它的主要作用是充分并正确利用HTTP协议的特性,规范资源获取的URI路径。通俗地讲,RESTful风格的设计将参数通过URL拼接传到服务端,目的是让URL看起来更简洁实用。并且对于不同操作,要指定不同的HTTP方法(POST/GET/PUT/DELETE)。可以这么说,只要是具有上述相关约束条件和原则的应用程序或设计就可以被称作RESTful风格的应用。
在RESTful风格的请求路径中,资源是由URI来指定的,RESTful通过规范资源的表现形式来操作资源,其是资源状态的一种表达。一个满足RESTful的程序或设计应满足以下条件和约束。
第一,对请求的URL进行规范。RESTful风格的URL的设计目的是将资源通过合理方式暴露出来。在RESTful风格的URL中不会出现动词,而是使用HTTP协议的动词。下表列出了非RESTful风格的URL及RESTful风格的URL的区别。
非RESTful风格的URL | RESTful风格的URL |
---|---|
POST …rest/userManage/getUserList | GET …rest/userManage/Users |
POST …rest/userManage/addUser?id=1 | POST …rest/userManage/getUser/1 |
POST …rest/userManage/editUser?id=1 | PUT …rest/userManage/editUser/1 |
POST …rest/userManage/deleteUser?id=1 | DELETE …rest/userManage/deleteUser/1 |
从表中可以看到,遵循RESTful风格的URL比较简洁和规范,而且更容易理解URL请求所要表达的需求。
第二,充分利用HTTP方法。下表中列出了HTTP请求对应的动作方法,包含了POST、GET、PUT、PATCH及DELETE方法,需要在不同的请求场景下使用不同的HTTP方法。
HTTP方法名 | 使用场景 |
---|---|
GET | 从服务器取出资源(一项或多项) |
POST | 在服务器新建一个资源 |
PUT | 在服务器更新资源(客户端提供完整资源数据) |
PATCH | 在服务器更新资源(客户端提供完整资源数据) |
DELETE | 从服务器删除资源 |
一般在非RESTful风格设计的应用中基本上只使用POST、GET类型的HTTP动作方法,而在RESTful风格设计的应用中充分利用了HTTP协议的另外动作方法,统一了数据操作的接口,使得URL请求变得简洁化、透明化。
Spring MVC可以使用@RequestMapping的路径设置,结合@PathVariable的参数指定,来实现RESTful风格的请求。
接下来实现这样一个演示案例:通过拼用户编号(userId)来实现一个RESTful风格的请求,并向后台发送该请求,以此来获取JSON格式的用户信息。
第一步,在“com.ccff.model”中创建JavaBean模型。采用该系列之前的User JavaBean模型。具体代码如下:
package com.ccff.model;
import com.ccff.validator.UserValidationGroup1;
import com.ccff.validator.UserValidationGroup2;
import org.hibernate.validator.constraints.NotEmpty;
import javax.validation.constraints.Size;
import java.util.Date;
public class User {
private int userId;
//@NotEmpty(message = "{user.username.isEmpty}", groups = {UserValidationGroup1.class})
private String username;
//@Size(min = 6, max = 12, message = "{user.password.length.error}", groups = {UserValidationGroup2.class})
private String password;
private Date loginDate;
public Date getLoginDate() {
return loginDate;
}
public void setLoginDate(Date loginDate) {
this.loginDate = loginDate;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
第二步,在“com.ccff.controller”中创建名为“RESTfulController”控制器类,并添加一个处理RESTful风格请求的方法,具体代码如下:
package com.ccff.controller;
import com.ccff.model.User;
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;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@Controller
@RequestMapping("/RESTfulTest")
public class RESTfulController {
private List<User> userInfoList = new ArrayList<>();
private void initUserInfoList(){
User user1 = new User();
user1.setUserId(1);
user1.setUsername("张三");
user1.setPassword("123456789");
user1.setLoginDate(new Date());
User user2 = new User();
user2.setUserId(2);
user2.setUsername("李四");
user2.setPassword("123456789");
user2.setLoginDate(new Date());
User user3 = new User();
user3.setUserId(3);
user3.setUsername("王五");
user3.setPassword("123456789");
user3.setLoginDate(new Date());
userInfoList.add(user1);
userInfoList.add(user2);
userInfoList.add(user3);
}
@RequestMapping(value = "/getUserById/{id}", method = {RequestMethod.GET})
public @ResponseBody User getUserById(@PathVariable("id") int userid){
initUserInfoList();
User user = new User();
for (User u : userInfoList){
if (u.getUserId() == userid)
return u;
}
return user;
}
}
在该方法中,在@RequestMapping注解的请求路径中添加一个动态数据“{id}”,它的作用是解析前台的请求路径,将动态数据所在的位置解析为名为id的请求参数。而在Controller的参数中,使用@PathVariable注解,在其中指定请求参数的key名称,并映射在后面定义的形参上,这里定义userid形参来接收名为id的请求参数。方法体中其余的操作就是正常的业务逻辑,最后使用@ResponseBody注解加上之前配置的类型转换器,返回给客户端JSON类型的用户信息。总的来说,利用Spring MVC实现RESTful风格主要就在于请求路径和请求参数的映射,以及RequestMethod的指定。
第三步,修改web.xml文件。由于之前在web.xml中配置了Spring MVC的前端控制器(DispatcherServlet)用于集中处理请求。前端控制器过滤的是后缀为“.action”的请求路径,所以前面编写的RESTful风格的请求是不能被前端控制器过滤并解析的,所以要修改配置,使得RESTful风格的请求可以被Spring MVC的前端控制器处理,即将“< servlet-mapping >”标签下的“< url-pattern >”标签对之前的内容修改为“/”,即过滤所有请求类型的请求至前端控制器,具体修改如下:
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>dispatcherservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>/WEB-INF/config/springmvc.xmlparam-value>
init-param>
servlet>
<servlet-mapping>
<servlet-name>dispatcherservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
<filter>
<filter-name>CharacterEncodingFilterfilter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
<init-param>
<param-name>encodingparam-name>
<param-value>utf-8param-value>
init-param>
<init-param>
<param-name>forceEncodingparam-name>
<param-value>trueparam-value>
init-param>
filter>
<filter-mapping>
<filter-name>CharacterEncodingFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
web-app>
最后,将项目部署到Tomcat上后,在浏览器内输入RESTful风格的请求URL:http://localhost:8080/demo/RESTfulTest/getUserById/1 ,即要查询用户编号为1的用户信息,按回车键访问该路径,可以看到页面上显示用户编号为1的用户信息的JSON格式的数据信息,具体如下所示:
上面演示了查询类型的请求,而新增、修改以及删除的请求与之类似,区别就是需要制定不同的RequestMethod(POST/PUT/DELETE)。样例代码如下:
package com.ccff.controller;
import com.ccff.model.User;
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;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@Controller
@RequestMapping("/RESTfulTest")
public class RESTfulController {
private List<User> userInfoList = new ArrayList<>();
private void initUserInfoList(){
User user1 = new User();
user1.setUserId(1);
user1.setUsername("张三");
user1.setPassword("123456789");
user1.setLoginDate(new Date());
User user2 = new User();
user2.setUserId(2);
user2.setUsername("李四");
user2.setPassword("123456789");
user2.setLoginDate(new Date());
User user3 = new User();
user3.setUserId(3);
user3.setUsername("王五");
user3.setPassword("123456789");
user3.setLoginDate(new Date());
userInfoList.add(user1);
userInfoList.add(user2);
userInfoList.add(user3);
}
/**
* 根据用户编号查询用户信息
* @param id
* @return
*/
@RequestMapping(value = "/getUserById/{id}", method = {RequestMethod.GET})
public @ResponseBody User getUserById(@PathVariable("id") int id){
initUserInfoList();
User user = new User();
for (User u : userInfoList){
if (u.getUserId() == id)
return u;
}
return user;
}
/**
* 添加用户信息
* @param user
* @return
*/
@RequestMapping(value = "/addUser", method = {RequestMethod.POST})
public String addUser(User user){
//具体添加用户信息逻辑
return "...";
}
/**
* 根据用户编号删除用户信息
* @param id
* @return
*/
@RequestMapping(value = "/deleteUser/{id}", method = {RequestMethod.DELETE})
public String deleteUser(@PathVariable("id") int id){
//具体删除用户信息逻辑
return "...";
}
/**
* 修改用户信息
* @param user
* @return
*/
@RequestMapping(value = "/updateUser", method = {RequestMethod.PUT})
public String updateUser(User user){
//具体修改用户信息逻辑
return "...";
}
}
前端在访问RESTful风格的增、删、改请求时,需要配置HTTP请求的方法(method参数)。如果是在JSP页面上使用form表单的提交方式来请求RESTful风格的服务,需要根据请求的类型,在form表单标签中指定HTTP请求的相关method参数。而当使用JavaScript脚本实现Ajax异步请求,或者在其他系统中使用HttpClient进行HTTP接口交互时,也是如此。
但是很多Web框架是不支持GET与POST类型之外的HTTP请求的,在这种情况下只能讲HTTP请求类型设为POST,然后在请求数据中添加名为“_method”的参数,来指定请求的真正类型。然后需要在Web工程中配置过滤器来将带有“_method”参数的POST请求类型转换为真正的请求类型,才能让系统正确处理这些请求。这里在web.xml中配置名为hiddenHttpMethodFilter的过滤器,具体配置如下:
<filter>
<filter-name>hiddenHttpMethodFilterfilter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilterfilter-class>
filter>
<filter-mapping>
<filter-name>hiddenHttpMethodFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
hiddenHttpMethodFilter过滤器的主要作用是,将含有“_method”参数的POST请求,转换为“_method”参数指定的真正请求,然后RESTful风格的服务方法就可以正确处理该类请求。
配置好HTTP类型的过滤器后,就可以对PUT、DELETE等方法进行访问。在请求参数中分别添加“_method:‘PUT’”和“_method:‘DELETE’”,如此就可以访问用户信息的修改及删除方法了。
前面在web.xml中配置了符合RESTful风格的DispatcherServlet前端控制器过滤器,通过过滤所有请求类型的请求至前端控制器的方式实现了正确处理RESTful风格请求的机制。但是这种过滤方式会造成静态资源无法访问的问题。
例如,在web文件夹下创建一个名为“images”的文件用来存放所需的图片静态资源,如图所示:
由于图片放置在WEB-INF文件夹外(由于Java Web的保护机制,WEB-INF文件夹下的文件不可直接访问),所以原则上是可以通过直接访问静态资源的方式获取到该图片的,但是在浏览器内输入“http://localhost:8080/demo/images/timg(1).jpg” 的请求路径后,发现并没有成功获取到图片资源,如下图所示:
出现该问题的原因在于在web.xml中配置的前端控制器的请求过滤机制,为了接收RESTful风格的请求,将过滤的后缀去除了,变成过滤所有后缀的请求路径,此时静态资源会被当做一个业务请求被前端控制器处理,前端控制器没有返现能够处理该请求的Controller控制器方法,所以对外抛出了404(请求资源不可用)错误。
如果想要正确处理静态资源,但又要保证RESTful风格的请求的正常响应,可以通过下面两种方式来解决。
方法一,在类加载配置文件springmvc.xml中使用“mvc:resources”配置静态资源的解析路径,将需要加载的静态资源的URI路径配置在标签中,然后配置该URI映射的真实资源路径。具体配置样例如下:
<mvc:resources mapping="/js/**" location="/js/" />
<mvc:resources mapping="/images/**" location="/images/" />
location表示静态资源所在目录。当然,这里的目录包含/WEB-INF/目录及其子目录。mapping表示对该资源的请求。注意,后面是两个星号**。
该配置会把对该静态资源的访问请求添加到SimpleUrlHandlerMapping的urlMap中,key就是真正与mapping的URI匹配的URI,而value则为静态资源处理器对象ResourceHttpRequestHandler。此时,前端控制器就会根据请求URL中的具体路径来映射出静态资源的真是路径,然后为前端反馈真是的静态资源信息。
再次将项目部署到Tomcat上后,在浏览器内输入请求URL:http://localhost:8080/demo/images/timg(1).jpg 后,此时便可以获取到该图片静态资源,如下图所示:
方法二,在类加载配置文件springmvc.xml中使用“< mvc:default-servlet-handler />”配置默认的Servlet处理器,该配置将在Spring MVC上下文中定义一个DefaultServletHttpRequestHandler,它会对进入DispatcherServlet前端控制器的请求进行筛查,如果返现是没有经过映射的请求,就将该请求交由Web应用服务器默认的Servlet处理,如果不是静态资源的请求,才由DispatcherServlet前端控制器继续处理。此时就可以将请求中的静态资源与其他业务请求分开处理,从而正常地返回静态资源信息。具体配置信息如下:
<mvc:default-servlet-handler />
将项目重新部署到Tomcat上后,在浏览器内输入请求URL:http://localhost:8080/demo/images/timg(1).jpg 后仍然能够得到该图片,具体如下: