目录
1.1 RESTful风格接口介绍
RESTful是目前最流行的 API 设计规范,用于 Web 数据接口的设计。通过不同的HTTP请求方法来实现对资源的CRUD操作,请求URL为"/资源名称/资源标识"的形式,用到的HTTP请求方法如下:
- GET:读取(Read)
- POST:新建(Create)
- PUT:更新(Update)
- PATCH:更新(Update),通常是部分更新
- DELETE:删除(Delete)
举例:
操作 | 普通接口URL | RESTful接口URL |
---|---|---|
查询全部 | /getDevices | (GET) /devices |
查询单个 | /getDevice?id=xxx | (GET) /devices/{id} |
添加 | /addDevice | (POST) /devices |
修改 | /updateDevice | (PUT) /devices/{id} |
删除 | /deleteDevice | (DELETE) /devices/{id} |
通过上面的对比,可以看到RESTful风格接口更加简洁。
关于更为详细的RESTful接口介绍推荐这一篇文章
1.2 编写Controller
Controller的编写非常简单,只需要两个注解+一个处理方法即可。
在controller包下编写UserController类,代码结构如下:
@Controller
public class UserController
{
@PostMapping("/login")
public String login()
{
....
return "success";
}
}
- @Controller表明该类为Spring MVC的一个控制器
- @PostMapping("/login")表示只要是uri为/login的POST请求,就交由该方法处理。等同于@RequestMapping(value = “/login”,method = RequestMethod.POST)
- return "success"表示返回/success界面。
1.3 前端提交数据给后端
1.3.1 获取单个值
编写login.html,代码如下:
<!DOCTYPE html>
<html lang="en">
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>用户登陆</title>
</head>
<body>
<form action="/login" method="POST">
用户名:<input type="text" name="userName"><br>
密码:<input type="password" name="password"><br>
<input type="submit" value="提交">
<input type="reset" value="重置">
</form>
</body>
</html>
UserController代码如下:
@Controller
public class UserController
{
Logger logger = LoggerFactory.getLogger(UserController.class);
@PostMapping("/login")
public String login(@RequestParam(value = "userName") String userName,@RequestParam(value = "password") String password)
{
logger.info("username:" + userName + ",password:" + password);
return "success";
}
}
在这里使用@RequestParam注解单个获取值,并注入到参数列表中。可以看到成功获取到了数据:
注:如果Controller中的变量名和html页面中的变量名一致,可以不加@RequestParam注解,Spring会自动匹配。
public String login(String userName,String password)
{
...
}
1.3.2 表单提交数据到对象
新创建User对象,代码如下:
public class User
{
private String userName;
private String password;
getter().....
setter().....
toString()....
}
修改Controller代码如下:
@PostMapping("/login")
public String login(User user)
{
logger.info(String.valueOf(user));
return "success";
}
值同样被注入进来了
1.3.3 获取PathValue
在前面的RESTful章节,我们看到了类似于这样的URI:devices/{id},那这里的id怎么获取呢?接下来演示一下:
在UserController新创建以下方法:
@GetMapping("/user/{id}")
public String get(@PathVariable("id") Integer id)
{
logger.info("id = " + id);
return "success";
}
访问试一下:
成功打印出了id
@PathVariable(“id”) Integer id的作用是将uri里的{id}绑定到变量id中。
1.3.4 获取前端提交的JSON数据到对象
现在很多公司web开发施行前后端分离策略,前后端之间通信的数据载体一般是JSON字符串。Spring MVC对此做了很好的支持。还是以提交User数据为例,JSON字符串可能是这样写的:
{
"userName":"admin",
"password":"123456"
}
Controller改写代码如下:
@PostMapping("/login")
public String login(@RequestBody User user)
{
logger.info(String.valueOf(user));
return "success";
}
使用Postman提交数据后,可以看到服务器有日志输出:
@RequestBody修饰User对象的作用是将客户端传递过来的JSON数据自动绑定到该对象中。
1.3.5 数据校验器
Spring MVC支持对客户端传递过来的数据进行JSR303数据校验。关于JSR303可看我前面写的文章:SpringBoot学习篇1[配置文件知多少]的2.3.3节数据校验。
假设我们限定用户名必须为Email,密码不得小于6位。修改User类,定义的校验规则如下:
public class User
{
@Email
private String userName;
@Length(min = 6)
private String password;
......
}
UserController的login方法修改如下:
@PostMapping("/login")
public String login(@Validated User user, BindingResult bindingResult)
{
List<ObjectError> allErrors = bindingResult.getAllErrors();
for (ObjectError error : allErrors)
{
System.out.println(error);
}
if(!allErrors.isEmpty())
return "error";
logger.info(String.valueOf(user));
return "success";
}
@Validation注解表示启用数据校验,BindingResult对象用于存放校验结果。
当输入一个错误的邮箱格式时:
当密码长度小于6时:
出现错误信息时的default message支持自定义,直接指定message的值即可:
@Email(message = "请输入邮箱")
private String userName;
效果如下 :
1.3.6 Controller支持的内部对象
Controller方法默认支持的参数类型有以下4个
- HttpServletRequest
- HttpServletResponse
- HttpSession
- Model
当我们需要用到上述对象时,直接在形参列表中声明此参数即可。比如用户登陆时,一般用HttpSession对象存储当前成功登陆的用户:
@PostMapping("/login")
public String login(@RequestBody User user, HttpSession session)
{
.......
session.setAttribute("user",user);
return "success";
}
还可以直接用HttpServletRequest对象获取html页面提交表单时传递过来的参数:
@PostMapping("/login")
public String login(HttpServletRequest request)
{
String userName = request.getParameter("userName");
String password = request.getParameter("password");
logger.info("username:" + userName + ",password:" + password);
return "success";
}
打印结果:
1.3.7 小结
- @Controller注解声明当前对象为Spring MNV控制器对象
- @RequestMapping、@GetMapping、@PostMapping、@PutMapping…绑定请求和处理方法
- 如果客户端传递过来的属性名与处理请求的方法参数列表的名称一致时,支持自动绑定参数到对象(需要有对应的setter方法)或普通变量中。
- @RequestParam用于将形参变量与前端传递过来的属性绑定
- @PathVariable用于获取RESTful形式接口的资源标识
- @RequestBody用于从JSON字符串中获取值,并自动注入Java Bean
- @Validated用于启用校验,校验结果将存储到BindingResult对象中
1.4 后端传递数据给前端界面
准备工作:在控制器层新创建EmployeeController类,用该类演示后端传递数据给前端。新创建/templates/employee/list.html模板页面,用于显示后端传递给前端的数据。
list.html代码如下:
<!DOCTYPE html>
<html lang="en">
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>员工列表</title>
</head>
<body>
<table border="1px">
<tr>
<td>姓名</td>
<td>工号</td>
<td>工资</td>
</tr>
<tr th:each="employee : ${employeeList}">
<td th:text="${employee.name}"></td>
<td th:text="${employee.id}"></td>
<td th:text="${employee.salary}"></td>
</tr>
</table>
</body>
</html>
1.4.1 利用Model对象传递数据
@GetMapping(value = "/employees")
public String list(Model model)
{
Employee employee1 = new Employee();
Employee employee2 = new Employee();
Employee employee3 = new Employee();
employee1.setter......
employee2.setter......
employee3.setter......
List<Employee> employeeList = new LinkedList<>();
employeeList.add(employee1);
employeeList.add(employee2);
employeeList.add(employee3);
model.addAttribute("employeeList",employeeList);
return "employee/list";
}
通过model.addAttribute方法可以把数据封装到Request对象中,在HTML界面通过变量表达式就可以直接获取到值。
1.4.2 利用Map集合传递数据
利用Map集合传递数据给前端和利用Model对象基本一致。
@GetMapping(value = "/employees")
public String list(Map<String,Object> map)
{
Employee employee1 = new Employee();
Employee employee2 = new Employee();
Employee employee3 = new Employee();
employee1.setter......
employee2.setter......
employee3.setter......
List<Employee> employeeList = new LinkedList<>();
employeeList.add(employee1);
employeeList.add(employee2);
employeeList.add(employee3);
map.put("employeeList",employeeList);
return "employee/list";
}
1.4.3 利用Session对象传递数据
@GetMapping(value = "/employees")
public String list(HttpSession session)
{
Employee employee1 = new Employee();
Employee employee2 = new Employee();
Employee employee3 = new Employee();
employee1.setter......
employee2.setter......
employee3.setter......
List<Employee> employeeList = new LinkedList<>();
employeeList.add(employee1);
employeeList.add(employee2);
employeeList.add(employee3);
session.setAttribute("employeeList",employeeList);
return "employee/list";
}
此时HTML界面代码就要改一下了:
<tr th:each="employee : ${session.employeeList}">
...
</tr>
1.4.4 接口返回JSON对象
将JSON字符串自动注入JavaBean时使用的是@RequestBody注解修饰要注入的对象,如果要返回JSON对象,只需要用@ResponseBody注解修饰该方法,并return要返回的对象即可。
@GetMapping(value = "/employees")
@ResponseBody
public List<Employee> list()
{
Employee employee1 = new Employee();
Employee employee2 = new Employee();
Employee employee3 = new Employee();
employee1.setter......
employee2.setter......
employee3.setter......
List<Employee> employeeList = new LinkedList<>();
employeeList.add(employee1);
employeeList.add(employee2);
employeeList.add(employee3);
return employeeList;
}
使用Postman可以看到接口成功得到返回了数据
1.4.5 数据回显
有时用户输入了数据提交表单后,需要将用户输入的数据回显在界面上。数据回显的本质是将数据绑定到request域中,前端再从request域获取值,这一点其实并不难。提交表单界面获取request域中的值:
<form th:action="@{/employees}" th:method="POST">
姓名:<input type="text" name="name" th:value="${employee.name}"><br>
工号:<input type="text" name="id" th:value="${employee.id}"><br>
工资:<input type="number" name="salary" th:value="${employee.salary}"><br>
<input type="submit" value="提交">
<input type="reset" value="重置">
</form>
绑定数据到request域
@PostMapping("/employees")
public String add(@ModelAttribute(value = "employee") Employee employee)
{
System.out.println(employee);
return "employee/add";
}
加上@ModelAttribute(value = “employee”)注解,会自动将数据绑定到request域中。相当于写了model.addAttribute(“employee”,employee)这一行程序。
1.4.6 小结
- 使用Model对象和Map集合都是将数据放入request域,其生命周期是这次request请求。
- Session生命周期在客户端第一次访问服务器开始,在超时时间过后或服务器手动销毁session对象后结束,前后端数据传递原则上能不使用Session就不使用Session。
- 使用@Controller修饰控制器类,默认情况下请求处理方法返回视图。
- 用@ResponseBody修饰请求处理方法,可以让该方法返回JSON数据。
- 可以直接用@RestController注解或@Controller + @ResponseBody修饰控制器类,这种情况下请求处理方法默认返回JSON数据,@RestController就相当于@Controller + @ResponseBody。
- 数据回显,本质就是将前端送上来的数据再放到request域中,可直接用@ModelAttribute注解。
1.5 扩展Spring MVC功能
Spring Boot为Spring MVC提供了自动配置,可与大多数应用程序完美配合。
自动配置在Spring默认值的基础上添加了以下功能:
- 包含ContentNegotiatingViewResolver和BeanNameViewResolver。
- 支持服务静态资源,包括对WebJars的支持
- 自动注册Converter,GenericConverter和Formatter
- 支持HttpMessageConverters
- 自动注册MessageCodesResolver
- 静态index.html支持
- 定制Favicon支持
- 自动使用ConfigurableWebBindingInitializer
如果想使用Spring Boot MVC功能,并且想要添加其他MVC配置(拦截器、参数转化器、视图控制器等),则可以自定义WebMvcConfigurer,并将其注入Spring IOC容器。
1.5.1 配置视图解析器
步骤:
[1] 自定义配置类
[2] 注入自定义WebMvcConfigurer对象到Spring容器
[3] 自定义WebMvcConfigurer对象重写addViewControllers方法
自定义config.MySpringMvcConfigurer代码如下:
@Configuration
public class MySpringMvcConfigurer {
@Bean
public WebMvcConfigurer webMvcConfigurer()
{
return new WebMvcConfigurer() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("public/login");
}
};
}
}
这段代码的作用是将localhost:8080这个url解析到templates/public/login.html模板页面下。
1.5.2 自定义拦截器
自定义拦截器同样是在自定义的WebMvcConfigurer下做文章。定义拦截器的步骤如下:
[1] 自定义拦截处理器
[2] 在自定义WebMvcConfigurer对象中重写addInterceptors方法。
[1] 自定义拦截处理器interceptor.LoginHandlerInterceptor,代码如下:
public class LoginHandlerInterceptor implements HandlerInterceptor
{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if(request.getSession().getAttribute("loginUser") == null) {
request.setAttribute("msg","没有权限,请先登录");
request.getRequestDispatcher("/").forward(request,response);
return false;
}
return true;
}
}
[2] 在自定义WebMvcConfigurer对象中重写addInterceptors方法
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerInterceptor())
//拦截所有请求
.addPathPatterns("/**")
//不拦截登陆请求
.excludePathPatterns("/","/login")
//springBoot2+之后需要将静态资源文件的访问路径也排除
.excludePathPatterns("/css/*","/img/*","/js/*");
}
处理登陆的Controller如下:
@PostMapping("/login")
public String login(HttpServletRequest request)
{
String userName = request.getParameter("userName");
String password = request.getParameter("password");
logger.info("username:" + userName + ",password:" + password);
User user = new User();
if(userName.equals("admin") && password.equals("123456"))
{
user.setUserName(userName);
user.setPassword(password);
request.getSession().setAttribute("loginUser",user);
return "redirect:/employees";
}
return "redirect:/";
}
登陆成功,直接跳转到员工列表,登陆失败,返回登陆页面。
1.5.3 自定义参数转换器
以获取前端传递过去的时间日期为例
date.html代码
<form method="post" action="/date">
<input type="text" name="date">
<input type="submit" value="提交">
</form>
控制器代码:
@PostMapping("/date")
public String date(@RequestParam("date")Date date)
{
System.out.println(date);
return "success";
}
提交数据
结果是程序报错了:
自定义参数转换器步骤:
1.自定义类实现Converter接口
2.将该类注入Spring IOC容器
代码如下:
@Component
public class MyMessageConverter implements Converter<String, Date> {
@Override
public Date convert(String s) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
return simpleDateFormat.parse(s);
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
}
再次提交数据,可以看到成功解析出来了前端传递过去的时间日期