1、REST 风格
1.1、REST 简介
- REST(Representational State Transfer),表现形式状态转换
在开发中,它其实指的就是访问网络资源的格式
1.1.1、传统风格资源描述形式
- http://localhost/user/getById?id=1
- http://localhost/user/saveUser
1.1.2、REST 风格描述形式
在 REST 风格中,一般用 模块名 + s 的格式描述资源,表示这是一类资源,而不是单个资源。
4 个常用的 REST 风格请求行为:
- http://localhost/users 查询所有用户信息 GET (查询)
- http://localhost/users/1 查询指定用户信息 GET (查询)
- http://localhost/users 添加用户信息 POST (新增)
- http://localhost/users 修改用户信息 PUT (更新)
- http://localhost/users/1 删除用户信息 DELETE (删除)
REST 风格下,对于同一个路径,不同的请求代表对资源不同的操作。也就是说,我们只需要一个路径和一个请求方式就可以确定一个访问行为。
根据 REST 风格对资源进行访问称为 RESTful 。
优点:
- 最大在优点就是隐藏资源的访问行为,无法通过地址得到对资源是如何操作的
注意:REST 风格只是一种风格,并不是一种规范,可以打破。也就是说我们想怎么写就怎么写,但是我们只是希望大家都遵守这种风格,方便开发。
1.2、RESTful 入门案例
RESTful 就是指使用 REST 风格开发项目。
1.2.1、设定 http 请求动作
REST 风格下,我们对资源的不同操作由路径和请求动作决定,这里的路径我们统一为 模块名 + s,所以我们需要把 Controller 类上面的 @RequsetMapping 中指定的该模块资源的统一前缀去掉。
添加请求动作只需要先把原本 Controller 类的方法上 RequsetMapping 注解的参数改为 模块名 + s ,然后通过 method 属性来给方法添加请求动作。
查询都是 GET、新增是 POST 、修改是 PUT、删除是 DELETE。
@RequestMapping(value = "/users",method = RequestMethod.POST)
@ResponseBody
public String save(@RequestBody User user){
System.out.println("user save, name = " + user.getName() + " , age = " + user.getAge());
return "{'module':'user save'}";
}
这里的新增用户请求需要传递用户参数,这里我们通过 @RequestBody 注解来表示从请求体中获取参数,也就是通过 json 来传递。
1.2.2、设定请求路径参数
也就是我们控制器类方法上面的注解 @RequsetMapping 中的 value 参数,这里需要根据需求决定需不需要带参数(比如删除 name 为 'zs' 的用户,那就需要指定 value="/users/name" 而不只是模块名+s)。
此外,对于带有路径变量的请求参数需要给形参前面添加注解 @PathVariable ,表示从请求路径中解析出该变量当做形参的值。
@RequestMapping(value = "/users/{id}",method = RequestMethod.DELETE)
@ResponseBody
public String delete(@PathVariable Integer id){
System.out.println("user deleted ... user id => " + id);
return "{'module': 'user delete'}";
}
完整代码:
@Controller
public class UserController {
@RequestMapping(value = "/users",method = RequestMethod.POST)
@ResponseBody
public String save(@RequestBody User user){
System.out.println("user save, name = " + user.getName() + " , age = " + user.getAge());
return "{'module':'user save'}";
}
@RequestMapping(value = "/users/{id}",method = RequestMethod.DELETE)
@ResponseBody
public String delete(@PathVariable Integer id){
System.out.println("user deleted ... user id => " + id);
return "{'module': 'user delete'}";
}
@RequestMapping(value = "/users",method = RequestMethod.PUT)
@ResponseBody
public String update(@RequestBody User user){
System.out.println("user deleted ... user => " + user.toString());
return "{'module': 'user update'}";
}
@RequestMapping(value = "/users/{id}",method = RequestMethod.GET)
@ResponseBody
public String getById(@PathVariable Integer id){
System.out.println("user get ... user id => " + id);
return "{'module': 'user get'}";
}
@RequestMapping(value = "/users",method = RequestMethod.GET)
@ResponseBody
public String getAll(){
System.out.println("user get all ...");
return "{'module': 'user get all'}";
}
}
测试结果:
查询所有用户:
查询 id 为 1 的用户:
新增用户:
注意:新增用户时我们需要给控制器方法传递一个 User 类型创参数,因为需要使用 jackson 把我们的 json 转为 java 对象,这里要求该对象必须要有无参构造器,不然会报错!
删除用户:
修改用户:
1.2.3、请求参数总结
到现在我们共学习了三种请求参数的传递方法:
- @RequestBody
- @RequestParam(用的比较少)
- @PathVariable
1.3、RESTful 快速开发
这一节我们来对上面的代码进行简化开发,比如所有的控制器方法的前缀都是 "/users" ,所有的控制器方法上面都有一个 @ResponseBody (因为我们不需要返回页面)。
1.3.1、统一路径前缀
上一节使用 REST 风格后,我们发现所有的请求路径的前缀都是 "/users" + 路径变量,所以既然都有一个前缀 "/users" ,那么为什么不可以都踢出来呢:
@RequestMapping("/users")
public class UserController {
// ...
}
这样,对于那些在 REST 风格下路径本来就是 模块名+s("/users")的方法,直接就可以直接省去 @RequestMapping 中的 value 属性,对于请求路径包含路径变量的方法,可以省去前缀,就像这样:
@RequestMapping(value = "/{id}",method = RequestMethod.DELETE)
public String delete(@PathVariable Integer id){
System.out.println("user deleted ... user id => " + id);
return "{'module': 'user delete'}";
}
1.3.2、@RestController
从上一节我们的代码中可以发现,控制器类的方法都带有一个 @ResponseBody ,这是因为因为我们不需要返回页面。所以这里我们可以直接简化把它提到控制器类上面:
@Controller
@ResponseBody
@RequestMapping("/users")
public class UserController {
// ...
}
但是 SpringMVC 还是觉得不够简化,它又提供了一个注解 @RestController ,它就相当于同时包含了 @ResponseBody 和 @Controller 两个注解,毕竟控制器类上面肯定是由 @Controller 注解的。所以以后我们直接这么写就行了:
@RestController
@RequestMapping("/users")
public class UserController {
// ...
}
1.3.2、标准请求动作注解
经过上一步的简化,我们的 UserController 现在变成了这样:
@RestController
@RequestMapping("/users")
public class UserController {
@RequestMapping(method = RequestMethod.POST)
public String save(@RequestBody User user){
System.out.println("user save, name = " + user.getName() + " , age = " + user.getAge());
return "{'module':'user save'}";
}
@RequestMapping(value = "/{id}",method = RequestMethod.DELETE)
public String delete(@PathVariable Integer id){
System.out.println("user deleted ... user id => " + id);
return "{'module': 'user delete'}";
}
@RequestMapping(method = RequestMethod.PUT)
public String update(@RequestBody User user){
System.out.println("user deleted ... user => " + user.toString());
return "{'module': 'user update'}";
}
@RequestMapping(value = "/{id}",method = RequestMethod.GET)
public String getById(@PathVariable Integer id){
System.out.println("user get ... user id => " + id);
return "{'module': 'user get'}";
}
@RequestMapping(method = RequestMethod.GET)
public String getAll(){
System.out.println("user get all ...");
return "{'module': 'user get all'}";
}
}
现在看起来还是很冗余,我们可以对这些方法的请求行为进行进一步简化,现在这么长的参数太扎眼了,现在我们的代码就变成了这样(把 value = "/{param}" 和 method = RequestMethpd.X 合并到一个注解中):
@RestController
@RequestMapping("/users")
public class UserController {
@PostMapping
public String save(@RequestBody User user){
System.out.println("user save, name = " + user.getName() + " , age = " + user.getAge());
return "{'module':'user save'}";
}
@DeleteMapping("/{id}")
public String delete(@PathVariable Integer id){
System.out.println("user deleted ... user id => " + id);
return "{'module': 'user delete'}";
}
@PutMapping
public String update(@RequestBody User user){
System.out.println("user deleted ... user => " + user.toString());
return "{'module': 'user update'}";
}
@GetMapping("/{id}")
public String getById(@PathVariable Integer id){
System.out.println("user get ... user id => " + id);
return "{'module': 'user get'}";
}
@GetMapping
public String getAll(){
System.out.println("user get all ...");
return "{'module': 'user get all'}";
}
}
显然一下子变得清新脱俗!舒服多了。
1.4、基于 RESTful 页面数据交互
实体类:
package com.lyh.domain;
public class Book {
private Integer id;
private String type;
private String name;
private String description;
public Book(){}
public Book(Integer id, String type, String name, String description) {
this.id = id;
this.type = type;
this.name = name;
this.description = description;
}
@Override
public String toString() {
return "Book{" +
"id=" + id +
", type='" + type + '\'' +
", name='" + name + '\'' +
", description='" + description + '\'' +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
SpringMVC 配置类:
@Configuration
@ComponentScan("com.lyh.controller") // 扫描
@EnableWebMvc // 开启 json 转对象的功能等各种格式转换
public class SpringMvcConfig {
}
Sevlet 容器配置类:
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
protected String[] getServletMappings() {
return new String[]{"/"};
}
// 乱码处理
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
return new Filter[]{filter};
}
}
BookController 控制器类:
@RestController
@RequestMapping("/books")
public class BookController {
@PostMapping
public String save(@RequestBody Book book){
System.out.println("book save ... book => " + book.toString());
return "{'module':'book save'}";
}
@GetMapping
public List<Book> getAll(){
List<Book> list = new ArrayList<Book>();
list.add(new Book(1,"编程","《图解Spark》","大数据技术"));
list.add(new Book(2,"小说","《黄金时代》","王小波著作"));
return list;
}
}
测试:
导入静态资源后访问 localhost:85/pages/boos.html 发现报错找不到映射:No Mapping for GET /pages/books.html 这是因为我们现在的请求都被 SpringMVC 拦截走了,其实这些静态资源应该是由 Tomcat 处理的。
1.4.1、放行非 SpringMVC 请求
上面的问题是对于静态资源的访问,本该是由 Tomcat 处理,但是前面我们在 Servlet 配置类中设置了所有请求都由 SpringMVC 来响应。所以,现在我们需要通过添加一个配置类来实现放行静态资源:
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
// 当访问 /pages/下的资源时,走 /pages 目录下的内容
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
}
}
然后,我们需要让 SpringMVC 去加载这个新的配置类:
这样就可以访问到了: