SSM
Mybatis、Spring和SpringMVC这三个框架整合在一起完成业务功能开发
文章目录
- SSM
- 5.1 流程
- 5.2 详细步骤
- 5.2.1 基本配置
- 5.2.2 功能模块开发
- 5.2.3 测试
- 5.2.3.1 单元测试
- 5.2.3.2 PostMan测试
- 5.3 统一结果封装
- 5.3.1 概念
- 5.3.2 实现
- 5.4 统一异常处理
- 5.4.1 异常处理器的使用
- 5.4.2 项目异常处理方案
- 5.4.2.1 异常分类
- 5.4.2.2 异常解决方案
- 5.4.2.3 具体实现
- 5.4.2.4 小结
- 5.5 拦截器
- 5.5.1 拦截器概念
- 5.5.2 入门案例
- 5.5.3 拦截器参数
- 5.5.3.1 前置处理方法
- 5.5.3.2 后置处理方法
- 5.5.3.3 完成处理方法
- 5.5.4 拦截器链配置(配置多个拦截器)
5.1 流程
(1) 创建工程
- 创建一个Maven的web工程
- pom.xml添加SSM需要的依赖jar包
- 编写Web项目的入口配置类,实现
AbstractAnnotationConfigDispatcherServletInitializer
重写以下方法- getRootConfigClasses():返回Spring的配置类→需要SpringConfig配置类
- getServletConfigClasses() :返回SpringMVC的配置类→需要SpringMvcConfig配置类
- getServletMappings() : 设置SpringMVC请求拦截路径规则
- getServletFilters() :设置过滤器,解决POST请求中文乱码问题
(2) SSM整合[重点是各个配置的编写]
- SpringConfig
- 标识该类为配置类 @Configuration
- 扫描Service所在的包 @ComponentScan
- 在Service层要管理事务 @EnableTransactionManagement
- 读取外部的properties配置文件 @PropertySource
- 整合Mybatis需要引入Mybatis相关配置类 @Import
- 第三方数据源配置类 JdbcConfig
- 构建DataSource数据源,DruidDataSouroce,需要注入数据库连接四要素, @Bean @Value
- 构建平台事务管理器,DataSourceTransactionManager,@Bean
- Mybatis配置类 MybatisConfig
- 构建SqlSessionFactoryBean并设置别名扫描与数据源,@Bean
- 构建MapperScannerConfigurer并设置DAO层的包扫描
- 第三方数据源配置类 JdbcConfig
- SpringMvcConfig
- 标识该类为配置类 @Configuration
- 扫描Controller所在的包 @ComponentScan
- 开启SpringMVC注解支持 @EnableWebMvc
(3) 功能模块[与具体的业务模块有关]
- 创建数据库表
- 根据数据库表创建对应的模型类
- 通过Dao层完成数据库表的增删改查(接口+自动代理)
- 编写Service层[Service接口+实现类]
- @Service
- @Transactional
- 整合Junit对业务层进行单元测试
- @RunWith
- @ContextConfiguration
- @Test
- 编写Controller层
- 接收请求 @RequestMapping @GetMapping @PostMapping @PutMapping @DeleteMapping
- 接收数据 简单、POJO、嵌套POJO、集合、数组、JSON数据类型
- @RequestParam
- @PathVariable
- @RequestBody
- 转发业务层
- @Autowired
- 响应结果
- @ResponseBody
5.2 详细步骤
5.2.1 基本配置
步骤1: 创建Maven的web项目
步骤2: 添加依赖
常用pom.xml配置
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.itheima</groupId><artifactId>springmvc_08_ssm</artifactId><version>1.0-SNAPSHOT</version><packaging>war</packaging><dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.2.10.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.2.10.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.2.10.RELEASE</version></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.6</version></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>1.3.0</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.47</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.16</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><scope>provided</scope></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.9.0</version></dependency></dependencies><build><plugins><plugin><groupId>org.apache.tomcat.maven</groupId><artifactId>tomcat7-maven-plugin</artifactId><version>2.1</version><configuration><port>80</port><path>/</path></configuration></plugin></plugins></build>
</project>
步骤3:创建项目包结构(各个包的作用)
- config目录存放的是相关的配置类
- controller编写的是Controller类
- dao存放的是Dao接口,因为使用的是Mapper接口代理方式,所以没有实现类包
- service存的是Service接口,impl存放的是Service实现类
- resources:存入的是配置文件,如Jdbc.properties
- webapp:目录可以存放静态资源
- test/java:存放的是测试类
步骤4:创建SpringConfig配置类
@Configuration
@ComponentScan({"com.itheima.service"})
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MyBatisConfig.class})
@EnableTransactionManagement
public class SpringConfig {
}
步骤5:创建JdbcConfig配置类,并注入到Spring容器当中
public class JdbcConfig {@Value("${jdbc.driver}")private String driver;@Value("${jdbc.url}")private String url;@Value("${jdbc.username}")private String username;@Value("${jdbc.password}")private String password;@Beanpublic DataSource dataSource(){DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName(driver);dataSource.setUrl(url);dataSource.setUsername(username);dataSource.setPassword(password);return dataSource;}@Beanpublic PlatformTransactionManager transactionManager(DataSource dataSource){DataSourceTransactionManager ds = new DataSourceTransactionManager();ds.setDataSource(dataSource);return ds;}
}
步骤6:创建MybatisConfig配置类
public class MyBatisConfig {@Beanpublic SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();factoryBean.setDataSource(dataSource);factoryBean.setTypeAliasesPackage("com.itheima.domain");return factoryBean;}@Beanpublic MapperScannerConfigurer mapperScannerConfigurer(){MapperScannerConfigurer msc = new MapperScannerConfigurer();msc.setBasePackage("com.itheima.dao");return msc;}
}
步骤7:创建jdbc.properties
在resources下提供jdbc.properties,设置数据库连接四要素
步骤8:创建SpringMVC配置类
@Configuration
@ComponentScan("com.itheima.controller")
@EnableWebMvc
public class SpringMvcConfig {
}
步骤9:创建Web项目入口配置类
public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer {//加载Spring配置类protected Class<?>[] getRootConfigClasses() {return new Class[]{SpringConfig.class};}//加载SpringMVC配置类protected Class<?>[] getServletConfigClasses() {return new Class[]{SpringMvcConfig.class};}//设置SpringMVC请求地址拦截规则protected String[] getServletMappings() {return new String[]{"/"};}//设置post请求中文乱码过滤器@Overrideprotected Filter[] getServletFilters() {CharacterEncodingFilter filter = new CharacterEncodingFilter();filter.setEncoding("utf-8");return new Filter[]{filter};}
}
5.2.2 功能模块开发
步骤1:创建数据库及表
步骤2:编写模型类(domain)
步骤3:编写Dao接口(SQL语句)
步骤4:编写Service接口和实现类 [@Transactional注解、@Service注解(包括@Autowired注解)]
接口对象标注@Autowired会报错,但在程序运行的时候,代理对象就会被创建,框架会使用DI进行注入,程序运行无影响。
步骤5:编写Contorller类
5.2.3 测试
5.2.3.1 单元测试
步骤1:新建测试类
步骤2:注入Service类
步骤3:编写测试方法
例如:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class BookServiceTest {@Autowiredprivate BookService bookService;@Testpublic void testGetById(){Book book = bookService.getById(1);System.out.println(book);}@Testpublic void testGetAll(){List<Book> all = bookService.getAll();System.out.println(all);}}
5.2.3.2 PostMan测试
略
5.3 统一结果封装
5.3.1 概念
如何将将返回结果的数据进行统一?
引入:
Controller层中存在多种返回对象
在Controller层增删改返回给前端的是boolean类型数据
在Controller层查询单个返回给前端的是对象
在Controller层查询所有返回给前端的是集合对象
如何将将返回结果的数据进行统一?
- 为了封装返回的结果数据:创建结果模型类,封装数据到data属性中
- 为了封装返回的数据是何种操作及是否操作成功:封装操作结果到code属性中
- 操作失败后为了封装返回的错误信息:封装特殊消息到message(msg)属性中
根据分析,我们可以设置统一数据返回结果类,
public class Result{private Object data;private Integer code;private String msg;
}
注意:Result类名及类中的字段并不是固定的,可以根据需要自行增减提供若干个构造方法,方便操作。
5.3.2 实现
步骤1:创建Result类
public class Result {//描述统一格式中的数据private Object data;//描述统一格式中的编码,用于区分操作,可以简化配置0或1表示成功失败private Integer code;//描述统一格式中的消息,可选属性private String msg;public Result() {}//构造方法是方便对象的创建public Result(Integer code,Object data) {this.data = data;this.code = code;}//构造方法是方便对象的创建public Result(Integer code, Object data, String msg) {this.data = data;this.code = code;this.msg = msg;}//setter...getter...省略
}
步骤2:定义返回码Code类
//状态码
public class Code {public static final Integer SAVE_OK = 20011;public static final Integer DELETE_OK = 20021;public static final Integer UPDATE_OK = 20031;public static final Integer GET_OK = 20041;public static final Integer SAVE_ERR = 20010;public static final Integer DELETE_ERR = 20020;public static final Integer UPDATE_ERR = 20030;public static final Integer GET_ERR = 20040;
}
**注意:**code类中的常量设计也不是固定的,可以根据需要自行增减,例如将查询再进行细分为GET_OK,GET_ALL_OK,GET_PAGE_OK等。
步骤3:修改Controller类的返回值
//统一每一个控制器方法返回值
@RestController
@RequestMapping("/books")
public class BookController {@Autowiredprivate BookService bookService;@PostMappingpublic Result save(@RequestBody Book book) {boolean flag = bookService.save(book);return new Result(flag ? Code.SAVE_OK:Code.SAVE_ERR,flag);}@PutMappingpublic Result update(@RequestBody Book book) {boolean flag = bookService.update(book);return new Result(flag ? Code.UPDATE_OK:Code.UPDATE_ERR,flag);}@DeleteMapping("/{id}")public Result delete(@PathVariable Integer id) {boolean flag = bookService.delete(id);return new Result(flag ? Code.DELETE_OK:Code.DELETE_ERR,flag);}@GetMapping("/{id}")public Result getById(@PathVariable Integer id) {Book book = bookService.getById(id);Integer code = book != null ? Code.GET_OK : Code.GET_ERR;String msg = book != null ? "" : "数据查询失败,请重试!";return new Result(code,book,msg);}@GetMappingpublic Result getAll() {List<Book> bookList = bookService.getAll();Integer code = bookList != null ? Code.GET_OK : Code.GET_ERR;String msg = bookList != null ? "" : "数据查询失败,请重试!";return new Result(code,bookList,msg);}
}
步骤4:获得结果进行处理
- 我们的返回结果就已经能以一种统一的格式返回给前端。
- 前端根据返回的结果,先从中获取
code
,根据code判断,如果成功则取data
属性的值,如果失败,则取msg
中的值做提示。
5.4 统一异常处理
异常的种类及出现异常的原因:
- 框架内部抛出的异常:因使用不合规导致
- 数据层抛出的异常:因外部服务器故障导致(例如:服务器访问超时)
- 业务层抛出的异常:因业务逻辑书写错误导致(例如:遍历业务书写操作,导致索引异常等)
- 表现层抛出的异常:因数据收集、校验等规则导致(例如:不匹配的数据类型间导致异常)
- 工具类抛出的异常:因工具类书写不严谨不够健壮导致(例如:必要释放的连接长期未释放等)
开发的任何一个位置都有可能出现异常,而且这些异常是不能避免的。
思考
-
各个层级均出现异常,异常处理代码书写在哪一层?
所有的异常均抛出到表现层进行处理
-
异常的种类很多,表现层如何将所有的异常都处理到呢?
异常分类
-
表现层处理异常,每个方法中单独书写,代码书写量巨大且意义不强,如何解决?
AOP
SpringMVC已经为我们提供了一套解决方案:异常处理器(集中的、统一的处理项目中出现的异常)
5.4.1 异常处理器的使用
步骤1:创建异常处理器类
//@RestControllerAdvice用于标识当前类为REST风格对应的异常处理器
@RestControllerAdvice
public class ProjectExceptionAdvice {//除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常@ExceptionHandler(Exception.class)public void doException(Exception ex){System.out.println("异常处理...")}
}
确保SpringMvcConfig能够扫描到异常处理器类
步骤2:让程序抛出异常
步骤3:运行程序,测试
此时**如果Controller层抛出异常,异常处理器就可以捕获到**
异常处理器类返回结果给前端(返回Result类)
//@RestControllerAdvice用于标识当前类为REST风格对应的异常处理器
@RestControllerAdvice
public class ProjectExceptionAdvice {//除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常@ExceptionHandler(Exception.class)public Result doException(Exception ex){System.out.println("嘿嘿,异常你哪里跑!")return new Result(666,null,"嘿嘿,异常你哪里跑!");}
}
知识点1:@RestControllerAdvice
名称 | @RestControllerAdvice |
---|---|
类型 | 类注解 |
位置 | Rest风格开发的控制器增强类定义上方 |
作用 | 为Rest风格开发的控制器类做增强 |
**说明:**此注解自带@ResponseBody注解与@Component注解,具备对应的功能
知识点2:@ExceptionHandler
名称 | @ExceptionHandler |
---|---|
类型 | 方法注解 |
位置 | 专用于异常处理的控制器方法上方 |
作用 | 设置指定异常的处理方案,功能等同于控制器方法, 出现异常后终止原始控制器执行,并转入当前方法执行 |
说明:此类方法可以根据处理的异常不同,制作多个方法分别处理对应的异常↓↓↓
5.4.2 项目异常处理方案
因为异常的种类有很多,如果每一个异常都对应一个@ExceptionHandler,那得写多少个方法来处理各自的异常,所以我们在处理异常之前,需要对异常进行一个分类:
5.4.2.1 异常分类
-
业务异常(BusinessException)
-
规范的用户行为产生的异常
-
用户在页面输入内容的时候未按照指定格式进行数据填写,如在年龄框输入的是字符串
-
-
不规范的用户行为操作产生的异常
-
如用户故意传递错误数据
-
-
-
系统异常(SystemException)
- 项目运行过程中可预计但无法避免的异常
- 比如数据库或服务器宕机
- 项目运行过程中可预计但无法避免的异常
-
其他异常(Exception)
-
编程人员未预期到的异常,如:用到的文件不存在
-
将异常分类以后,针对不同类型的异常,要提供具体的解决方案:
5.4.2.2 异常解决方案
- 业务异常(BusinessException)
- 发送对应消息传递给用户,提醒规范操作
- 大家常见的就是提示用户名已存在或密码格式不正确等
- 发送对应消息传递给用户,提醒规范操作
- 系统异常(SystemException)
- 发送固定消息传递给用户,安抚用户
- 系统繁忙,请稍后再试
- 系统正在维护升级,请稍后再试
- 系统出问题,请联系系统管理员等
- 发送特定消息给运维人员,提醒维护
- 可以发送短信、邮箱或者是公司内部通信软件
- 记录日志
- 发消息和记录日志对用户来说是不可见的,属于后台程序
- 发送固定消息传递给用户,安抚用户
- 其他异常(Exception)
- 发送固定消息传递给用户,安抚用户
- 发送特定消息给编程人员,提醒维护(纳入预期范围内)
- 一般是程序没有考虑全,比如未做非空校验等
- 记录日志
5.4.2.3 具体实现
步骤1:自定义异常类
//自定义异常处理器,用于封装异常信息,对异常进行分类
public class SystemException extends RuntimeException{private Integer code;public Integer getCode() {return code;}public void setCode(Integer code) {this.code = code;}public SystemException(Integer code, String message) {super(message);this.code = code;}public SystemException(Integer code, String message, Throwable cause) {super(message, cause);this.code = code;}}//自定义异常处理器,用于封装异常信息,对异常进行分类
public class BusinessException extends RuntimeException{private Integer code;public Integer getCode() {return code;}public void setCode(Integer code) {this.code = code;}public BusinessException(Integer code, String message) {super(message);this.code = code;}public BusinessException(Integer code, String message, Throwable cause) {super(message, cause);this.code = code;}}
- 让自定义异常类继承
RuntimeException
的好处是,后期在抛出这两个异常的时候,就不用在try…catch…或throws了 - 自定义异常类中添加
code
属性的原因是为了更好的区分异常是来自哪个业务的
步骤2:将其他异常包成自定义异常
不在controller层抛出的异常,要如何处理?
具体的包装方式有:
- 方式一:
try{}catch(){}
在catch中重新throw我们自定义异常即可。 - 方式二:直接throw自定义异常即可
上面为了使code
看着更专业些,我们在Code类中再新增需要的属性
//状态码
public class Code {public static final Integer SAVE_OK = 20011;public static final Integer DELETE_OK = 20021;public static final Integer UPDATE_OK = 20031;public static final Integer GET_OK = 20041;public static final Integer SAVE_ERR = 20010;public static final Integer DELETE_ERR = 20020;public static final Integer UPDATE_ERR = 20030;public static final Integer GET_ERR = 20040;public static final Integer SYSTEM_ERR = 50001;public static final Integer SYSTEM_TIMEOUT_ERR = 50002;public static final Integer SYSTEM_UNKNOW_ERR = 59999;public static final Integer BUSINESS_ERR = 60002;
}
步骤3:处理器类中处理自定义异常
//@RestControllerAdvice用于标识当前类为REST风格对应的异常处理器
@RestControllerAdvice
public class ProjectExceptionAdvice {//@ExceptionHandler用于设置当前处理器类对应的异常类型@ExceptionHandler(SystemException.class)public Result doSystemException(SystemException ex){//记录日志//发送消息给运维//发送邮件给开发人员,ex对象发送给开发人员return new Result(ex.getCode(),null,ex.getMessage());}@ExceptionHandler(BusinessException.class)public Result doBusinessException(BusinessException ex){return new Result(ex.getCode(),null,ex.getMessage());}//除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常@ExceptionHandler(Exception.class)public Result doOtherException(Exception ex){//记录日志//发送消息给运维//发送邮件给开发人员,ex对象发送给开发人员return new Result(Code.SYSTEM_UNKNOW_ERR,null,"系统繁忙,请稍后再试!");}
}
对于异常我们就已经处理完成了,不管后台哪一层抛出异常,都会以我们与前端约定好的方式进行返回,前端只需要把信息获取到,根据返回的正确与否来展示不同的内容即可。
5.4.2.4 小结
以后项目中的异常处理方式为:
以后在业务中出现错误,就可以根据code编码来进行信息提示:
例如:
handleEdit() {//发送ajax请求axios.put("/books",this.formData).then((res)=>{//如果操作成功,关闭弹层,显示数据if(res.data.code == 20031){this.dialogFormVisible4Edit = false;this.$message.success("修改成功");}else if(res.data.code == 20030){this.$message.error("修改失败");}else{this.$message.error(res.data.msg);}}).finally(()=>{this.getAll();});
}
handleDelete(row) {//1.弹出提示框this.$confirm("此操作永久删除当前数据,是否继续?","提示",{type:'info'}).then(()=>{//2.做删除业务axios.delete("/books/"+row.id).then((res)=>{if(res.data.code == 20021){this.$message.success("删除成功");}else{this.$message.error("删除失败");}}).finally(()=>{this.getAll();});}).catch(()=>{//3.取消删除this.$message.info("取消删除操作");});
}
5.5 拦截器
5.5.1 拦截器概念
当拦截器的配置为/*
时
(1)浏览器发送一个请求会先到Tomcat的web服务器
(2)Tomcat服务器接收到请求以后,会去判断请求的是静态资源还是动态资源
(3)如果是静态资源,会直接到Tomcat的项目部署目录下去直接访问
(4)如果是动态资源,就需要交给项目的**后台代码**进行处理
(5)在找到具体的方法之前,我们可以去配置过滤器(可以配置多个),按照顺序进行执行
(6)然后进入到到**中央处理器(SpringMVC中的内容)**,SpringMVC会根据配置的规则进行拦截
(7)如果满足规则,则进行处理,找到其对应的controller类中的方法进行执行,完成后返回结果
(8)如果不满足规则,则不进行处理
(9)这个时候,如果我们需要在每个Controller方法执行的前后添加业务,具体该如何来实现?
这个就是拦截器要做的事。
- 拦截器(Interceptor)是一种动态拦截方法调用的机制,在SpringMVC中动态拦截控制器方法的执行
- 作用:
- 在指定的方法调用前后执行预先设定的代码
- 阻止原始方法的执行
- 总结:拦截器就是用来做增强
看完以后,大家会发现
- 拦截器和过滤器在作用和执行顺序上也很相似
所以这个时候,就有一个问题需要思考:
拦截器和过滤器之间的区别是什么?
- 归属不同:Filter属于Servlet技术,Interceptor属于SpringMVC技术
- 拦截内容不同:Filter对所有访问进行增强,Interceptor仅针对SpringMVC的访问进行增强
5.5.2 入门案例
环境准备:常见的SSM环境
拦截器开发:
步骤1:创建拦截器类
让类实现HandlerInterceptor接口,重写接口中的三个方法。
@Component
//定义拦截器类,实现HandlerInterceptor接口
//注意当前类必须受Spring容器控制
public class ProjectInterceptor implements HandlerInterceptor {@Override//原始方法调用前执行的内容public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("preHandle...");return true;}@Override//原始方法调用后执行的内容public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("postHandle...");}@Override//原始方法调用完成后执行的内容public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("afterCompletion...");}
}
拦截器中的preHandler
方法,如果返回true,则代表放行,会执行原始Controller类中要请求的方法,如果返回false,则代表拦截,后面的就不会再执行了。
**注意:**拦截器类要被SpringMVC容器扫描到。
回顾:@Component注解是什么?
在Spring MVC中,
@Component
注解是Spring框架中用于标识一个类为Spring容器管理的组件的注解之一。具体来说,@Component
注解是通用的,它表明被注解的类会被Spring自动扫描,并注册为Spring应用上下文中的一个bean。除了
@Component
注解,Spring还提供了一系列类似的注解,如@Service
、@Repository
和@Controller
,它们分别用于标识服务类、数据访问类和控制器类。这些注解的目的是为了更加明确地指定类的角色,但它们都是@Component
注解的特殊化,最终都会被Spring容器识别并管理。
步骤2:配置拦截器类
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {@Autowiredprivate ProjectInterceptor projectInterceptor;@Overrideprotected void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");}@Overrideprotected void addInterceptors(InterceptorRegistry registry) {//配置拦截器registry.addInterceptor(projectInterceptor).addPathPatterns("/books" );}
}
这段配置表示什么?
addResourceHandlers(ResourceHandlerRegistry registry)
: 该方法配置静态资源的处理,指定了静态资源的路径和位置。在这里,/pages/**
是 URL 中的路径模式,/pages/
是实际存放静态资源的位置。addInterceptors(InterceptorRegistry registry)
: 该方法配置拦截器。在这里,registry.addInterceptor(projectInterceptor)
表示将名为projectInterceptor
的拦截器添加到配置中。然后,.addPathPatterns("/books")
表示只拦截路径为 “/books” 的请求。总体而言,这段配置的效果是:
- 配置了静态资源的处理,使得路径
/pages/**
下的静态资源可以被访问。- 配置了一个拦截器
projectInterceptor
,该拦截器只拦截路径为 “/books” 的请求。拦截器的具体逻辑和实现应该在projectInterceptor
类中定义。
步骤3:SpringMVC添加SpringMvcSupport包扫描
@Configuration
@ComponentScan({"com.itheima.controller","com.itheima.config"})
@EnableWebMvc
public class SpringMvcConfig{}
步骤4:运行程序测试
步骤5: 修改(添加)拦截规则
如果想要访问http://localhost:8080/12/books/100
之类的,就需要拦截器将其拦截
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {@Autowiredprivate ProjectInterceptor projectInterceptor;@Overrideprotected void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");}@Overrideprotected void addInterceptors(InterceptorRegistry registry) {//配置拦截器registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*" );}
}
这个时候,如果再次访问http://localhost:8080/12/books/100
,拦截器就会被执行。
步骤6:简化SpringMvcSupport的编写
SpringMvcSupport类的编写可以被简化掉
@Configuration
@ComponentScan({"com.itheima.controller"})
@EnableWebMvc
//实现WebMvcConfigurer接口可以简化开发,但具有一定的侵入性
public class SpringMvcConfig implements WebMvcConfigurer {@Autowiredprivate ProjectInterceptor projectInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {//配置多拦截器registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*");}
}
拦截器的执行流程:
当有拦截器后,请求会先进入preHandle方法,
-
如果方法返回true,则放行继续执行后面的handle[controller的方法]和后面的方法
-
如果返回false,则直接跳过后面方法的执行。
5.5.3 拦截器参数
5.5.3.1 前置处理方法
原始方法之前运行preHandle
public boolean preHandle(HttpServletRequest request,HttpServletResponse response,Object handler) throws Exception {System.out.println("preHandle");return true;
}
-
request:请求对象
使用request对象可以获取请求数据中的内容,如获取请求头的
Content-Type
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String contentType = request.getHeader("Content-Type");System.out.println("preHandle..."+contentType);return true; }
-
response:响应对象
-
handler:被调用的处理器对象,本质上是一个方法对象,对反射中的Method对象进行了再包装;通过这个对象,可以知道将要访问哪一个方法
使用handler参数,可以获取方法的相关信息
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {HandlerMethod hm = (HandlerMethod)handler;String methodName = hm.getMethod().getName();//可以获取方法的名称System.out.println("preHandle..."+methodName);return true; }
演示结果:
5.5.3.2 后置处理方法
原始方法运行后运行,如果原始方法被拦截,则不执行
public void postHandle(HttpServletRequest request,HttpServletResponse response,Object handler,ModelAndView modelAndView) throws Exception {System.out.println("postHandle");
}
前三个参数和上面的是一致的。
modelAndView:如果处理器执行完成具有返回结果,可以读取到对应数据与页面信息,并进行调整
因为咱们现在都是返回json数据,所以该参数的使用率不高。
5.5.3.3 完成处理方法
拦截器最后执行的方法,无论原始方法是否执行
public boolean preHandle(HttpServletRequest request,HttpServletResponse response,Object handler) throws Exception {System.out.println("preHandle");return true;
}
前三个参数与上面的是一致的。
ex:如果处理器执行过程中出现异常对象,可以针对异常情况进行单独处理
因为我们现在已经有全局异常处理器类,所以该参数的使用率也不高。
小结:
最常用的是preHandle, 在这个方法中可以通过返回值来决定是否要进行放行,我们可以把业务逻辑放在该方法中,如果满足业务则返回true放行,不满足则返回false拦截。
5.5.4 拦截器链配置(配置多个拦截器)
目前,我们在项目中只添加了一个拦截器,如果有多个,该如何配置?配置多个后,执行顺序是什么?
步骤1:创建拦截器类
@Component
public class ProjectInterceptor2 implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("preHandle...222");return false;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("postHandle...222");}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("afterCompletion...222");}
}
步骤2:配置拦截器类
在SpringMvcConfig类中
@Configuration
@ComponentScan({"com.itheima.controller"})
@EnableWebMvc
//实现WebMvcConfigurer接口可以简化开发,但具有一定的侵入性
public class SpringMvcConfig implements WebMvcConfigurer {@Autowiredprivate ProjectInterceptor projectInterceptor;@Autowiredprivate ProjectInterceptor2 projectInterceptor2;@Overridepublic void addInterceptors(InterceptorRegistry registry) {//配置多拦截器registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*");registry.addInterceptor(projectInterceptor2).addPathPatterns("/books","/books/*");}
}
步骤3: 观察执行顺序
拦截器执行的顺序是和配置顺序有关。
- 当配置多个拦截器时,形成拦截器链
- 拦截器链的运行顺序参照拦截器添加顺序为准
- 当拦截器中出现对原始处理器的拦截,后面的拦截器均终止运行
- 当拦截器运行中断,仅运行配置在前面的拦截器的afterCompletion操作
preHandle:与配置顺序相同,必定运行
postHandle:与配置顺序相反,可能不运行
afterCompletion:与配置顺序相反,可能不运行。
这个顺序不太好记,最终只需要把握住一个原则即可: 以最终的运行结果为准
如:上述的最后运行结果为