整合SSM
什么是SSM?
Q:什么是SSM?
A:SSM是Spring + SpringMVC + Mybatis组合的一个企业级框架。
创建项目框架
这是本次实验的项目大体框架,由config、controller、dao、entity、service包组成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 \---springmvc-05-ssm \---src +---main | +---java | | \---site | | \---hikki | | +---config | | +---controller | | +---dao | | +---entity | | \---service | | \---impl | +---resources | \---webapp | \---WEB-INF \---test \---site \---hikki
管理pom.xml文件
添加依赖
1 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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 <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 >
添加tomcat7插件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <build > <finalName > springmvc-05-ssm</finalName > <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 >
添加完tomcat7
插件,选中右上角
的编辑配置,再侧边框
点击+
添加一个Maven
运行配置,选中工作目录为当前项目或模块的路径,然后在运行
配置项下的输入框填入tomcat7:run
。选中合适的名称即可。
连接数据库
创建数据库
设置数据库连接信息
在resources
资源文件夹创建jdbc.properties
文件
1 2 3 4 jdbc.driver =com.mysql.cj.jdbc.Driver jdbc.url =jdbc:mysql://localhost:3306/spring_db jdbc.username =root jdbc.password =root
设置配置类
配置类
的作用就是代替以前的spring-bean.xml
管理,使用注解
来减少xml文件配置
创建JdbcConfig配置类
1 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 package site.hikki.config;import com.alibaba.druid.pool.DruidDataSource;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import javax.sql.DataSource;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; @Bean public DataSource dataSource () { DruidDataSource druidDataSource = new DruidDataSource (); druidDataSource.setUrl(url); druidDataSource.setDriverClassName(driver); druidDataSource.setUsername(username); druidDataSource.setPassword(password); return druidDataSource; } }
创建MybatisConfig配置类
对Mybatis配置进行配置管理,代替以前使用的mybatis-config.xml
文件,减少一些繁琐的配置。
1 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 package site.hikki.config;import org.mybatis.spring.SqlSessionFactoryBean;import org.mybatis.spring.mapper.MapperScannerConfigurer;import org.springframework.context.annotation.Bean;import javax.sql.DataSource;public class MybatisConfig { @Bean public SqlSessionFactoryBean sessionFactory (DataSource dataSource) { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean (); sqlSessionFactoryBean.setDataSource(dataSource); sqlSessionFactoryBean.setTypeAliasesPackage("site.hikki.entity" ); return sqlSessionFactoryBean; } @Bean public MapperScannerConfigurer mapperScannerConfigurer () { MapperScannerConfigurer configurer = new MapperScannerConfigurer (); configurer.setBasePackage("site.hikki.dao" ); return configurer; } }
创建SpringConfig主配置类
该配置类是Spring的主配置类,Spring的Ioc容器是由他产生的
1 2 3 4 5 6 @Configuration @ComponentScan({"com.itheima.service"}) @PropertySource("classpath:jdbc.properties") @Import({JdbcConfig.class,MyBatisConfig.class}) public class SpringConfig {}
Spring整合SpringMVC
创建SpringMvcConfig
1 2 3 4 5 6 7 8 9 10 11 package site.hikki.config;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.EnableWebMvc;@Configuration @ComponentScan("site.hikki.controller") @EnableWebMvc public class SpringMvcConfig {}
创建ServletConfig配置类
加载 SpringMvcConfig 和 SpringConfig 配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package site.hikki.config;import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer { protected Class<?>[] getRootConfigClasses() { return new Class []{SpringConfig.class}; } protected Class<?>[] getServletConfigClasses() { return new Class []{SpringMvcConfig.class}; } protected String[] getServletMappings() { return new String []{"/" }; } }
功能模块开发
需求
使用mybatis在web网站上对spring_db
下的tb_book
表进行数据的增高删改查。
分析
controller
对web请求进行路由控制
dao
负责数据库的持久层操作
entity
存放实体类
service
负责业务处理
数据层开发
Book实体类
创建图书的实体类,用于对图书的基本数据进行封装存储。
1 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 39 40 41 42 43 44 45 46 47 48 49 package site.hikki.entity;public class Book { private Integer id; private String type; private String name; private String description; @Override public String toString () { return "Book{" + "id=" + id + ", name='" + name + '\'' + ", description='" + description + '\'' + ", type='" + type + '\'' + '}' ; } 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; } public String getDescription () { return description; } public void setDescription (String description) { this .description = description; } public String getType () { return type; } public void setType (String type) { this .type = type; } }
BookDao接口
编写SQL语句,对数据进行增删改查操作。
1 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 package site.hikki.dao;import org.apache.ibatis.annotations.Delete;import org.apache.ibatis.annotations.Insert;import org.apache.ibatis.annotations.Select;import org.apache.ibatis.annotations.Update;import site.hikki.entity.Book;import java.util.List;public interface BookDao { @Select("select * from tb_book where id=#{id}") Book findBookById (Integer id) ; @Select("select * from tb_book") List<Book> findAll () ; @Insert("insert into tb_book (type,name,description) values(#{type},#{name},#{description})") void addBook (Book book) ; @Update("update tb_book set type=#{type},name=#{name},description=#{description} where id = #{id}") void update (Book book) ; @Delete("delete from tb_book where id = #{id}") void delete (Integer id) ; }
业务层开发
BookService接口
编写BookService接口,对数据库的读写进行实现操作。
1 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 39 40 41 package site.hikki.service;import site.hikki.entity.Book;import java.util.List;public interface BookService { Book findBookById (Integer id) ; List<Book> findAll () ; boolean addBook (Book book) ; boolean update (Book book) ; boolean delete (Integer id) ; }
BookServiceImpl接口实现
对BookDao的方法进行调用,实现对数据库的增删改查操作。
1 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 site.hikki.service.impl;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import site.hikki.dao.BookDao;import site.hikki.entity.Book;import site.hikki.service.BookService;import java.util.List;@Service public class BookServiceImpl implements BookService { @Autowired private BookDao bookDao; public Book findBookById (Integer id) { return bookDao.findBookById(id); } public List<Book> findAll () { return bookDao.findAll(); } public boolean addBook (Book book) { bookDao.addBook(book); return true ; } public boolean update (Book book) { bookDao.update(book); return true ; } public boolean delete (Integer id) { bookDao.delete(id); return true ; } }
表现层开发
也叫控制层,对Web项目的请求URL进行控制,也可以说是一个路由控制器。
在以往的Model2中,我们需要使用doGet
和doPost
来对数据的请求处理,但在SSM中,我们使用一个注解就可以省去这些麻烦的步骤了,比如使用get
请求查询全部图书,使用@GetMapping
注解就可以是实现查询全部图书了,非常方便。
1 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 39 40 package site.hikki.controller;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;import site.hikki.entity.Book;import site.hikki.service.BookService;import java.util.List;@RestController @RequestMapping("/books") public class BookContrtoller { @Autowired private BookService bookService; @GetMapping("/{id}") public Book findBookById (@PathVariable Integer id) { return bookService.findBookById(id); } @GetMapping public List<Book> findAll () { return bookService.findAll(); } @PostMapping public boolean addBook (@RequestBody Book book) { return bookService.addBook(book); } @PutMapping public boolean update (@RequestBody Book book) { return bookService.update(book); } @DeleteMapping("/{id}") public boolean delete (@PathVariable Integer id) { return bookService.delete(id); } }
开启事务管理器
开启事务管理
@EnableTransactionManagement
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package site.hikki.config;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Import;import org.springframework.context.annotation.PropertySource;import org.springframework.transaction.annotation.EnableTransactionManagement;@Configuration @ComponentScan(basePackages ={"site.hikki.service"}) @PropertySource("classpath:jdbc.properties") @Import({JdbcConfig.class, MybatisConfig.class}) @EnableTransactionManagement public class SpringConfig {}
在SpringConfig
配置类添加@EnableTransactionManagement
注解开启事务管理
实现事务管理器接口
在JdbcConfig
配置类添加,或者在其他类添加也是可以的。记得添加bean,被扫描到就好。
1 2 3 4 5 6 7 8 9 10 11 @Bean public PlatformTransactionManager transactionManager (DataSource dataSource) { DataSourceTransactionManager ds = new DataSourceTransactionManager (); ds.setDataSource(dataSource); return ds; }
DataSource
自动装配
加事务
在BookService
添加@Transactional
注解
1 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 39 40 41 42 43 package site.hikki.service;import org.springframework.transaction.annotation.Transactional;import site.hikki.entity.Book;import java.util.List;@Transactional public interface BookService { Book findBookById (Integer id) ; List<Book> findAll () ; boolean addBook (Book book) ; boolean update (Book book) ; boolean delete (Integer id) ; }
接口测试
Junit测试
我们在实现完业务逻辑代码时,我们得先使用Junit测试方法是否正常运行,然后再使用web测试方法。
1 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 site.hikki;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import site.hikki.config.SpringConfig;import site.hikki.entity.Book;import site.hikki.service.BookService;import java.util.List;@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfig.class) public class BookServiceTest { @Autowired private BookService bookService; @Test public void testGetById () { Book bookById = bookService.findBookById(1 ); System.out.println(bookById); } @Test public void findALl () { List<Book> all = bookService.findAll(); for (Book b :all) { System.out.println(b); } } }
Web测试表现层接口
我这里使用Apifox测试接口
测试查询全部图书
有需要测试的文件接口,可以下载导入apifox,如果你是postman,或许可以使用,博主未测试,若你导入成功,还望告知一声。
表现层数据封装
为什么需要封装数据?
我们在web前端对后端发起请求时,如果发起的请求URL不正确或者发起请求成功了,但返回没有数据(比如返回null),前端要怎么判定该请求是否有效呢?
我们一般是打开开发者(F12)查看请求URL状态是不是200,如果是200,则说明请求的URL成功了,但返回的内容为空,这个要怎么判断呢?
我要怎么知道是我的请求方式不对,还是请求携带的内容不对,还是后端处理逻辑有问题呢? 还是数据库出现了问题呢?
于是我们想,如果后端在返回数据的时候,返回状态码或者返回消息说明该请求是哪里出错了就好了,这样可以通过F12
判断请求状态码是否对后端请求成功
了,然后通过请求成功后返回的消息中的状态码
来判断请求是哪里出错了,是缺少了什么请求头还是缺少了什么东西,这样可以更好的解决前端对请求的URL不清晰的描述。
返回对象
我们可以封装一个带有返回状态码
、数据
、消息
的一个实体类,然后返回时,使用指定的状态码来表示不同的结果。(如果你还有需要返回需求,你可以根据自己的需求来作修改)
1 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 39 40 41 42 43 44 45 46 47 48 package site.hikki.controller;public class Result { private Integer code; private Object data; private String msg; public Result () { } public Result (Integer code, Object data) { this .code = code; this .data = data; } public Result (Integer code, Object data, String msg) { this .code = code; this .data = data; this .msg = msg; } public Integer getCode () { return code; } public void setCode (Integer code) { this .code = code; } public Object getData () { return data; } public void setData (Object data) { this .data = data; } public String getMsg () { return msg; } public void setMsg (String msg) { this .msg = msg; } }
我们为什么需要使用三个构造方法,其实不一定,你也可以不添加构造方法,直接使用默认添加的无参构造方法也行,只是添加了其他两个,在使用时方便一点,不需要每次使用都new
一个对象出来,然后再对该对象进行set
方法设置值。
自定义返回状态码
我们的状态码由5位数字组成,前三位200表示该请求是有效
的,第四位表示各种请求类型
,第五位表示该请求的后台逻辑代码是否有误
,0表示失败
,1表示成功
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package site.hikki.controller;public class Code { public static final Integer SAVE_OK = 20011 ; public static final Integer UPDATE_OK = 20021 ; public static final Integer DELETE_OK = 20031 ; public static final Integer GET_OK = 20041 ; public static final Integer SAVE_ERR = 20010 ; public static final Integer UPDATE_ERR = 20020 ; public static final Integer DELETE_ERR = 20030 ; public static final Integer GET_ERR = 20040 ; }
这里必须设置public static
,因为后面在使用该变量时,是在别的类调用的,访问权限需要public,同时,需要该变量同jvm一起初始化,这样使用时不会存在找不到该变量的情况。
修改返回状态
我们之前在BookContrtoller
下设置了URL的请求路由,并且作出了返回结果,我们上面定义了返回对象,我们准备在该控制器设置返回对象。
1 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 39 40 41 42 43 44 45 46 47 48 49 package site.hikki.controller;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;import site.hikki.entity.Book;import site.hikki.service.BookService;import java.util.List;@RestController @RequestMapping("/books") public class BookContrtoller { @Autowired private BookService bookService; @GetMapping("/{id}") public Result findBookById (@PathVariable Integer id) { Book book = bookService.findBookById(id); Integer code = book != null ? Code.GET_OK : Code.GET_ERR; String msg = book!=null ?"数据查询成功" :"数据查询失败,请重试" ; return new Result (code,book,msg); } @GetMapping public Result findAll () { List<Book> all = bookService.findAll(); Integer code = all != null ? Code.GET_OK : Code.GET_ERR; String msg = all != null ? "数据查询成功" :"数据查询失败,请重试" ; return new Result (code,all,"查询成功" ); } @PostMapping public Result addBook (@RequestBody Book book) { boolean b = bookService.addBook(book); return new Result (b?Code.SAVE_OK:Code.SAVE_ERR,b); } @PutMapping public Result update (@RequestBody Book book) { boolean b = bookService.update(book); return new Result (b?Code.UPDATE_OK:Code.UPDATE_ERR,b); } @DeleteMapping("/{id}") public Result delete (@PathVariable Integer id) { boolean b = bookService.delete(id); return new Result (b?Code.DELETE_OK:Code.DELETE_ERR,b); } }
这里的返回结果,我们使用三目运算符,提高开发效率。
测试结果
使用Apifox测试根据ID查询结果如下:
在返回数据中,多了状态码、数据和消息,便于前端开发者对该数据进行处理。
异常处理
异常介绍
异常是开发中不可避免的情况,数据中心起火、服务器掉线、开发者代码不规范等等行为都会导致异常出现,但我们不能给用户看到这种异常。
出现异常现象的常见位置与常见诱因如下:
框架内部抛出的异常:因使用不合规导致
数据层抛出的异常:因外部服务器故障导致(例如:服务器访问超时)
业务层抛出的异常:因业务逻辑书写错误导致(例如:遍历业务书写操作,导致索引异常等)
表现层抛出的异常:因数据收集、校验等规则导致(例如:不匹配的数据类型间导致异常)
工具类抛出的异常:因工具类书写不严谨不够健壮导致(例如:必要释放的连接长期未释放等)
异常处理器
编写异常
由于异常处理是属于表现层的,我们将放在controller
下,新建一个ProjectExceptionAdvice
类位于controller
包下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package site.hikki.controller;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.RestControllerAdvice;@RestControllerAdvice public class ProjectExceptionAdvice { @ExceptionHandler(Exception.class) public Result doException (Exception e) { return new Result (666 ,null ); } }
返回错误结果:
@RestControllerAdvice注解介绍
@ExceptionHandler注解介绍
名称:@ExceptionHandler
类型:方法注解
位置:专用于异常处理的控制器方法上方
作用:设置指定异常的处理方案,功能等同于控制器方法,出现异常后终止原始控制器执行,并转入当前方法执行
说明:此类方法可以根据处理的异常不同,制作多个方法分别处理对应的异常
项目异常处理介绍
项目异常分类
业务异常
(BusinessException)
规范的用户行为产生的异常
不规范的用户行为操作产生的异常
系统异常
(SystemException)
其他异常
(Exception)
项目异常处理方案
业务异常(BusinessException)
系统异常(SystemException)
发送固定消息传递给用户,安抚用户
发送特定消息给运维人员,提醒维护
记录日志
其他异常(Exception)
发送固定消息传递给用户,安抚用户
发送特定消息给编程人员,提醒维护(纳入预期范围内)
记录日志
项目异常处理代码实现
因为异常种类有多种,我们一般将它区分开,使用一个实体类将该异常种类区分开,比如业务异常、系统异常、其他异常,在上面我们已经创建了一个其他异常的类了。
系统级异常处理
1 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 package site.hikki.controller;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; } }
业务级异常处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package site.hikki.controller;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; } }
自定义异常状态码
我们在封装表现层的数据时,我们也定义过一部分的状态码,在给前端页面返回数据时,带上状态码,让前端开发人员更清楚的了解到返回的是什么东西。
我们在发生异常时,会抛出异常,我们需要告诉前端人员,后端遇到了异常,暂时无法正常运行,我们也需要让前端开发人员安抚用户,让用户等一下或者让用户稍后再试。
那我们遇到异常时,也定义一个状态码,让前端开发人员根据状态码来给用户提示响应的信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package site.hikki.controller;public class Code { 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 ; }
触发异常案例
模拟异常抛出
在site.hikki.service.impl.BookServiceImpl
类中的findBookById方法增加如下内容,模拟异常测试。
1 2 3 4 5 6 7 8 9 10 11 12 public Book findBookById (Integer id) { if (id ==1 ){ throw new BusinessException (666 ,"服务器断电了" ); } try { int i = 5 /2 ; }catch (Exception e){ throw new SystemException (666 ,"服务器连接超时" ,e); } return bookDao.findBookById(id); }
处理异常
在异常通知类中拦截并处理异常
我们原本已经在ProjectExceptionAdvice
类中已经添加过一个方法doException
方法了,该方法是处理所有异常错误,我们新添加两个方法,用于处理系统级
和业务级
的异常信息。
1 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 package site.hikki.controller;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.RestControllerAdvice;@RestControllerAdvice public class ProjectExceptionAdvice { @ExceptionHandler(SystemException.class) public Result doSystemException (SystemException e) { return new Result (e.getCode(),null ,e.getMessage()); } @ExceptionHandler(BusinessException.class) public Result doBusinessException (BusinessException e) { return new Result (e.getCode(),null ,e.getMessage()); } @ExceptionHandler(Exception.class) public Result doException (Exception e) { return new Result (Code.SYSTEM_UNKNOW_ERR,"系统繁忙,请稍后再试" ); } }
测试异常
测试:在apifox中发送请求访问getById方法,传递参数1,得到以下结果:
SSM整合页面开发
该实验整合web页面使用了Vue。
设置静态资源过滤
新建SpringMvcSupport类
为了确保静态资源能够被访问到,需要设置静态资源过滤
在site.hikki.config
包下新建SpringMvcSupport
类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package site.hikki.config;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/**" ) .addResourceLocations("/pages/" ); registry.addResourceHandler("/css/**" ) .addResourceLocations("/css/" ); registry.addResourceHandler("/js/**" ) .addResourceLocations("/js/" ); registry.addResourceHandler("/plugins/**" ) .addResourceLocations("/plugins/" ); } }
扫描配置项
在SpringMvcConfig
配置类中导扫描site.hikki.config
包
1 2 3 4 5 6 7 8 9 10 11 package site.hikki.config;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.EnableWebMvc;@Configuration @ComponentScan({"site.hikki.controller","site.hikki.config"}) @EnableWebMvc public class SpringMvcConfig {}
列表查询功能
在webapp/pages/books.html
下添加了如下代码,表示对后台/books
发起get
请求。
1 2 3 4 5 6 getAll ( ) { axios.get ("/books" ).then ((res ) => { this .dataList = res.data .data ; }); },
添加功能
前端代码
1 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 handleCreate ( ) { this .dialogFormVisible = true ; this .resetForm (); }, resetForm ( ) { this .formData = {}; }, handleAdd ( ) { axios.post ("/books" , this .formData ).then ((res ) => { console .log (res.data ); if (res.data .code == 20011 ) { this .dialogFormVisible = false ; this .$message .success ("添加成功" ); } else if (res.data .code == 20010 ) { this .$message .error ("添加失败" ); } else { this .$message .error (res.data .msg ); } }).finally (() => { this .getAll (); }); },
后端代码
将site.hikki.dao.BookDao
接口的增删改的返回类型由void
改为int
。
然后再将site.hikki.service.impl.BookServiceImpl
实现类的返回稍微修改一下,若影响条数大于0
,则返回true
,否则返回false
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public boolean addBook (Book book) { return bookDao.addBook(book) >0 ; } public boolean update (Book book) { return bookDao.update(book) >0 ; } public boolean delete (Integer id) { return bookDao.delete(id) >0 ; }
修改功能
在site.hikki.service.impl.BookServiceImpl.findBookById方法中模拟了异常抛出,当点击ID为1的图书修改按钮时,会返回异常。
1 2 3 4 5 6 7 public Book findBookById (Integer id) { if (id ==1 ){ throw new BusinessException (666 ,"服务器断电了" ); } return bookDao.findBookById(id); }
此案例为了模拟前端返回错误演示。
显示弹出框查询图书信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 handleUpdate (row ) { axios.get ("/books/" + row.id ).then ((res ) => { if (res.data .code == 20041 ) { this .formData = res.data .data ; this .dialogFormVisible4Edit = true ; } else { this .$message .error (res.data .msg ); } }); },
保存修改后的图书信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 handleEdit ( ) { 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 (); }); },
删除功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 handleDelete(row) { this .$confirm("此操作永久删除当前数据,是否继续?" , "提示" , { type: 'info' }).then(() => { axios.delete("/books/" + row.id).then((res) => { if (res.data.code == 20031 ) { this .$message.success("删除成功" ); } else { this .$message.error("删除失败" ); } }).finally (() => { this .getAll(); }); }).catch (() => { this .$message.info("取消删除操作" ); }); }
拦截器
拦截器的概念
拦截器(Interceptor)是一种动态拦截方法调用的机制,在SpringMVC中动态拦截控制器方法的执行
作用:
在指定的方法调用前后执行预先设定的代码
阻止原始方法的执行
总结:增强
核心原理:AOP思想
拦截器和过滤器的区别
归属不同
拦截内容不同
生命周期
使用场景
作用对象
执行顺序
入门案例
初始化项目
项目结构
1 2 3 4 5 6 7 8 9 10 11 \---src +---main | +---java | | \---site | | \---hikki | | +---config | | \---controller | +---resources | \---webapp | \---WEB-INF \---test
SpringConfig
Spring项目核心配置
1 2 3 4 5 6 7 package site.hikki.config;import org.springframework.context.annotation.Configuration;@Configuration public class SpringConfig {}
SpringMvcConfig
SpringMVC项目核心配置
1 2 3 4 5 6 7 8 9 10 11 package site.hikki.config;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.EnableWebMvc;@ComponentScan({"site.hikki.controller","site.hikki.config"}) @Configuration @EnableWebMvc public class SpringMvcConfig {}
ServletConfig
管理SpringMVC容器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package site.hikki.config;import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer { protected Class<?>[] getRootConfigClasses() { return new Class []{SpringConfig.class}; } protected Class<?>[] getServletConfigClasses() { return new Class []{SpringMvcConfig.class}; } protected String[] getServletMappings() { return new String []{"/" }; } }
BookController
Book控制器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package site.hikki.controller;import org.springframework.web.bind.annotation.*;@RestController @RequestMapping("/books") public class BookController { @GetMapping("/{id}") public String getBook (@PathVariable Integer id) { return "{'name':'AI','desc':'ChatGPT'}" ; } @GetMapping public String getAll () { return "[{'name':'Java web','desc':'this is Java'},{'name':'blog','desc':'blog.hikki.site'}]" ; } }
一个简单的web项目已经创建好了,测试如下:
定义拦截器
在在site.hikki.controller.interceptor
包下创建一个类,实现HandlerInterceptor
接口,并且重写preHandle
、postHandle
、afterCompletion
方法即可。他们的分别是拦截前实现
、拦截后实现
、拦截结束后实现
。
1 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 package site.hikki.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 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...." ); } }
配置加载拦截器
在site.hikki.config
包下创建一个名为SpringMvcSupport
类,并重写addInterceptors
方法,然后注册一个addInterceptor
接口,然后设置拦截路径addPathPatterns
。
我们之前也使用过这个接口,之前使用的是addResourceHandlers
接口,之前是设置了不拦截静态资源,设置了相关的不拦截路径,具体使用如下:
1 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 package site.hikki.config;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;import site.hikki.controller.interceptor.ProjectInterceptor;@Configuration public class SpringMvcSupport extends WebMvcConfigurationSupport { @Autowired private ProjectInterceptor interceptor; @Override protected void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(interceptor).addPathPatterns("/books" ,"/books/*" ); } @Override protected void addResourceHandlers (ResourceHandlerRegistry registry) { registry.addResourceHandler("/pages/" ).addResourceLocations("/pages/" ); registry.addResourceHandler("/js/" ).addResourceLocations("/js/" ); } }
在给接口设置拦截路径时,addPathPatterns
方法是可以设置多个参数的,比如上面就设置了两个参数/books
和/books/*
。
/books
和/books/*
,设置的效果不一样的,如果只是设置/books/*
,你请求的路径如果是http://localhost/books
的话,是不会被拦截到的。也就是说这个路径是精确匹配的。
测试拦截效果
拦截器链设置
拦截器流程
定义第二个拦截器
在site.hikki.controller.interceptor
包下新建ProjectInterceptor2
类,可以直接复制ProjectInterceptor
的文件修改文件名即可。
1 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 package site.hikki.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 ProjectInterceptor2 implements HandlerInterceptor { @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("preHandle....222" ); return true ; } @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" ); } }
配置拦截器
还是在之前配置拦截器的类中,site.hikki.config.SpringMvcSupport
注册一个拦截器,并添加拦截路径。
自动注入ProjectInterceptor2
类
注册拦截器并设置拦截路径
1 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 site.hikki.config;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;import site.hikki.controller.interceptor.ProjectInterceptor;import site.hikki.controller.interceptor.ProjectInterceptor2;@Configuration public class SpringMvcSupport extends WebMvcConfigurationSupport { @Autowired private ProjectInterceptor interceptor; @Autowired private ProjectInterceptor2 interceptor2; @Override protected void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(interceptor).addPathPatterns("/books" ,"/books/*" ); registry.addInterceptor(interceptor2).addPathPatterns("/books" ,"/books/*" ); } @Override protected void addResourceHandlers (ResourceHandlerRegistry registry) { registry.addResourceHandler("/pages/" ).addResourceLocations("/pages/" ); registry.addResourceHandler("/js/" ).addResourceLocations("/js/" ); } }
测试拦截器链
由结果可以看到,拦截器的执行顺序,首先是preHandle....111-->preHandle....222-->postHandle....222-->postHandle....111-->afterCompletion....222-->afterCompletion....111
,这个方式,有点点像栈,先进后出。
多个拦截器工作流程分析
在拦截器中,我们可以通过改变定义拦截器
中的preHandle
方法返回的boolean
的值来改变后续执行的拦截器流程。
比如我下面改变ProjectInterceptor
定义拦截器的preHandle
方法的返回值为false
,我们测试一下结果是怎么样的。
可以看到返回的结果只有执行了preHandle
方法,当前拦截器的其他方法也不执行了,连其他拦截器的全部方法都不执行了。
总结
当配置多个拦截器时,形成拦截器链
拦截器链的运行顺序参照拦截器添加顺序为准
当拦截器中出现对原始处理器的拦截,后面的拦截器均终止运行
当拦截器运行中断,仅运行配置在前面的拦截器的afterCompletion操作
下图是采用三个拦截器来描述拦截器的工作流程。
分别当拦截器的preHandle
返回false
时后续的拦截器的执行方法顺序。
本文章来源于我的博客:https://blog.hikki.site