关于Restful API 设计的Java SpringMVC的介绍主要分为两部分,先介绍Restful API的基础知识,然后介绍如何在Java web项目中的Spring MVC中使用。
一:关于Restful API相关知识
Restful API 对于HTTP动作的约定
Restful API是目前比较成熟的一套互联网应用程序的API设计理论,它遵循统一接口原则,包含了一组预定义的操作,比如,常用的HTTP动词包括以下五个:
GET:查询操作(可以重复操作)对应MySQL的SELECT语句;
POST:添加操作,对应MySQL的CREATE语句;
PUT:更新操作(客户端提供改变后的完整资源),对应MySQL的UPDATE语句;
PATCH:更新操作(客户端提供改变的属性),对应MySQL的UPDATE语句;
DELETE:删除操作,对应MySQL的DELETE语句。
Restful API对接口的域名规定(两种方法)
方法一:对于部署的API应该在专用的域名之下,比如:https://api.example.com(推荐)
方法二:如果确定API很简单,不会进一步扩展,可以考虑放在主域名下:https://example.om/api/
Restful API 对于版本号的约定(两种方法)
方法一:将API的版本号放入URL中:https://api.example.com/v1/
方法二:将版本号放在HTTP头信息中,但不如放入URL方便和直观。Github采用此法。
Restful API 对于URL的约定
在Restful 架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表名相对应。一般来说,数据库中的表都是同种记录的“集合”(collection),所以API中的名词也可以使用复数。
URL的设计规则如下:
/模块/资源/{标识}/集合1/….
比如:
/user/{uid}/friends -> 好友列表
/user/{uid}/followers -> 关注者列表
再举个栗子:
与秒杀相关的操作的接口:
GET /seckill/list 秒杀列表
GET /seckill/{id}/detail 详情页
GET /seckill/time/now 系统时间
POST /seckill/{id}/exposer 暴露秒杀
POST /seckill/{id}/{md5}/execution 执行秒杀
不友好的设计如下:
POST /seckill/execute/{seckillId} (不友好,execute是动词)
GET /seckill/delete/{id} (不友好,应该用DELETE提交,可改为如下:)
DELETE /seckill/{id}/delete (友好)
二、Restful API 在Spring MVC中的实现
Spring中的@RequestMapping注解,分析如下:
@RequestMapping注解的特点如下:
(1)支持标准的URL
(2)Ant风格URL(即?和**等字符)
(3)带{×××}占位符的URL
例如:
/user/*/creation
匹配: /user/aaa/creation、/user/bbb/creation等URL。
/user/**/creation
匹配: /user/creation、/user/aaa/bbb/creation等URL。
/user/{userId}
匹配: /user/123、/user/abc等URL。
/company/{campanyId}/user/{userId}/detail
匹配:/company/123/user/323/detail等的URL
SpringMVC中的具体配置如下:
(1)web.xml文件的内容
<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_3_1.xsd"
version="3.1">
<!-- 修改Servlet 版本为3.1 -->
<!-- 配置DispatcherServlet-->
<servlet>
<servlet-name>seckill-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置springMVC配置需要加载的配置文件
spring-dao.xml,spring-service.xml,spring-web.xml
Mybatis -> spring -> springMVC
-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/spring-*.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>seckill-dispatcher</servlet-name>
<!-- *.do 是非常丑陋的,默认匹配所有请求-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
注意:上述的DispatcherServlet对应的seckill-dispatcher将所有的请求(这里配置为”/”),都会进行handlemapping,即去控制器中去找对于的路径,因此需要配置一个对静态的资源默认的Servlet,因此有如下配置文件。
(2)spring-web.xml 中配置对静态的资源默认的Servlet
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置springMVC -->
<!-- 1:开启SpringMVC注解模式 -->
<!--
(1)自动注册DefaultAnnotationHandlerMapping, AnnotationMethodHandlerAdapter
(2)提供一系列:数据绑定,数字和日期的format @NumberFormat, @DataTimeFormat,xml,json默认读写支持.
-->
<mvc:annotation-driven />
<!-- 2、servlet-mapping 映射路径:"/" -->
<!-- 静态资源默认servlet配置
1、加入对静态资源的处理:js,gif,png
2、允许使用“/”做整体映射
-->
<!-- <mvc:default-servlet-handler />-->
<!-- 3:配置jsp 显示ViewResolver -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
<!-- 4:扫描web相关的bean -->
<context:component-scan base-package="org.seckill.web" />
</beans>
(3)Java 秒杀 SeckillController控制器Restful 接口设计
@Controller
@RequestMapping("/seckill") // url:/模块/{id}/细分 /seckill/list
public class SeckillController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private SeckillService seckillService;
@RequestMapping(value="/list", method = RequestMethod.GET)
public String list(Model model){
//获取列表页
List<Seckill> list = seckillService.getSeckillList();
model.addAttribute("list", list);
// list.jsp + model = ModelAndView
return "list"; //WEB-INF/jsp/list.jsp
}
@RequestMapping(value = "/{seckillId}/detail", method = RequestMethod.GET)
public String detail(@PathVariable("seckillId") Long seckillId, Model model){
if(seckillId == null){
return "redirect:/seckill/list";
}
Seckill seckill = seckillService.getById(seckillId);
if(seckill == null){
return "forward:/seckill/list";
}
model.addAttribute("seckill", seckill);
return "detail";
}
@RequestMapping(value="/{seckillId}/exposer",
method = RequestMethod.POST,
produces = {"application/json;charset=UTF-8"})
@ResponseBody
public SeckillResult<Exposer> exposer(Long seckillId){
SeckillResult<Exposer> result;
try{
Exposer exposer = seckillService.exposeSeckillUrl(seckillId);
result = new SeckillResult<Exposer>(true, exposer);
}catch (Exception e){
logger.error(e.getMessage(), e);
result = new SeckillResult<Exposer>(false, e.getMessage());
}
return result;
}
@RequestMapping(value = "/{seckillId}/{md5}/execution",
method = RequestMethod.POST, produces = {"application/json;charset=UTF-8"})
public SeckillResult<SeckillExecution> execute(@PathVariable("seckillId") Long seckillId,
@PathVariable("md5") String md5,
@CookieValue(value = "killPhone", required = false) Long phone){
//SpringMVC valid
if(phone == null){
return new SeckillResult<SeckillExecution>(false, "未注册");
}
SeckillResult<SeckillExecution> result;
try {
SeckillExecution execution = seckillService.executeSeckill(seckillId, phone, md5);
return new SeckillResult<SeckillExecution>(true, execution);
}catch (RepeatKillException e){
SeckillExecution execution = new SeckillExecution(seckillId, SeckillStateEnum.REPEAT_KILL);
return new SeckillResult<SeckillExecution>(false, execution);
}catch (SeckillCloseException e){
SeckillExecution execution = new SeckillExecution(seckillId, SeckillStateEnum.END);
return new SeckillResult<SeckillExecution>(false, execution);
}catch (Exception e){
logger.error(e.getMessage(), e);
SeckillExecution execution = new SeckillExecution(seckillId, SeckillStateEnum.INNER_ERROR);
return new SeckillResult<SeckillExecution>(false, execution);
}
}
@RequestMapping(value = "/time/now", method = RequestMethod.GET)
@ResponseBody
public SeckillResult<Long> time(){
Date now = new Date();
return new SeckillResult(true, now.getTime());
}
}