前言
SpringMVC学习笔记
利用Maven创建Web项目
传送门
添加依赖
pom.xml1 2 3 4 5 6 7 8 9 10 11
| <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.10.RELEASE</version> </dependency>
|
定义Controller类
src/main/java/com/controller/UserController.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package com.controller;
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody;
@Controller public class UserController {
@RequestMapping("/method") @ResponseBody public String method() { return "{'key':'value'}"; } }
|
创建SpringMVC容器配置类
- 将Controller类交给SpringMVC容器管理
src/main/java/com/conf/SpringMvcConfig.java1 2 3 4 5 6 7 8 9
| package com.conf;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration;
@Configuration @ComponentScan("com.controller") public class SpringMvcConfig { }
|
创建Servlet配置类
继承子类(用于简化开发)
- 创建Servlet配置类,继承AbstractAnnotationConfigDispatcherServletInitializer类,加载SpringMVC的配置类
src/main/java/com/conf/ServletContainersInitConfig.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package com.conf;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class<?>[] getRootConfigClasses() { return new Class[]{SpringConfig.class}; }
protected Class<?>[] getServletConfigClasses() { return new Class[]{SpringMvcConfig.class}; }
protected String[] getServletMappings() { return new String[]{"/"}; } }
|
继承父类(用于学习底层)
- 创建Servlet配置类,继承AbstractDispatcherServletInitializer类,加载SpringMVC的配置类
src/main/java/com/conf/ServletContainersInitConfig.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package com.conf;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer;
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
protected WebApplicationContext createServletApplicationContext() { AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext(); applicationContext.register(SpringMvcConfig.class); return applicationContext; }
protected String[] getServletMappings() { return new String[]{"/"}; }
protected WebApplicationContext createRootApplicationContext() { return null; } }
|
放行静态页面访问请求
创建SpringMvcSupport配置类
- 继承
WebMvcConfigurationSupport类,重写addResourceHandlers()方法和addInterceptors()方法
addResourceHandler("/pages/**", "/pages/"):当访问的资源路径为/pages/**时,访问静态路径/pages/下的资源
src/main/java/com/conf/SpringMvcSupport.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package com.conf;
import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
@Configuration public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Override protected void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/pages/**", "/pages/"); registry.addResourceHandler("/css/**", "/pages/"); registry.addResourceHandler("/js/**", "/pages/"); } }
|
在SpringMVC容器配置类添加包扫描范围
src/main/java/com/conf/SpringMvcConfig.java1 2 3 4 5 6 7 8 9 10
| package com.conf;
import org.springframework.context.annotation.ComponentScan; import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@Configuration @ComponentScan({"com.controller", "com.conf"}) @EnableWebMvc public class SpringMvcConfig { }
|
与Spring容器整合
创建Spring容器配置类
- 因为Controller类已经交给了SpringMVC容器管理,所以在Spring容器配置类中排除对Controller的bean管理
@ComponentScan():指定包扫描
value = "":指定扫描的包
excludeFilters = @ComponentScan.Filter():指定排除过滤器
type:过滤器类型
FilterType.ANNOTATION:根据注解过滤
FilterType.REGEX:根据正则表达式过滤
FilterType.CUSTOM:根据用户自定义方式过滤
classes:如果过滤类型是根据注解过滤,可以通过classes指定过滤的注解类型
src/main/java/com/conf/SpringConfig.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package com.conf;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; import org.springframework.stereotype.Controller;
@Configuration @ComponentScan( value = "com", excludeFilters = @ComponentScan.Filter( type = FilterType.ANNOTATION, classes = Controller.class ) ) public class SpringConfig {
}
|
不将SpringMvcConfig类作为配置类
- 因为Controller已经交给了SpringMVC容器管理,为了防止Spring容再次管理Controller,需要注释SpringMVC容器配置类中的
@Configuration注解
src/main/java/com/conf/SpringMvcConfig.java1 2 3 4 5 6 7 8
| package com.conf;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan("com.controller") public class SpringMvcConfig { }
|
让tomcat服务器启动时加载Spring容器
src/main/java/com/conf/ServletContainersInitConfig.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package com.conf;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer;
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
protected WebApplicationContext createServletApplicationContext() { AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext(); applicationContext.register(SpringMvcConfig.class); return applicationContext; }
protected String[] getServletMappings() { return new String[]{"/"}; }
protected WebApplicationContext createRootApplicationContext() { AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext(); applicationContext.register(SpringConfig.class); return applicationContext; } }
|
过滤器
解决POST请求的中文乱码
- 在Servlet配置类中添加
getServletFilters()过滤器配置方法,利用过滤器解决POST请求的中文乱码
src/main/java/com/conf/ServletContainersInitConfig.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| package com.conf;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.filter.CharacterEncodingFilter; import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer;
import javax.servlet.Filter;
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
protected WebApplicationContext createServletApplicationContext() { AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext(); applicationContext.register(SpringMvcConfig.class); return applicationContext; }
protected String[] getServletMappings() { return new String[]{"/"}; }
protected WebApplicationContext createRootApplicationContext() { AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext(); applicationContext.register(SpringConfig.class); return applicationContext; }
@Override protected Filter[] getServletFilters() { CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter(); characterEncodingFilter.setEncoding("UTF-8"); return new Filter[]{characterEncodingFilter}; } }
|
开启JSON请求转换对象接收
- 在SpringMVC配置类上添加
@EnableWebMvc注解
src/main/java/com/conf/SpringMvcConfig.java1 2 3 4 5 6 7 8 9
| package com.conf;
import org.springframework.context.annotation.ComponentScan; import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@ComponentScan("com.controller") @EnableWebMvc public class SpringMvcConfig { }
|
处理请求
处理任意方法请求
1 2 3 4 5
| @RequestMapping("/") public String method() { ... return ""; }
|
处理指定方法请求
1 2 3 4 5
| @RequestMapping(value = "/", method = RequestMethod.GET) public String method() { ... return ""; }
|
1 2 3 4 5
| @GetMapping public String method() { ... return ""; }
|
获取请求参数
获取query请求参数
- 通过
@RequestParam("")标注的形参为query请求参数
- 如果形参没有注解标注,则默认为query请求参数
基本类型
@RequestParam(""):指定请求中的参数名,如果请求参数名与形参名相同,可以省略
value = "":指定请求参数名
required = true:是否为必传的参数
defaultValue = "":指定参数默认值
1 2 3 4 5
| @RequestMapping("/") public String method(@RequestParam("") int id, String name) { ... return ""; }
|
实体类
- 首先定义一个实体类,实体类中的属性名与请求参数名保持相同,并定义相关get、set、toString方法
src/main/java/com/pojo/User.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package com.pojo;
public class User { Integer id; String name;
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; } }
|
1 2 3 4 5
| @RequestMapping("/") public String method(User user) { ... return ""; }
|
数组
- 当相同参数名的请求值有多个的时候,可以使用数组进行接收
- 也可以使用字符串类型强制接收所有参数值,默认使用逗号分隔
1 2 3 4 5
| @RequestMapping("/") public String method(String[] name) { ... return ""; }
|
集合
- 当相同参数名的请求值有多个的时候,可以使用集合进行接收
- 接收集合类型的请求时,必须要添加
@RequestParam注解
1 2 3 4 5
| @RequestMapping("/") public String method(@RequestParam List<String> name) { ... return ""; }
|
日期类型
1 2 3 4 5 6
| @RequestMapping("/") public String method(Date date) { System.out.println(date.toLocaleString()); ... return ""; }
|
重新指定日期格式化方式
1 2 3 4 5 6
| @RequestMapping("/") public String method(@DateTimeFormat(pattern = "yyyy-MM-dd") Date date) { System.out.println(date.toLocaleString()); ... return ""; }
|
重新指定所有日期格式化方式
- 添加SpringMVC自定义日期格式转换器,将SpringMVC默认以
/分隔改为以-分隔
1 2 3 4
| @InitBinder public void InitBinder (ServletRequestDataBinder binder) { binder.registerCustomEditor(java.util.Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"), true)); }
|
获取body请求参数
- 通过
@RequestBody标注的形参为body请求参数
实体类
src/main/java/com/pojo/User.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package com.pojo;
public class User { Integer id; String name;
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; } }
|
1 2 3 4 5
| @RequestMapping("/") public String method(@RequestBody User user) { ... return ""; }
|
集合
1 2 3 4 5
| @RequestMapping("/") public String method(@RequestBody List<String> name) { ... return ""; }
|
获取path请求参数
- 通过
@PathVariable标注的形参为path请求参数
- 用
{}作为参数占位符
@PathVariable:在请求路径传参
1 2 3 4 5
| @RequestMapping("/{name}") public String method(@PathVariable String name) { ... return ""; }
|
处理响应
只跳转页面
- 以后我们写代码可以单独写
TemplatePageController用于处理页面跳转
配置前端页面访问路径的前后缀
1 2
| spring.thymeleaf.prefix=classpath:/templates/ spring.thymeleaf.suffix=.html
|
返回字符串(常用)
- 通过返回的字符串(string),根据prefix+string+suffix进行页面跳转
1 2 3 4 5
| @RequestMapping("/") public String method() { ... return ""; }
|
转发和重定向
转发到本项目
1 2 3 4 5
| @RequestMapping("/") public String method() { ... return "index"; }
|
转发到其他位置
1 2 3 4 5
| @RequestMapping("/") public String method() { ... return "forward:/index"; }
|
目标重定向
重定向到站内
- 在SpringMVC中目标重定向也可以通过Model传递参数
- 返回值加
redirect:前缀
1 2 3 4 5
| @RequestMapping("/") public String method() { ... return "redirect:/test"; }
|
重定向到站外
1 2 3 4 5
| @RequestMapping("/") public String method() { ... return "redirect:/http://127.0.0.1:8080/"; }
|
只响应数据
返回Map对象(常用)
1 2 3 4 5 6 7
| @RequestMapping("/") @ResponseBody public Map<String, Object> method() { Map<String, Object> map = new HashMap<>(); map.put(<key>, <value>); return map; }
|
返回引用类型对象
src/main/java/com/pojo/User.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package com.pojo;
public class User { Integer id; String name;
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; } }
|
1 2 3 4 5 6 7 8
| @RequestMapping("/") @ResponseBody public User method() { User user = new User(); user.setId(1); user.setName(""); return user; }
|
返回值为空
- 将数据存储在Map对象,将Map对象转换为json格式字符串
- 通过HttpServletResponse对象,响应数据到客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @RequestMapping("/") public void method(HttpServletResponse response) throws Exception { Map<String, Object> map = new HashMap<>(); map.put(<key>, <value>); ObjectMapper objectMapper = new ObjectMapper(); String jsonStr = objectMapper.writeValueAsString(map); response.setContentType("text/html;charset=utf-8"); response.getWriter().println(jsonStr); }
|
既响应数据,又跳转页面
- 以后我们写代码可以单独写
ModelAndViewController用于同时页面跳转并响应数据
返回ModelAndView对象(常用)
- 返回值会交给DispatcherServletd对象进行处理
- DispatcherServlet对象会调用viewresolver对结果进行解析
- 基于viewresolver找到对应的view页面(prefix+viewname+suffix)
- 将model中的数据填充到view页面上
- 返回一个带有model数据的页面给DispatcherServlet
- DispatcherServlet将带有model数据的页面响应到客户端
1 2 3 4 5 6 7
| @RequestMapping("/") public ModelAndView method() { ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName(""); modelAndView.addObject(<key>, <value>); return modelAndView; }
|
返回字符串
- 通过Model对象传输数据,将数据填充到页面上
- 通过返回的字符串(string),根据prefix+string+suffix进行页面跳转
<key>:键,仅可以字符串类型
<value>:值,可以任意类型
1 2 3 4 5 6
| @RequestMapping("/") public String method(Model model) { model.addAttribute(<key>, <value>); ... return ""; }
|
拦截器
- 拦截器(Interceptor)是一种动态拦截方法调用的机制,在SpringMVC中动态拦截控制器方法的执行
- 作用
- 在指定的方法调用前后执行预先设定的代码
- 终止原始方法的执行
- 拦截器与过滤器的区别
- 归属不同:拦截器属于SpringMVC的技术,过滤器属于Servlet的技术
- 拦截内容不同:拦截器仅对SpringMVC进行增强,过滤器对所有访问进行增强
创建拦截器类
- 如果
preHandle()方法返回false,表示拦截,则原始操作及其之后的操作都不执行
- 如果
preHandle()方法返回true,表示放行,则原始操作及其之后的操作都正常执行
- 形参
handler可以通过反射获取原始操作的对象
src/main/java/com/controller/interceptor/UserInterceptor.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| package com.controller.interceptor;
import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
@Component public class UserInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("preHandle()"); return true; }
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("postHandle()"); }
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("afterCompletion()"); } }
|
配置类
通过SpringMvcSupport配置类完成配置
创建SpringMvcSupport配置类
- 继承
WebMvcConfigurationSupport类,重写addResourceHandlers()方法和addInterceptors()方法
- 通过
addResourceHandlers()方法配置放行的请求范围
- 通过
addInterceptors()方法配置拦截器及其管理的请求范围
addResourceHandler("/pages/**", "/pages/"):当访问的资源路径为/pages/**时,访问静态路径/pages/下的资源
src/main/java/com/conf/SpringMvcSupport.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package com.conf;
import com.interceptor.UserInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
@Configuration public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Autowired private UserInterceptor userInterceptor;
@Override protected void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/pages/**").addResourceLocations("/pages/"); }
@Override protected void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(userInterceptor).addPathPatterns("/users", "/users/*); } }
|
在SpringMVC容器配置类添加包扫描范围
src/main/java/com/conf/SpringMvcConfig.java1 2 3 4 5 6 7 8 9 10
| package com.conf;
import org.springframework.context.annotation.ComponentScan; import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@Configuration @ComponentScan({"com.controller", "com.interceptor", "com.conf"}) @EnableWebMvc public class SpringMvcConfig { }
|
直接通过SpringMvcConfig配置类完成配置(简化)
src/main/java/com/conf/SpringMvcConfig.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package com.conf;
import com.controller.interceptor.UserInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration @ComponentScan({"com.controller"}) @EnableWebMvc public class SpringMvcConfig implements WebMvcConfigurer {
@Autowired private UserInterceptor userInterceptor;
public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(userInterceptor).addPathPatterns("/user", "/users/*"); } }
|
拦截器链
创建多个拦截器类
src/main/java/com/controller/interceptor/UserInterceptor.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package com.controller.interceptor;
import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
@Component public class UserInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("preHandle()"); return true; }
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("postHandle()"); }
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("afterCompletion()"); } }
|
src/main/java/com/controller/interceptor/UserInterceptor2.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package com.controller.interceptor;
import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
@Component public class UserInterceptor2 implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("preHandle()"); return true; }
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("postHandle()"); }
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("afterCompletion()"); } }
|
引入多个配置
src/main/java/com/conf/SpringMvcConfig.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package com.conf;
import com.controller.interceptor.UserInterceptor; import com.controller.interceptor.UserInterceptor2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration @ComponentScan({"com.controller"}) @EnableWebMvc public class SpringMvcConfig implements WebMvcConfigurer {
@Autowired private UserInterceptor userInterceptor;
@Autowired private UserInterceptor2 userInterceptor2; public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(userInterceptor).addPathPatterns("/user", "/users/*"); registry.addInterceptor(userInterceptor2).addPathPatterns("/user", "/users/*"); } }
|
拦截器的执行顺序
- 拦截器执行顺序根据
addInterceptors()方法配置拦截器的顺序而排序
拦截器的执行顺序
当preHandle()返回true时
- 拦截器的
preHandle() -> 原始操作 -> 拦截器的postHandle() -> 拦截器的afterCompletion() -> 运行结束
当preHandle()返回false时
拦截器链的执行顺序
当preHandle()返回true时
- 拦截器1的
preHandle() -> 拦截器2的preHandle() -> 拦截器3的preHandle() -> 原始操作 -> 拦截器3的postHandle() -> 拦截器2的postHandle() -> 拦截器1的postHandle() -> 拦截器3的afterCompletion() -> 拦截器2的afterCompletion() -> 拦截器1的afterCompletion() -> 运行结束
当userInterceptor3的preHandle()返回false时
- 拦截器1的
preHandle() -> 拦截器2的preHandle() -> 拦截器3的preHandle() -> 拦截器2的afterCompletion() -> 拦截器1的afterCompletion() -> 运行结束
完成
参考文献
哔哩哔哩——黑马程序员