SSM后台整合(Spring+SpringMvc+Mybtis+事务+Rest风格+统一结果封装+统一异常处理+拦截器)
文章目录
- 1 基础环境搭建
- 1.1 建表
- 1.2 创建web项目
- 1.3 导入依赖坐标(pom.xml)
- 1.4 包路径的创建
- 1.5 在pojo包下编写book实体类
- 1.6 在webapp包下导入静态资源
- 1.7 最终项目包结构
- 2 创建Spring配置文件类SpringConfig
- 3 Mybatis搭建
- 3.1 在mapper包下编写bookDao接口
- 3.2 在service包下编写bookService业务层接口
- 3.3 在service包下创建impl包编写业务层接口实现类
- 3.4 在resources包下创建db.properties配置文件
- 3.5 在config包下创建数据源的JdbcConfig配置类
- 3.6 创建Mybatis配置类并配置SqlSessionFactory
- 3.7 完善SpringConfig配置类
- 更多Mybatis搭建细节看这篇
- 4 事务搭建
- 4.1 在业务层需要被事务管理的方法上添加注解
- 4.2 在JdbcConfig类中配置事务管理器
- 4.3 在SpringConfig类中开启事务注解
- 更多事务细节看这篇(第6节 AOP事务管理)
- 5 Rest风格简介
- 6 统一结果封装搭建
- 6.1 在Controller包下创建Result类
- 6.2 在Controller包下定义返回码Code枚举类
- 6.3 实现的效果
- 7 SpringMvc搭建
- 7.1 在controller包下创建控制器类
- 7.2 在controller包下创建Tomcat的Servlet容器配置类ServletContainersInitConfig
- 7.3 在controller包下创建静态资源放行类SpringMvcSupport
- 7.4 在controller包下创建SpringMvcConfig配置类
- 8 统一异常处理搭建
- 8.1 异常的种类及出现异常的原因:
- 8.2 异常的分类和处理
- 8.3 自定义异常类
- 8.5 在异常处理器类中处理异常
- 关于更多异常细节看这篇(第3节,统一异常处理)
- 9 拦截器搭建
- 9.1 在controller包下创建拦截器类
- 9.2 在config包下的SpringMvcSupport类中配置拦截器bean
- 关于拦截器更多细节看这篇(第5节,拦截器)
- 10 结合前端页面实现简单(增删改查)
- 11 注解汇总
1 基础环境搭建
1.1 建表
CREATE TABLE tbl_book(
id INT PRIMARY KEY AUTO_INCREMENT,
TYPE VARCHAR(20),
NAME VARCHAR(50),
description VARCHAR(255)
)ENGINE=INNODB DEFAULT CHARSET=utf8
INSERT INTO `tbl_book`(`id`,`type`,`name`,`description`) VALUES
(1,'计算机理论','Spring实战 第五版','Spring入门经典教程,深入理解Spring原理技术内幕'),
(2,'计算机理论','Spring 5核心原理与30个类手写实践','十年沉淀之作,手写Spring精华思想'),
(3,'计算机理论','Spring 5设计模式','深入Spring源码刨析Spring源码中蕴含的10大设计模式'),
(4,'计算机理论','Spring MVC+Mybatis开发从入门到项目实战','全方位解析面向Web应用的轻量级框架,带你成为Spring MVC开发高手'),
(5,'计算机理论','轻量级Java Web企业应用实战','源码级刨析Spring框架,适合已掌握Java基础的读者'),(6,'计算机理论','Java核心技术 卷Ⅰ 基础知识(原书第11版)','Core Java第11版,Jolt大奖获奖作品,针对Java SE9、10、11全面更新'),
(7,'计算机理论','深入理解Java虚拟机','5个纬度全面刨析JVM,大厂面试知识点全覆盖'),
(8,'计算机理论','Java编程思想(第4版)','Java学习必读经典,殿堂级著作!赢得了全球程序员的广泛赞誉'),
(9,'计算机理论','零基础学Java(全彩版)','零基础自学编程的入门图书,由浅入深,详解Java语言的编程思想和核心技术'),
(10,'市场营销','直播就这么做:主播高效沟通实战指南','李子柒、李佳奇、薇娅成长为网红的秘密都在书中'),
(11,'市场营销','直播销讲实战一本通','和秋叶一起学系列网络营销书籍'),
(12,'市场营销','直播带货:淘宝、天猫直播从新手到高手','一本教你如何玩转直播的书,10堂课轻松实现带货月入3W+');
1.2 创建web项目
1.3 导入依赖坐标(pom.xml)
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!-- 导入spring和springMvc依赖包 -->
<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>
<!-- 整合mybatis和操作数据库 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</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>8.0.26</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<!-- Spring整合test测试 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- json数据处理 -->
<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><!--tomcat端口号-->
<path>/</path> <!--虚拟目录-->
<uriEncoding>UTF-8</uriEncoding><!--访问路径编解码字符集-->
</configuration>
</plugin>
</plugins>
</build>
说明: servlet的坐标为什么需要添加<scope>provided</scope>?
scope是maven中jar包依赖作用范围的描述,
如果不设置默认是compile在在编译、运行、测试时均有效
如果运行有效的话就会和tomcat中的servlet-api包发生冲突,导致启动报错
provided代表的是该包只在编译和测试的时候用,运行的时候无效直接使用tomcat中的,就避免冲突
1.4 包路径的创建
1.5 在pojo包下编写book实体类
package com.hyl.pojo;
/**
* @author hyl
* @version 1.0
* @date 2023/1/10-10:23
*/
public class Book {
private Integer id;
private String type;
private String name;
private String description;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
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;
}
@Override
public String toString() {
return "Book{" +
"id=" + id +
", type='" + type + '\'' +
", name='" + name + '\'' +
", description='" + description + '\'' +
'}';
}
}
1.6 在webapp包下导入静态资源
SSM纯注解后台代码整合静态资源包
https://www.aliyundrive.com/s/Gvqs3fSuAEw
提取码: 9m2v
books.html页面效果
1.7 最终项目包结构
2 创建Spring配置文件类SpringConfig
在config包下创建SpringConfig配置类(替代Spring-config.xml配置文件)
package com.hyl.config;
import org.springframework.context.annotation.Configuration;
/**
* @author hyl
* @version 1.0
* @date 2023/1/10-14:19
*/
@Configuration
public class SpringConfig {
}
3 Mybatis搭建
3.1 在mapper包下编写bookDao接口
使用Mybatis注解开发,不需要写对应的mapper.xml文件
对于复杂情况sql还是mapper.xml方便
package com.hyl.mapper;
import com.hyl.pojo.Book;
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 java.util.List;
/**
* @author hyl
* @version 1.0
* @date 2023/1/10-14:42
*/
public interface BookDao {
@Insert("insert into tbl_book (type,name,description) values(#{type},#{name},#{description})")
public int save(Book book);
@Update("update tbl_book set type = #{type}, name = #{name}, description = #{description} where id = #{id}")
public int update(Book book);
@Delete("delete from tbl_book where id = #{id}")
public int delete(Integer id);
@Select("select * from tbl_book where name like concat('%',#{input},'%')")
public List<Book> getByName(String input);
@Select("select * from tbl_book where id = #{id}")
public Book getById(Integer id);
@Select("select * from tbl_book")
public List<Book> getAll();
}
3.2 在service包下编写bookService业务层接口
package com.hyl.service;
import com.hyl.pojo.Book;
import java.util.List;
/**
* @author hyl
* @version 1.0
* @date 2023/1/10-14:44
*/
public interface BookService {
/**
* 保存
* @param book
* @return
*/
public boolean save(Book book);
/**
* 修改
* @param book
* @return
*/
public boolean update(Book book);
/**
* 按id删除
* @param id
* @return
*/
public boolean delete(Integer id);
/**
* 依据id查询
* @param id
* @return
*/
public Book getById(Integer id);
/**
* 按输入模糊查询
* @param input
* @return
*/
public List<Book> getByName(String input);
/**
* 查询全部
* @return
*/
public List<Book> getAll();
}
3.3 在service包下创建impl包编写业务层接口实现类
package com.hyl.service.impl;
import com.hyl.mapper.BookDao;
import com.hyl.pojo.Book;
import com.hyl.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author hyl
* @version 1.0
* @date 2023/1/10-14:44
*/
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
@Override
public boolean save(Book book) {
return bookDao.save(book)>0;
}
@Override
public boolean update(Book book) {
return bookDao.update(book)>0;
}
@Override
public boolean delete(Integer id) {
return bookDao.delete(id)>0;
}
@Override
public Book getById(Integer id) {
return bookDao.getById(id);
}
@Override
public List<Book> getByName(String input) {
return bookDao.getByName(input);
}
@Override
public List<Book> getAll() {
return bookDao.getAll();
}
}
3.4 在resources包下创建db.properties配置文件
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/table_04
username=root
password=123456
3.5 在config包下创建数据源的JdbcConfig配置类
package com.hyl.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
/**
* @author hyl
* @version 1.0
* @date 2023/1/10-14:18
*/
@Component
public class JdbcConfig {
/**
*从db.properties文件读取加载
*/
@Value("${driver}")
private String driver;
@Value("${url}")
private String url;
@Value("${username}")
private String username;
@Value("${password}")
private String password;
/**
* 获取数据源
* @return
*/
@Bean
public DataSource dataSource(){
DruidDataSource db=new DruidDataSource();
db.setDriverClassName(driver);
db.setUrl(url);
db.setUsername(username);
db.setPassword(password);
return db;
}
}
3.6 创建Mybatis配置类并配置SqlSessionFactory
package com.hyl.config;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
/**
* @author hyl
* @version 1.0
* @date 2023/1/10-14:33
* Mybatis配置文件(等同于mybatis-config.xml配置文件)
*/
@Component
public class MybatisConfig {
/**
* 定义bean,SqlSessionFactoryBean,用于产生SqlSessionFactory对象
* =====================================================
* dataSource从JdbcConfig中获取,替代配置文件中的
*<dataSource type="POOLED">
* <property name="driver" value="${driver}"/>
* <property name="url" value="${url}"/>
* <property name="username" value="${username}"/>
* <property name="password" value="${password}"/>
*</dataSource>
*
*======================================================
*
* setTypeAliasesPackage("com.hyl.pojo")替代起别名
*<typeAliases>
* <package name="com.hyl.pojo"/>
*</typeAliases>
*
* =====================================================
* @param dataSource 获取的DataSource在ioc容器中已经注入,会自动装填
* @return 返回一个sqlSession对象
*/
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean sqlSession=new SqlSessionFactoryBean();
//设置模型类的别名扫描
sqlSession.setTypeAliasesPackage("com.hyl.pojo");
//设置数据源
sqlSession.setDataSource(dataSource);
return sqlSession;
}
/**
* 定义bean,返回MapperScannerConfigurer对象,替代
* <mappers>
* <package name="com.hyl.mapper"/>
*<mappers/>
* @return
*/
@Bean
public MapperScannerConfigurer mappers(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.hyl.mapper");
return msc;
}
}
3.7 完善SpringConfig配置类
主配置类中service包下的业务bean
@ComponentScan({"com.hyl.service"})
主配置类中引入Mybatis配置类
主配置类中读properties并引入数据源配置类
@PropertySource({"classpath:db.properties"}) @Import({JdbcConfig.class,MybatisConfig.class})
package com.hyl.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;
/**
* @author hyl
* @version 1.0
* @date 2023/1/10-14:19
*/
@Configuration
@ComponentScan({"com.hyl.service"})
@PropertySource({"classpath:db.properties"})
@Import({JdbcConfig.class,MybatisConfig.class})
public class SpringConfig {
}
更多Mybatis搭建细节看这篇
https://blog.csdn.net/m0_58730471/article/details/128595925
4 事务搭建
4.1 在业务层需要被事务管理的方法上添加注解
注意:
@Transactional可以写在接口类上、接口方法上、实现类上和实现类方法上写在接口类上,该接口的所有实现类的所有方法都会有事务
写在接口方法上,该接口的所有实现类的该方法都会有事务
写在实现类上,该类中的所有方法都会有事务
写在实现类方法上,该方法上有事务
建议写在实现类或实现类的方法上
给BookService接口添加@Transactional注解
package com.hyl.service;
import com.hyl.pojo.Book;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* @author hyl
* @version 1.0
* @date 2023/1/10-14:44
*/
@Transactional
public interface BookService {
/**
* 保存
* @param book
* @return
*/
public boolean save(Book book);
/**
* 修改
* @param book
* @return
*/
public boolean update(Book book);
/**
* 按id删除
* @param id
* @return
*/
public boolean delete(Integer id);
/**
* 依据id查询
* @param id
* @return
*/
public Book getById(Integer id);
/**
* 按name查询
* @param input
* @return
*/
public List<Book> getByName(String input);
/**
* 查询全部
* @return
*/
public List<Book> getAll();
}
4.2 在JdbcConfig类中配置事务管理器
/**
* 配置事务管理器,mybatis使用的是jdbc事务
* @param dataSource
* @return
*/
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager sourceTransactionManager=new DataSourceTransactionManager();
sourceTransactionManager.setDataSource(dataSource);
return sourceTransactionManager;
}
package com.hyl.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.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
/**
* @author hyl
* @version 1.0
* @date 2023/1/10-14:18
*/
@Component
public class JdbcConfig {
/**
*从db.properties文件读取加载
*/
@Value("${driver}")
private String driver;
@Value("${url}")
private String url;
@Value("${username}")
private String username;
@Value("${password}")
private String password;
/**
* 获取数据源
* @return
*/
@Bean
public DataSource dataSource(){
DruidDataSource db=new DruidDataSource();
db.setDriverClassName(driver);
db.setUrl(url);
db.setUsername(username);
db.setPassword(password);
return db;
}
/**
* 配置事务管理器,mybatis使用的是jdbc事务
* @param dataSource
* @return
*/
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager sourceTransactionManager=new DataSourceTransactionManager();
sourceTransactionManager.setDataSource(dataSource);
return sourceTransactionManager;
}
}
4.3 在SpringConfig类中开启事务注解
@EnableTransactionManagement
package com.hyl.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;
/**
* @author hyl
* @version 1.0
* @date 2023/1/10-14:19
*/
@Configuration
@ComponentScan({"com.hyl.service"})
@PropertySource({"classpath:db.properties"})
@Import({JdbcConfig.class,MybatisConfig.class})
@EnableTransactionManagement
public class SpringConfig {
}
更多事务细节看这篇(第6节 AOP事务管理)
https://blog.csdn.net/m0_58730471/article/details/127782322?spm=1001.2014.3001.5501
5 Rest风格简介
REST(Representational State Transfer),表现形式状态转换,它是一种软件架构风格
请求的方式比较多,但是比较常用的就4种,分别是GET,POST,PUT,DELETE
按照REST风格访问资源时使用行为动作区分对资源进行了何种操作
发送GET请求是用来做查询
发送POST请求是用来做新增
发送PUT请求是用来做修改
发送DELETE请求是用来做删除例如:
http://localhost/users 查询全部用户信息 GET(查询)
http://localhost/users/1 查询指定用户信息 GET(查询)
http://localhost/users 添加用户信息 POST(新增/保存)
http://localhost/users 修改用户信息 PUT(修改/更新)
http://localhost/users/1 删除用户信息 DELETE(删除)描述模块的名称通常使用复数,也就是加s的格式描述,表示此类资源,而非单个资源,例如:users、books、accounts…
REST的优点有:
- 隐藏资源的访问行为,无法通过地址得知对资源是何种操作
- 书写简化
那什么又是RESTful呢?
- 根据REST风格对资源进行访问称为RESTful。
上述行为是约定方式,约定不是规范,可以打破,所以称REST风格,而不是REST规范
6 统一结果封装搭建
6.1 在Controller包下创建Result类
Result类名及类中的字段并不是固定的,可以根据需要自行增减提供若干个构造方法,方便操作。
package com.hyl.controller;
/**
* @author hyl
* @version 1.0
* @date 2023/1/10-17:21
*/
public class Result {
/**
* 描述统一格式中的数据
*/
private Object data;
/**
* 描述统一格式中的编码,用于区分操作,可以简化配置0或1表示成功失败
*/
private Integer code;
/**
* 描述统一格式中的消息,可选属性
*/
private String msg;
public Result() {
}
public Result(Code code,Object data) {
this.data = data;
this.code = code.getCode();
}
public Result(Code code, Object data, String msg) {
this.data = data;
this.code = code.getCode();
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
6.2 在Controller包下定义返回码Code枚举类
状态码都是自定义的,并不是规定都得这样写
package com.hyl.controller;
/**
* @author hyl
* @version 1.0
* @date 2023/1/10-17:22
* 状态码(枚举类)
*/
public enum Code {
//保存成功
SAVE_OK(20011),
//删除成功
DELETE_OK(20021),
//更新成功
UPDATE_OK(20031),
//查询成功
GET_OK(20041),
//保存失败
SAVE_ERR(20010),
//删除失败
DELETE_ERR(20020),
//更新失败
UPDATE_ERR(20030),
//查询失败
GET_ERR(20040),
//系统异常
SYSTEM_ERR(50001),
//超时异常
SYSTEM_TIMEOUT_ERR(50002),
//未知异常
SYSTEM_UN_KNOW_ERR(59999),
//业务异常
BUSINESS_ERR(60002);
private Integer code;
private Code(Integer code) {
this.code = code;
}
public Integer getCode() {
return code;
}
}
6.3 实现的效果
在Controller包中返回结果(封装的Result对象处理成的json格式数据)就已经能以一种统一的格式返回给前端。前端根据返回的结果,先从中获取code,根据code判断,如果成功则取data属性的值,如果失败,则取msg中的值做提示。
最终无论操作处理成功或是失败,返回给前台的数据格式都如下所示
{
"data":{"id":1,"type":"计算机理论","name":"Spring实战 第五版","description":"Spring入门经典教程,深入理解Spring原理技术内幕"},
"code":20041,
"msg":""
}
// data 携带数据
// code 是状态码
// msg 是信息
7 SpringMvc搭建
7.1 在controller包下创建控制器类
controller是表现层(写在该层下的BookController类就类似于javaweb阶段学习的servlet一样负责控制转发)
使用@RestController
注解替换@Controller、@ResponseBody
注解,简化书写
package com.hyl.controller;
import com.hyl.pojo.Book;
import com.hyl.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @author hyl
* @version 1.0
* @date 2023/1/10-10:34
*/
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private BookService bookService;
@PostMapping
public Result save(@RequestBody Book book) {
boolean flag = bookService.save(book);
return new Result(flag ? Code.SAVE_OK:Code.SAVE_ERR,flag);
}
@PutMapping
public 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("/byName/{input}")
public Result getByName(@PathVariable String input) {
System.out.println("input="+input);
List<Book> book = bookService.getByName(input);
Code code = book != null ? Code.GET_OK : Code.GET_ERR;
String msg = book != null ? "" : "数据查询失败,请重试!";
return new Result(code,book,msg);
}
@GetMapping("/byId/{id}")
public Result getById(@PathVariable Integer id){
System.out.println("id="+id);
Book book = bookService.getById(id);
Code 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 = bookService.getAll();
Code code = bookList != null ? Code.GET_OK : Code.GET_ERR;
String msg = bookList != null ? "" : "数据查询失败,请重试!";
return new Result(code,bookList,msg);
}
}
7.2 在controller包下创建Tomcat的Servlet容器配置类ServletContainersInitConfig
package com.hyl.config;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import javax.servlet.Filter;
/**
* @author hyl
* @version 1.0
* @date 2023/1/9-14:10
* 定义servlet容器的配置类
*/
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
/**
*乱码处理
*/
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
return new Filter[]{filter};
}
}
7.3 在controller包下创建静态资源放行类SpringMvcSupport
前端页面访问
静态资源会被SpringMVC拦截
我们在Servlet容器配置类中写的是拦截所有资源请求
package com.hyl.config;
import com.hyl.controller.interceptor.ProjectInterceptor;
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;
/**
* @author hyl
* @version 1.0
* @date 2023/1/10-11:00
*/
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Autowired
private ProjectInterceptor projectInterceptor;
/**
* 设置静态资源访问过滤,当前类需要设置为配置类,并被扫描加载
* @param registry
*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
//当访问/pages/---时候,从/pages目录下查找内容(放行这些页面资源)
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
// 解决该警告 No mapping for GET /favicon.ico
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
}
}
7.4 在controller包下创建SpringMvcConfig配置类
扫描controller包下的XxxController类
扫描config包下的静态资源放行类
@ComponentScan({"com.hyl.controller","com.hyl.config"})
package com.hyl.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
/**
* @author hyl
* @version 1.0
* @date 2023/1/9-14:05
*/
@Configuration
@ComponentScan({"com.hyl.controller","com.hyl.config"})
@EnableWebMvc
public class SpringMvcConfig {
}
8 统一异常处理搭建
8.1 异常的种类及出现异常的原因:
框架内部抛出的异常:因使用不合规导致
数据层抛出的异常:因外部服务器故障导致(例如:服务器访问超时)
业务层抛出的异常:因业务逻辑书写错误导致(例如:遍历业务书写操作,导致索引异常等)
表现层抛出的异常:因数据收集、校验等规则导致(例如:不匹配的数据类型间导致异常)
工具类抛出的异常:因工具类书写不严谨不够健壮导致(例如:必要释放的连接长期未释放等)看完上面这些出现异常的位置,你会发现,在我们开发的任何一个位置都有可能出现异常,而且这些异常是不能避免的。所以我们就得将异常进行处理。
思考:
- 各个层级均出现异常,异常处理代码书写在哪一层?
所有的异常均抛出到表现层进行处理- 异常的种类很多,表现层如何将所有的异常都处理到呢?
异常分类- 表现层处理异常,每个方法中单独书写,代码书写量巨大且意义不强,如何解决?
AOP
对于上面这些问题及解决方案,SpringMVC已经为我们提供了一套解决方案:
异常处理器:集中的、统一的处理项目中出现的异常。
例如:
//@RestControllerAdvice用于标识当前类为REST风格对应的异常处理器
@RestControllerAdvice
public class ProjectExceptionAdvice {
//除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常
@ExceptionHandler(Exception.class)
public Result doException(Exception ex){
System.out.println("异常已捕获!")
return new Result(666,null,"后台出异常了!");
}
}
8.2 异常的分类和处理
因为异常的种类有很多,如果每一个异常都对应一个@ExceptionHandler,那得写多少个方法来处理各自的异常,所以我们在处理异常之前,需要对异常进行一个分类:
- 业务异常(BusinessException)
规范的用户行为产生的异常
用户在页面输入内容的时候未按照指定格式进行数据填写,如在年龄框输入的是字符串
不规范的用户行为操作产生的异常
如用户故意传递错误数据
- 系统异常(SystemException)
项目运行过程中可预计但无法避免的异常
比如数据库或服务器宕机
- 其他异常(Exception)
编程人员未预期到的异常,如:用到的文件不存在
将异常分类以后,针对不同类型的异常,要提供具体的解决方案
8.3 自定义异常类
自定义异常类
- 让自定义异常类继承RuntimeException的好处是,后期在抛出这两个异常的时候,就不用在try…catch…或throws了
- 自定义异常类中添加code属性的原因是为了更好的区分异常是来自哪个业务的
在exception包下创建系统异常
和业务异常
自定义异常类
SystemException.java
package com.hyl.exception;
import com.hyl.controller.Code;
/**
* @author hyl
* @version 1.0
* @date 2023/1/10-17:52
* 自定义异常处理器,用于封装异常信息,对异常进行分类
*/
public class SystemException extends RuntimeException{
private Code code;
public Code getCode() {
return code;
}
public void setCode(Code code) {
this.code = code;
}
public SystemException(Code code, String message) {
super(message);
this.code = code;
}
public SystemException(Code code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
}
BusinessException.java
package com.hyl.exception;
import com.hyl.controller.Code;
/**
* @author hyl
* @version 1.0
* @date 2023/1/10-17:53
* 自定义异常处理器,用于封装异常信息,对异常进行分类
*/
public class BusinessException extends RuntimeException{
private Code code;
public Code getCode() {
return code;
}
public void setCode(Code code) {
this.code = code;
}
public BusinessException(Code code, String message) {
super(message);
this.code = code;
}
public BusinessException(Code code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
}
8.5 在异常处理器类中处理异常
package com.hyl.controller;
import com.hyl.exception.BusinessException;
import com.hyl.exception.SystemException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* @author hyl
* @version 1.0
* @date 2023/1/10-18:50
* @RestControllerAdvice 用于标识当前类为REST风格对应的异常处理器
*/
@RestControllerAdvice
public class ProjectExceptionAdvice {
/**
* @ExceptionHandler 用于设置当前处理器类对应的异常类型
* @param ex
* @return
*/
@ExceptionHandler(SystemException.class)
public Result doSystemException(SystemException ex){
//记录日志 (略)
//发送消息给运维 (略)
//发送邮件给开发人员,ex对象发送给开发人员 (略)
return new Result(ex.getCode(),null,ex.getMessage());
}
/**
* 自定义的业务异常
* @param ex
* @return
*/
@ExceptionHandler(BusinessException.class)
public Result doBusinessException(BusinessException ex){
return new Result(ex.getCode(),null,ex.getMessage());
}
/**
* 除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常
* @param ex
* @return
*/
@ExceptionHandler(Exception.class)
public Result doOtherException(Exception ex){
//记录日志 (略)
//发送消息给运维 (略)
//发送邮件给开发人员,ex对象发送给开发人员 (略)
return new Result(Code.SYSTEM_UN_KNOW_ERR,null,"系统繁忙,请稍后再试!");
}
}
关于更多异常细节看这篇(第3节,统一异常处理)
https://blog.csdn.net/m0_58730471/article/details/128611244?spm=1001.2014.3001.5501
9 拦截器搭建
拦截器(Interceptor)是一种动态拦截方法调用的机制,在SpringMVC中动态拦截控制器方法的执行
作用:
- 在指定的方法调用前后执行预先设定的代码
- 阻止原始方法的执行
总结:拦截器就是用来做增强
这个时候,就有一个问题需要思考:拦截器和过滤器之间的区别是什么?
- 归属不同:Filter属于Servlet技术,Interceptor属于SpringMVC技术
- 拦截内容不同:Filter对所有访问进行增强,Interceptor仅针对SpringMVC的访问进行增强
9.1 在controller包下创建拦截器类
在controller包下创建interceptor包,在interceptor包下创建拦截器类,让类实现HandlerInterceptor接口,重写接口中的三个方法。
package com.hyl.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;
/**
* @author hyl
* @version 1.0
* @date 2023/1/11-12:02
* 定义拦截器类,实现HandlerInterceptor接口
* 当前类必须受Spring容器控制
*/
@Component
public class ProjectInterceptor implements HandlerInterceptor {
/**
* 原始方法调用前执行的内容
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle...");
return true;
}
/**
* 原始方法调用后执行的内容
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle...");
}
/**
* 原始方法调用完成后执行的内容
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion...");
}
}
拦截器类要被SpringMVC容器配置类SpringMvcConfig扫描到
而我们的拦截器写在controller包下的interceptor包中
由@ComponentScan({"com.hyl.controller","com.hyl.config"})
正好可以扫描到
package com.hyl.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
/**
* @author hyl
* @version 1.0
* @date 2023/1/9-14:05
*/
@Configuration
@ComponentScan({"com.hyl.controller","com.hyl.config"})
@EnableWebMvc
public class SpringMvcConfig {
}
9.2 在config包下的SpringMvcSupport类中配置拦截器bean
/**
* 配置拦截器
* @param registry
/
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(projectInterceptor).addPathPatterns(“/books”,“/books/*”,"/books/*/" );
}
package com.hyl.config;
import com.hyl.controller.interceptor.ProjectInterceptor;
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;
/**
* @author hyl
* @version 1.0
* @date 2023/1/10-11:00
*/
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Autowired
private ProjectInterceptor projectInterceptor;
/**
* 设置静态资源访问过滤,当前类需要设置为配置类,并被扫描加载
* @param registry
*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
//当访问/pages/---时候,从/pages目录下查找内容(放行这些页面资源)
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
// 解决该警告 No mapping for GET /favicon.ico
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
}
/**
* 配置拦截器
* @param registry
*/
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*","/books/*/*" );
}
}
当有拦截器后,请求会先进入preHandle方法,
如果方法返回true,则放行继续执行后面的handle[controller的方法]和后面的方法
如果返回false,则直接跳过后面方法的执行。
关于拦截器更多细节看这篇(第5节,拦截器)
https://blog.csdn.net/m0_58730471/article/details/128611244?spm=1001.2014.3001.5501
10 结合前端页面实现简单(增删改查)
初始化查全表
添加图书(增)
修改图书信息(改)
删除(删)
模糊查询(查)