SSM纯注解后台代码整合(Spring+SpringMvc+Mybatis)

SSM后台整合(Spring+SpringMvc+Mybtis+事务+Rest风格+统一结果封装+统一异常处理+拦截器)

文章目录

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 异常的种类及出现异常的原因:

框架内部抛出的异常:因使用不合规导致
数据层抛出的异常:因外部服务器故障导致(例如:服务器访问超时)
业务层抛出的异常:因业务逻辑书写错误导致(例如:遍历业务书写操作,导致索引异常等)
表现层抛出的异常:因数据收集、校验等规则导致(例如:不匹配的数据类型间导致异常)
工具类抛出的异常:因工具类书写不严谨不够健壮导致(例如:必要释放的连接长期未释放等)

看完上面这些出现异常的位置,你会发现,在我们开发的任何一个位置都有可能出现异常,而且这些异常是不能避免的。所以我们就得将异常进行处理。

思考:

  1. 各个层级均出现异常,异常处理代码书写在哪一层?
    所有的异常均抛出到表现层进行处理
  2. 异常的种类很多,表现层如何将所有的异常都处理到呢?
    异常分类
  3. 表现层处理异常,每个方法中单独书写,代码书写量巨大且意义不强,如何解决?
    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 结合前端页面实现简单(增删改查)

初始化查全表

在这里插入图片描述

添加图书(增)

在这里插入图片描述

修改图书信息(改)

在这里插入图片描述

删除(删)

在这里插入图片描述

模糊查询(查)

在这里插入图片描述

11 注解汇总

在这里插入图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值