请描述“SSM整合流程”中各个配置类的作用?
最终目录结构先贴出来:
pom.xml
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>5.2.10.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>5.2.10.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.2.10.RELEASEversion>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.6version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatis-springartifactId>
<version>1.3.0version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.47version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.16version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>3.1.0version>
<scope>providedscope>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>2.9.0version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.26version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.mavengroupId>
<artifactId>tomcat7-maven-pluginartifactId>
<version>2.1version>
<configuration>
<port>80port>
<path>/path>
configuration>
plugin>
plugins>
build>
下面依次创建各个文件
-- 创建ssm_db数据库
CREATE DATABASE IF NOT EXISTS ssm_db CHARACTER SET utf8;
-- 使用ssm_db数据库
USE ssm_db;
-- 创建tbl_book表
CREATE TABLE tbl_book(
id INT PRIMARY KEY AUTO_INCREMENT, -- 图书编号
TYPE VARCHAR(100), -- 图书类型
NAME VARCHAR(100), -- 图书名称
description VARCHAR(100) -- 图书描述
);
-- 添加初始化数据
INSERT INTO tbl_book VALUES(NULL,'计算机理论','Spring实战 第5版','Spring入门经典教材,深入理解Spring原理技术内幕');
INSERT INTO tbl_book VALUES(NULL,'计算机理论','Spring 5核心原理与30个类手写实战','十年沉淀之作,手写Spring精华思想');
INSERT INTO tbl_book VALUES(NULL,'计算机理论','Spring 5设计模式','深入Spring源码剖析,Spring源码蕴含的10大设计模式');
INSERT INTO tbl_book VALUES(NULL,'市场营销','直播就该这么做:主播高效沟通实战指南','李子柒、李佳琦、薇娅成长为网红的秘密都在书中');
INSERT INTO tbl_book VALUES(NULL,'市场营销','直播销讲实战一本通','和秋叶一起学系列网络营销书籍');
INSERT INTO tbl_book VALUES(NULL,'市场营销','直播带货:淘宝、天猫直播从新手到高手','一本教你如何玩转直播的书,10堂课轻松实现带货月入3W+');
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/ssm_db?useSSL=false
jdbc.username=root
jdbc.password=1234
package cn.whu.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
public class JdbcConfig {
//这里可以加载jdbc.properties里的变量
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
//1.定义一个方法获得要管理的对象
//2.添加@Bean,表示当前方法的返回值是一个bean
@Bean
public DataSource dataSource() {
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
//Spring事务管理需要的平台事务管理器对象
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
public class MybatisConfig {
@Bean //IoC容器中有SqlSessionFactory了
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {//@Bean注入引用类型 特别方便 提供参数即可
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
//框架的好处,大部分东西都是默认的,不用设计,那些动态变化设置一下就ok了
ssfb.setTypeAliasesPackage("cn.whu.domain"); //
ssfb.setDataSource(dataSource);//
return ssfb;
}
//帮你造mapper 也就是Dao实现类的 (Service里的Autowired有东西自动注入了)
@Bean //IoC容器中有 所有DaoImpl实现类了
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("cn.whu.dao");//
return msc;
}
}
@Configuration
@ComponentScan({"cn.whu.service"})//有些该springMVC扫描 因此这里配置得细一点
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MyBatisConfig.class})
@EnableTransactionManagement //开启Spring事务管理
public class SpringConfig {
}
@Configuration
@ComponentScan({"cn.whu.controller"})
@EnableWebMvc //很多辅助功能
public class SpringMvcConfig {
}
web容器刚刚启动时 就是加载这个配置类 也即是创建2个容器 A:SpringIOC容器和B:SpringMVCIOC容器
Spring和SpringMVC分别加载各自的Bean
B可以访问A但A不可以访问B
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
// Spring核心配置文件
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
/*web容器启动时 加载这两个配置类 也即是创建2个容器 A:SpringIOC容器和B:SpringMVCIOC容器 Spring和SpringMVC分别加载各自的Bean B可以访问A但A不可以访问B*/
// SpringMVC核心配置文件(SpringMVC的Controller就是我们的Servlet嘛)
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
//配置SpringMVC拦截所有路径
protected String[] getServletMappings() {
return new String[]{"/"};
}
//Post请求乱码处理
@Override //本质也是过滤器 (这里写新的方法,就相当于xml中写新的配置)
protected Filter[] getServletFilters() {
//可以自己写过滤器Filter,但是SpringMVC肯定有内置写好的
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("utf-8");
return new Filter[]{filter};
}
}
整合配置完成了,启动服务器不报错,就OK啦。备份一下
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Book {
private Integer id;
private String type;
private String name;
private String description;
}
接口方法 默认修饰符:public abstract
public interface BookDao {
@Insert("insert into tbl_book(type, name, description) values(#{type},#{name},#{description})")
void save(Book book);
@Update("update tbl_book set TYPE=#{type},NAME=#{name},description=#{description} where id=#{id}")
void update(Book book);
@Delete("delete from tbl_book where id = #{id}")
void delete(Integer id);
@Select("select * from tbl_book where id=#{id}")
Book getById(Integer id);
@Select("select * from tbl_book")
List<Book> getAll();
}
JdbcConfig配置类中配置了PlatformTransactionManager
SpringConfig核心配置类中开启了注解式事务驱动 @EnableTransactionManagement
这里就可以直接一行注解开启事务了
@Transactional //表示所有方法进行事务管理
public interface BookService {
//接口方法 默认修饰符:public abstract 所以不用写public
boolean save(Book book);
boolean update(Book book);
boolean delete(Integer id);
Book getById(Integer id);
List<Book> getAll();
}
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
public boolean save(Book book) {
bookDao.save(book);
return true;//抛出异常的时候 return false
}
public boolean update(Book book) {
bookDao.update(book);
return true;
}
public boolean delete(Integer id) {
bookDao.delete(id);
return true;
}
public Book getById(Integer id) {
return bookDao.getById(id);
}
public List<Book> getAll() {
return bookDao.getAll();
}
}
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private BookService service;
//不返回String了 该返回啥就返回啥 异步交互的好处
@PostMapping
public boolean save(@RequestBody Book book){
return service.save(book);
}
@PutMapping
public boolean update(@RequestBody Book book) {
return service.update(book);
}
@DeleteMapping("/{id}")
public boolean delete(@PathVariable Integer id) {
return service.delete(id);
}
@GetMapping("/{id}")
public Book getById(@PathVariable Integer id) {
return service.getById(id);
}
@GetMapping
public List<Book> getAll() {
return service.getAll();
}
}
开发过程中有两个地方要停下来测试
1)Service开发完毕要停下来用Junit进行(Service)业务层接口测试
2)(Controller)表现层开发完毕要停下来用PostMan进行(Controller)表现层测试
@RunWith(SpringJUnit4ClassRunner.class)//Spring整合Junit专用的类加载器
@ContextConfiguration(classes = {SpringConfig.class})//加载spring核心配置类 把Spring用起来呀
public class BookServiceTest {
@Autowired
private BookService service;
@Test
public void testSave() {
Book book = new Book();
book.setType("计算机");
book.setName("springMVC入门");
book.setDescription("小试牛刀");
service.save(book);
}
@Test
public void testGetById(){
Book book = service.getById(1);
System.out.println(book);
}
@Test
public void testGetAll(){
List<Book> books = service.getAll();
System.out.println(books);
}
}
均能正常查询或者插入,后台没有问题啦~
先启动服务器呀
目前我们表现层响应给客户端的数据有哪几种?
问题:我们表现层增删改方法返回true或者false表示是否成功,getById()方法返回一个json对象,getAll()方法返回一个json对象数组,这里就出现了三种格式的响应结果,极其不利于前端解析。
如果前后端都我们开发,这些格式其实很合理,但是现在前端不归我们写,那这些种类的返回格式得协商到什么时候啊,前后端肯定有一种统一的数据格式规范,不用怎么协商,直接就能看懂。
其实就是前后端人员通信的协议
eg:
解决:我们需要统一响应结果的格式
环境:将springmvc_08_ssm原封不动复制一份为springmvc_09_ssm_result即可
controller包下新建类Result.java
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Result {
private Object data;
private Integer code;
private String msg;
// 加一个双参
public Result(Object data, Integer code) {
this.data = data;
this.code = code;
}
}
注意事项:
Result类中的字段并不是固定的,可以根据需要自行增减
controller包下新建类Code.java
//状态码
public class Code {
// 1 结尾的表示成功
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;
// 0 结尾的表示失败
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
返回值类型全部改成刚刚设计的Result类型
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private BookService service;
//全部统一返回Result,然后再把Result对象自动转成json
@PostMapping
public Result save(@RequestBody Book book) {
boolean flag = service.save(book);
return new Result(flag ? Code.SAVE_OK : Code.SAVE_ERR, flag);
}
@PutMapping
public Result update(@RequestBody Book book) {
boolean flag = service.update(book);
return new Result(flag ? Code.UPDATE_OK : Code.UPDATE_ERR, flag);
}
@DeleteMapping("/{id}")
public Result delete(@PathVariable Integer id) {
boolean flag = service.delete(id);
return new Result(flag ? Code.DELETE_OK : Code.DELETE_ERR, flag);
}
@GetMapping("/{id}")
public Result getById(@PathVariable Integer id) {
Book book = service.getById(id);
Integer code = book != null ? Code.GET_OK : Code.GET_ERR;
String msg = book != null ? "" : "数据查询失败,请重试!";
return new Result(code, book, msg);
}
@GetMapping
public Result getAll() {
List<Book> bookList = service.getAll();
//查询0条也可能成功,可能就是没有那种数据。 但是bookList==null 一定是抛异常失败了
Integer code = bookList != null ? Code.GET_OK : Code.GET_ERR;
String msg = bookList != null ? "" : "数据查询失败,请重试!";
return new Result(code, bookList, msg);
}
}
问题1:项目各个个层级均可能出现异常,异常处理代码书写在哪一层?
思考1:各个层级均出现异常,异常处理代码书写在哪一层?
答1:所有得异常均抛出到表现层进行处理
思考2:异常种类千千万,若在表现层处理异常,每个方法中单独书写,代码书写量巨大且意义不强,如何解决?
答2:AOP思想呀
环境:springmvc_09_ssm_result原封不动复制一份为springmvc_09_ssm_result_exception即可
写在表现层Controller包下(因为是Controller要用啊,写在这里最合适了)
核心: 关键一个注解@RestControllerAdvice 容器里有他就能自动捕获Rest风格的Controller里的异常了
// springMVC扫描的就是"cn.whu.controller"包 所以此注解能被扫描到
@RestControllerAdvice //处理Rest风格开发的Controller
public class ProjectExceptionAdvice {
@ExceptionHandler(Exception.class)//处理哪种类型的异常? Exception.class就统一处理所有种类的异常了
public Result doException(Exception e) {
System.out.println("异常哪里跑? " + e);
return new Result(666, null, "异常哪里跑?");//返回值格式还得符合协议规范
}
}
使用异常处理器之后的效果
也返回了统一格式的数据
名称:@RestControllerAdvice
类型:类注解
位置:Rest风格开发的控制器增强类定义上方
作用:为Rest风格开发的控制器类做增强(就是AOP)
说明:此注解自带@ResponseBody注解与@Component注解,具备对应的功能
请说出项目当前异常的分类以及对应类型异常该如何处理?
新建一个包:exception,在里面写两个自定义异常
//自定义异常处理器,用于封装异常信息,对异常进行分类
@Data
public class SystemException extends RuntimeException {
private Integer 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;
}
}
和上面一模一样 只是类名不同而已,继承的类都一样
//自定义异常处理器,用于封装异常信息,对异常进行分类
@Data
public class BusinessException extends RuntimeException {
private Integer 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;
}
}
controller.Code类里面加一些自定义编码
public class Code {
//之前其他状态码省略没写,以下是新补充的状态码,可以根据需要自己补充
public static final Integer SYSTEM_ERR = 50001;
public static final Integer SYSTEM_TIMEOUT_ERR = 50002;
public static final Integer SYSTEM_UNKNOWN_ERR = 59999;
public static final Integer BUSINESS_ERR = 60002;
}
实际开发肯定不这么干,而是用AOP干
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
//在getById演示触发异常,其他方法省略没有写进来
// 下面代码只是为了模拟异常,所以看起来有点搞笑
public Book getById(Integer id) {
//比如所有异常都被我们分为2类,那么捕获到所有异常转成抛两类自定义异常
//eg1: 模拟业务异常
//将可能出现的异常进行包装,转换成自定义异常
if(id < 0){
throw new BusinessException(Code.BUSINESS_ERR,"请不要用你的技术挑战我的耐性!");
}
//eg2: 模拟系统异常
//将可能出现的异常进行包装,转换成自定义异常
try {
int i = 1/0;
}catch (Exception e){//怎么转换? 就是捕获到一切异常,这里都转换成throw new 自定义异常类(...)
throw new SystemException(Code.SYSTEM_TIMEOUT_ERR,"服务器访问超时,请稍后再试试!",e);
}
return bookDao.getById(id);
}
}
controller.ProjectExceptionAdvice
// springMVC扫描的就是"cn.whu.controller"包 所以此注解能被扫描到
@RestControllerAdvice //处理Rest风格开发的Controller
public class ProjectExceptionAdvice {
// 这里统一拦截到系统异常,并做好处理
@ExceptionHandler(SystemException.class)
public Result doSystemException(SystemException e) {
//记录日志
//发送消息给运维
//发送邮件给开发人员,ex对象发送给开发人员
return new Result(e.getCode(),null,e.getMessage());
//ex.getCode()是(ServiceImpl.getById)方法内throw new SystemException(code,"info",e); 时塞进来的code
}
// 这里统一拦截到业务异常,并做好处理
@ExceptionHandler(BusinessException.class)
public Result doBusinessException(BusinessException e) {
return new Result(e.getCode(),null,e.getMessage());
//ex.getCode()和e.getMessage()是(ServiceImpl.getById)方法内throw new SystemException(code,"info",e); 时塞进来的
}
@ExceptionHandler(Exception.class)//处理哪种类型的异常? Exception.class就统一处理所有种类的异常了
public Result doException(Exception e) {
//记录日志
//发送消息给运维
//发送邮件给开发人员,ex对象发送给开发人员
return new Result(Code.SYSTEM_UNKNOWN_ERR,null,"系统繁忙,请稍后再试!");
//ex.getCode()和e.getMessage()是(ServiceImpl.getById)方法内throw new SystemException(code,"info",e); 时塞进来的
}
}
测试:在postman中发送请求访问getById方法,传递参数-1,得到以下结果:
将springmvc_09_ssm_result_exception复制一份为springmvc_09_ssm_result_exception_page
这次打算加上页面
链接:https://pan.baidu.com/s/1opoo-lQ-DvqOFWN2mYsLSw
提取码:z0ae
复制好直接粘贴到webapp目录下
为了确保静态资源能够被访问到,需要设置静态资源过滤
config包下新建文件SpringMvcSupport.java
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/pages/**")
.addResourceLocations("/pages/");
registry.addResourceHandler("/css/**")
.addResourceLocations("/css/");
registry.addResourceHandler("/js/**")
.addResourceLocations("/js/");
registry.addResourceHandler("/plugins/**")
.addResourceLocations("/plugins/");
}
}
SpringMvcConfig的@ComponentScan要多扫描一个config包了 保证SpringMvcSupport 配置类能被扫描到
其实这两个配置类都可以直接创建成模板的
@Configuration
@ComponentScan({"cn.whu.controller","cn.whu.config"})//config是为了加载SpringMvcSupport类,以放行静态资源
@EnableWebMvc //很多辅助功能 eg: 自动类型转换 自动JSON格式转换
public class SpringMvcConfig {
}
//列表
getAll() {
//发送ajax请求
axios.get("/books").then(resp => {
//console.log(resp.data);
this.dataList = data.data;
})
},
就是这么简单
//弹出添加窗口
handleCreate() {
this.dialogFormVisible = true;
this.resetForm();//否则刚点击完并弹窗时表单会自动填充上次的数据
},
//重置表单
resetForm() {
this.formData = {}
},
//添加
handleAdd() {
axios.post("/books",this.formData).then(resp => {
if(resp.data.code==20011){
this.dialogFormVisible=false;
this.$message.success("添加成功");
}else if(resp.data.code==20010){
this.$message.error("添加失败");
}else {
this.$message.error(resp.data.msg);
}
}).finally(()=>{//这里也可以写finally
this.getAll();
})
},
先把bookDao接口里的增删改方法返回值改成int
再让service按照影响行数返回true或者false
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
//增删改的方法判断了影响的行数是否大于0,而不是固定返回true
public boolean save(Book book) {
return bookDao.save(book) > 0;
}
//增删改的方法判断了影响的行数是否大于0,而不是固定返回true
public boolean update(Book book) {
return bookDao.update(book) > 0;
}
//增删改的方法判断了影响的行数是否大于0,而不是固定返回true
public boolean delete(Integer id) {
return bookDao.delete(id) > 0;
}
public Book getById(Integer id) {
if(id < 0){
throw new BusinessException(Code.BUSINESS_ERR,"请不要使用你的技术挑战我的耐性!");
return bookDao.getById(id);
}
}
public List<Book> getAll() {
return bookDao.getAll();
}
}
//弹出编辑窗口
handleUpdate(row) {
// console.log(row); //row.id 查询条件
//查询数据,根据id查询
axios.get("/books/"+row.id).then((res)=>{
// console.log(res.data.data);
if(res.data.code == 20041){
//展示弹层,加载数据
this.formData = res.data.data;
this.dialogFormVisible4Edit = true;//这里才显示弹层
}else{
this.$message.error(res.data.msg);
}
});
}
我直接这么做的,其实也行,但是万一人家修改了前端页面,就可能出问题了,上面写法去查下数据库可以保证数据一定是数据库里原版的
//弹出编辑窗口
handleUpdate(row) {//直接就把行数据传送过来了 真好呀
this.dialogFormVisible4Edit = true;
this.formData = row;
},
//编辑
handleEdit() {
axios.put("/books", this.formData).then(res => {
//如果操作成功,关闭弹层,显示数据
if (res.data.code == 20031) {
this.$message.success("修改成功!");
this.dialogFormVisible4Edit = false;
} 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: 'warning'
}).then(() => {
//2. 做删除业务
axios.delete("/books/"+row.id).then(res=>{
if(res.data.code ==20021){
this.$message.success("删除成功!");
}else {
this.$message.success("删除失败!");
}
}).finally(() => {
this.getAll();
});
}).catch(() => {
//3. 取消删除
this.$message.info('已取消删除');
});
},
问题1:拦截器拦截的对象是谁?
问题2:拦截器和过滤器有什么区别?
ServletContainersInitConfig里面配置了哪些请求需要走SpringMVC
假如配置成下面这样,那么只有/books/开头的请求走SpringMVC,Interceptor只能拦截这些请求,其他请求Interceptor管不到了 (Interceptor只能管SpringMVC的,管不了spring、mybatis以及其他一些框架的)
当然只是现在我们是直接这么配的,就很难看清二者区别了
protected String[] getServletMappings() { return new String[]{"/"}; }
定义拦截器需要实现什么接口?
之前的springmvc_06_rest复制一份,改名为springmvc_12_interceptor,并删除不必要的文件
链接:https://pan.baidu.com/s/1tbXdjN_8PxzMPEwV01AdJA
提取码:roy6
拦截怎么做?给个接口限制一下就OK啦,你就不会瞎写啦
做法:定义一个类,实现HandlerInterceptor接口即可
controller包下新建interceptor包,再在该包下新建ProjectInterceptor类,也就是
cn.whu.controller.interceptor.ProjectInterceptor
好处:@Component
注解就自动被扫描到了,因为也在Controller包下
@Component //注意当前类必须受Spring容器控制
//定义拦截器类,实现HandlerInterceptor接口
public class ProjectInterceptor implements HandlerInterceptor {
@Override
//原始方法调用前执行的内容
//返回值类型可以拦截控制的执行,true放行,false终止
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle..."+contentType);
return true;//return false;原始controller方法就不会再被访问执行了
}
@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...");
}
}
config.SpringMvcSupport 之前的静态资源过滤放行(addResourceHandlers
)也写在这里
这么写 SpringMvcConfig 得多扫描一个包:
cn.whu.Controller
(下面写法2直接写在SpringMvcConfig中简单一点)
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
//注入我们自己写的拦截器 [cn.whu.controller.interceptor.ProjectInterceptor]
@Autowired
private ProjectInterceptor projectInterceptor;
// 添加拦截器
@Override
protected void addInterceptors(InterceptorRegistry registry) {
// 请求/books (完全匹配) 时用这个拦截器拦截 【/boks/1 拦截不了哦】
// 请求/books/* (eg:/books/1) 时也用这个拦截器拦截 【/books/s/1 拦截不到哦】
// 请求/books/*/* (eg:/books/s/1) 时也用这个拦截器拦截
registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*","/books/*/*");
}
}
可以删除SpringMvcSupport配置类了,他的代码直接写在SpringMvcConfig里面,这样少了一个文件SpringMvcConfig 也可以少扫描一个"cn.whu.config"包
@Configuration
@ComponentScan({"cn.whu.controller"})
@EnableWebMvc
public class SpringMvcConfig implements WebMvcConfigurer {
//SpringMvcSupport的内容直接写这里 上面不用再扫描 "cn.whu.config" 包了
// 1、设置静态资源访问过滤,当前类需要设置为配置类,并被扫描加载
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//当访问/pages/xxx的时候,走/pages目录下的内容
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
// 其他静态资源依次类推 逐一 放行
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
}
//注入我们自己写的拦截器 [cn.whu.controller.interceptor.ProjectInterceptor]
@Autowired
private ProjectInterceptor projectInterceptor;
// 2、添加拦截器
public void addInterceptors(InterceptorRegistry registry) {
// 请求/books (完全匹配) 时用这个拦截器拦截 【/boks/1 拦截不了哦】
// 请求/books/* (eg:/books/1) 时也用这个拦截器拦截 【/books/s/1 拦截不到哦】
// 请求/books/*/* (eg:/books/s/1) 时也用这个拦截器拦截
registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*","/books/*/*");
}
}
postHandle()和afterCompletion()方法都是处理器方法执行之后执行,有什么区别?
//原始方法调用前执行的内容
//返回值类型可以拦截控制的执行,true放行,false终止
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle..."+contentType);
return true;
}
参数
返回值
返回值为false,被拦截的处理器将不执行。
//原始方法调用前执行的内容
//返回值类型可以拦截控制的执行,true放行,false终止
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1、可以获取请求参数
String contentType = request.getHeader("Content-Type");
System.out.println(contentType);
//2、特别好用的handler
System.out.println(handler);//cn.whu.controller.BookController#save(Book)
System.out.println(handler.getClass());//class org.springframework.web.method.HandlerMethod
//2.1 不妨强转试试
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 哇:可以做反射里的一系列事情了
System.out.println(handlerMethod.getBean());
System.out.println(handlerMethod.getMethod());
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...");
}
注意:无论处理器方法内部是否出现异常,该方法都会执行。
其实以后很少用多拦截器,一般一个拦截器就够了。
什么是拦截器链?
@Component
public class ProjectInterceptor2 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle...222");
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle...222");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion...222");
}
}
@Configuration
@ComponentScan({"cn.whu.controller"})
@EnableWebMvc
//实现WebMvcConfigurer接口可以简化开发,但具有一定的侵入性
public class SpringMvcConfig implements WebMvcConfigurer {
@Autowired
private ProjectInterceptor projectInterceptor;
@Autowired
private ProjectInterceptor2 projectInterceptor2;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//配置多拦截器
registry.addInterceptor(projectInterceptor)
.addPathPatterns("/books","/books/*");
// 同样的路径被多个拦截器匹配,形成拦截器链 (这里添加的顺序就是拦截器链的顺序)
registry.addInterceptor(projectInterceptor2)
.addPathPatterns("/books","/books/*");
}
}
再访问任意路径,eg:http://localhost/books
有一个return false了,所有的post都不会执行。
只要拦截器return true了,对应的after一定会执行 (return false的那个拦截器的after不会执行)