【前置內容】Spring 學習筆記全系列傳送門: Spring學習筆記 - 第一章 - IoC(控制反轉)、IoC容器、Bean的實例化與生命周期、DI(依賴註入) Spring學習筆記 - 第二章 - 註解開發、配置管理第三方Bean、註解管理第三方Bean、Spring 整合 MyBatis 和 ...
【前置內容】Spring 學習筆記全系列傳送門:
目錄SpingMVC 學習筆記全系列傳送門:
1、SSM整合
1.1 流程分析
1.2 整合配置
-
創建 Maven - web 項目
-
添加依賴
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>priv.dandelion</groupId> <artifactId>08_ssm</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.0</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.16</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.1</version> <configuration> <port>80</port> <path>/</path> </configuration> </plugin> </plugins> </build> </project>
-
創建項目包結構
-
SpringConfig配置類
@Configuration @ComponentScan({"priv.dandelion.service"}) @PropertySource("classpath:jdbc.properties") @Import({JdbcConfig.class,MybatisConfig.class}) // 開啟事務 @EnableTransactionManagement public class SpringConfig { }
-
JdbcConfig配置類
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 dataSource = new DruidDataSource(); dataSource.setDriverClassName(driver); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); return dataSource; } // 事務控制管理器,數據源使用自動裝配(由Spring管理) @Bean public PlatformTransactionManager transactionManager(DataSource dataSource){ DataSourceTransactionManager ds = new DataSourceTransactionManager(); ds.setDataSource(dataSource); return ds; } }
-
Mybatis 配置類
public class MybatisConfig { @Bean public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){ SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); factoryBean.setTypeAliasesPackage("priv.dandelion.entity"); return factoryBean; } @Bean public MapperScannerConfigurer mapperScannerConfigurer(){ MapperScannerConfigurer msc = new MapperScannerConfigurer(); msc.setBasePackage("priv.dandelion.dao"); return msc; } }
-
jdbc.properties 配置文件
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/ssm_db jdbc.username=root jdbc.password=123456
-
SpringMvc 配置類
@Configuration @ComponentScan("priv.dandelion.controller") @EnableWebMvc public class SpringMvcConfig { }
-
web 項目入口配置類
public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer { //載入Spring配置類 protected Class<?>[] getRootConfigClasses() { return new Class[]{SpringConfig.class}; } //載入SpringMVC配置類 protected Class<?>[] getServletConfigClasses() { return new Class[]{SpringMvcConfig.class}; } //設置SpringMVC請求地址攔截規則 protected String[] getServletMappings() { return new String[]{"/"}; } //設置post請求中文亂碼過濾器 @Override protected Filter[] getServletFilters() { CharacterEncodingFilter filter = new CharacterEncodingFilter(); filter.setEncoding("utf-8"); return new Filter[]{filter}; } }
1.3 功能模塊開發
-
SQL
create database ssm_db character set utf8; use ssm_db; create table tbl_book( id int primary key auto_increment, type varchar(20), name varchar(50), description varchar(255) ) 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+');
-
實體類
public class Book { private Integer id; private String type; private String name; private String description; //getter...setter...toString省略 }
-
Dao 介面
public interface BookDao { // @Insert("insert into tbl_book values(null,#{type},#{name},#{description})") @Insert("insert into tbl_book (type,name,description) values(#{type},#{name},#{description})") public void save(Book book); @Update("update tbl_book set type = #{type}, name = #{name}, description = #{description} where id = #{id}") public void update(Book book); @Delete("delete from tbl_book where id = #{id}") public void delete(Integer id); @Select("select * from tbl_book where id = #{id}") public Book getById(Integer id); @Select("select * from tbl_book") public List<Book> getAll(); }
-
Service
-
介面
// 聲明該類需要被事務管理 @Transactional public interface BookService { public boolean save(Book book); public boolean update(Book book); public boolean delete(Integer id); public Book getById(Integer id); public List<Book> getAll(); }
-
實現類
@Service public class BookServiceImpl implements BookService { @Autowired private BookDao bookDao; @Override public boolean save(Book book) { bookDao.save(book); return true; } @Override public boolean update(Book book) { bookDao.update(book); return true; } @Override public boolean delete(Integer id) { bookDao.delete(id); return true; } @Override public Book getById(Integer id) { return bookDao.getById(id); } @Override public List<Book> getAll() { return bookDao.getAll(); } }
-
-
Controller
package priv.dandelion.service.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import priv.dandelion.dao.BookDao; import priv.dandelion.entity.Book; import priv.dandelion.service.BookService; import java.util.List; @Service public class BookServiceImpl implements BookService { @Autowired private BookDao bookDao; @Override public boolean save(Book book) { bookDao.save(book); return true; } @Override public boolean update(Book book) { bookDao.update(book); return true; } @Override public boolean delete(Integer id) { bookDao.delete(id); return true; } @Override public Book getById(Integer id) { return bookDao.getById(id); } @Override public List<Book> getAll() { return bookDao.getAll(); } }
1.4 單元測試
-
新建測試類
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfig.class) public class BookServiceTest { }
-
註入 Service 類,編寫測試方法
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfig.class) public class BookServiceTest { @Autowired private BookService bookService; @Test public void testGetById() { Book byId = bookService.getById(1); System.out.println(byId); } @Test public void testGetAll() { List<Book> all = bookService.getAll(); System.out.println(all); } }
1.5 PostMan 測試(細節不表)
2、統一結果封裝(前後端數據通信協議)
2.1 表現層與前端數據傳輸協議定義
-
目前程式返回的數據類型(返回的數據類型太多,未來可能會更加混亂)
-
Controller 增刪改返回給前端的是 Boolean 類型的數據
true
-
Controller 查詢單個返回給前端的是對象
{ "id":123, "type":"xxx", "name":"xxx", "description":"xxx" }
-
Controller 查詢所有返回給前端的是對象的集合
[ { "id":123, "type":"xxx", "name":"xxx", "description":"xxx" }, { "id":124, "type":"xxx", "name":"xxx", "description":"xxx" } ]
-
-
將返回的結果數據統一的方案
-
為了封裝返回的結果數據:創建結果模型類,封裝數據到data屬性中
便於知道什麼內容是數據部分
-
為了封裝返回的數據是何種操作及是否操作成功:封裝操作結果到code屬性中
多個相同的返回類型可能是不同操作,便於區分不同操作;另外,可以對code進行規定,如末位為0代表失敗,為1代表成功
-
操作失敗後為了封裝返回的錯誤信息:封裝特殊消息到message(msg)屬性中
必要的錯誤信息
-
-
統一結果封裝的格式
// 1. { "code":20031, "data":true } // 2. { "code":20040, "data":null, "msg":"查詢失敗" } // 3. { code:20041, "data":[ { "id":123, "type":"xxx", "name":"xxx", "description":"xxx" }, { "id":124, "type":"xxx", "name":"xxx", "description":"xxx" } ] }
2.2 表現層與前端數據傳輸協議實現
2.2.1 思路分析
-
統一數據返回結果實體類
-
統一返回結果所需的狀態碼定義
2.2.2 結果封裝
-
創建 Result 類
public class Result{ private Object data; private Integer code; private String msg; // 構造、getter、setter略... }
-
定義返回碼 Code 類
public class Code { public static final Integer SAVE_OK = 20011; public static final Integer DELETE_OK = 20021; public static final Integer UPDATE_OK = 20031; public static final Integer GET_OK = 20041; public static final Integer SAVE_ERR = 20010; public static final Integer DELETE_ERR = 20020; public static final Integer UPDATE_ERR = 20030; public static final Integer GET_ERR = 20040; }
-
修改 Conteoller 類的返回值
@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("/{id}") public Result getById(@PathVariable Integer id) { Book book = bookService.getById(id); Integer code = book != null ? Code.GET_OK : Code.GET_ERR; String msg = book != null ? "" : "查詢的數據不存在,請重試"; return new Result(code, book, msg); } @GetMapping public Result getAll() { List<Book> all = bookService.getAll(); Integer code = all != null ? Code.GET_OK : Code.GET_ERR; String msg = all != null ? "" : "數據查詢失敗,請重試"; return new Result(code, all, msg); } }
3、統一異常處理
3.1 問題描述
當出現異常時,響應500等錯誤代碼,返回錯誤頁面,前端獲取到有效信息,無法處理
-
可能的異常的種類及其原因
- 框架內部拋出的異常:因使用不合規導致
- 數據層拋出的異常:因外部伺服器故障導致(例如:伺服器訪問超時)
- 業務層拋出的異常:因業務邏輯書寫錯誤導致(例如:遍歷業務書寫操作,導致索引異常等)
- 表現層拋出的異常:因數據收集、校驗等規則導致(例如:不匹配的數據類型間導致異常)
- 工具類拋出的異常:因工具類書寫不嚴謹不夠健壯導致(例如:必要釋放的連接長期未釋放等)
-
統一處理方案
-
所有異常拋出到表現層進行處理
- 各級均可能出現異常,為保證統一處理,需要向上拋出,直至表現層統一處理
- 關於MVC模式與三層架構的關係可以參考:三層架構
-
異常分類
- 異常的種類有很多。對其分類以保證都能處理到
-
使用AOP
- 表現層處理異常,每個方法中單獨書寫,代碼書寫量巨大且意義不強,可以使用AOP以提高耦合
-
3.2 異常處理器的使用
為實現統一的異常處理,Spring提供了異常處理器
3.2.1 環境準備
- 在表現層中創建統一異常處理類 ProjectExceptionAdvice
- 不一定非要寫在表現層對應的 controller 包下,但是一定要保證 SpringMVC 控制類的包掃描配置能掃描到異常處理器類
3.2.2 使用步驟
-
創建異常處理器類
// 聲明這個類用於Rest風格對應的統一異常處理器類 @RestControllerAdvice public class ProjectExceptionAdvice { // 攔截異常 @ExceptionHandler(Exception.class) public void doException(Exception ex) { System.out.println("捕獲異常"); } }
-
異常處理器類返回結果到前端
// 聲明這個類用於Rest風格對應的統一異常處理 @RestControllerAdvice public class ProjectExceptionAdvice { // 攔截異常 @ExceptionHandler(Exception.class) // 修改返回值類型,向前端返回異常信息 public Result doException(Exception ex) { String msg = "異常捕獲"; System.out.println(msg); return new Result(00000, null, msg); } }
3.2.3 相關知識點
3.2.3.1 @RestControllerAdvice
名稱 | @RestControllerAdvice |
---|---|
類型 | 類註解 |
位置 | Rest風格開發的控制器增強類定義上方 |
作用 | 為Rest風格開發的控制器類做增強 |
說明 | 此註解自帶@ResponseBody註解與@Component註解,具備對應的功能 |
3.2.3.2 @ExceptionHandler
名稱 | @ExceptionHandler |
---|---|
類型 | 方法註解 |
位置 | 專用於異常處理的控制器方法上方 |
作用 | 設置指定異常的處理方案,功能等同於控制器方法, 出現異常後終止原始控制器執行,並轉入當前方法執行 |
說明 | 此類方法可以根據處理的異常不同,製作多個方法分別處理對應的異常 |
3.3 項目異常處理方案
3.3.1 異常分類
因為異常的種類有很多,如果每一個異常都對應一個@ExceptionHandler,那得寫多少個方法來處理各自的異常,所以在處理異常之前,需要對異常進行一個分類
-
業務異常(BusinessException)
- 規範的用戶行為產生的異常:如用戶在頁面輸入內容的時候未按照指定格式進行數據填寫,如在年齡框輸入字元串
- 不規範的用戶行為操作產生的異常:如故意傳遞錯誤數據
-
系統異常(SystemException)
- 項目運行過程中可預計但無法避免的異常:如資料庫或伺服器宕機
-
其他異常(Exception)
- 開發人員未預期到的異常:如用到的文件不存在
3.3.2 異常解決方案
-
業務異常(BusinessException)
- 向用戶發送對應消息,提醒其規範操作
-
系統異常(SystemException)
- 向用戶發送消息內容,告知當前系統狀態及可選操作
- 發送特定消息給運維人員提醒維護
- 記錄日誌
-
其他異常(Exception)
- 向用戶發送消息內容,告知當前系統狀態
- 發送特定消息給開發人員,提醒維護
- 記錄日誌
3.3.3 異常解決方案的具體實現
-
自定義異常類
-
系統異常(SystemException)
// 繼承RuntimeException,可以不做處理自動上拋 public class SystemException extends RuntimeException{ private Integer code; public SystemException(Integer code, String message) { super(message); this.code = code; } public SystemException(Integer code, String message, Throwable cause) { super(message, cause); this.code = code; } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } }
-
業務異常(BusinessException)
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; } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } }
-
-
將其他異常包成自定義異常(本案例在Service中進行)
@Override public Book getById(Integer id) { // 將可能出現的異常進行包裝,轉換成自定義異常 if (id <= 0) { throw new BusinessException(Code.BUSINESS_ERR, "數據不合法"); } // 將可能出現的異常進行包裝,轉換成自定義異常 try{ int i = 1/0; return bookDao.getById(id); }catch (ArithmeticException ae){ throw new SystemException(Code.SYSTEM_TIMEOUT_ERR, "伺服器訪問超時"); } }
-
處理器類中處理自定義異常
-
原先的
public Result doException(Exception ex){}
仍然保留,用於處理其他異常 -
此處為了方便新增了錯誤代碼
public static final Integer SYSTEM_ERR = 50001; public static final Integer SYSTEM_TIMEOUT_ERR = 50002; public static final Integer SYSTEM_UNKNOWN_ERR = 59999; public static final Integer BUSINESS_ERR = 60001;
// 聲明這個類用於Rest風格對應的統一異常處理 @RestControllerAdvice public class ProjectExceptionAdvice { // 攔截異常 @ExceptionHandler(SystemException.class) public Result doException(SystemException ex) { // 記錄日誌 // 發送消息給運維 // 郵件發送ex的對象給開發 // 返回消息內容 return new Result(ex.getCode(), null, ex.getMessage()); } // 攔截異常 @ExceptionHandler(BusinessException.class) public Result doException(BusinessException ex) { // 記錄日誌 // 發送郵件給開發 // 返回消息內容 return new Result(ex.getCode(), null, ex.getMessage()); } // 仍然保留,用於處理其他異常 @ExceptionHandler(Exception.class) // 修改返回值類型,向前端返回異常信息 public Result doException(Exception ex) { // 記錄日誌 // 發送消息給運維 // 郵件發送ex的對象給開發 // 返回消息內容 return new Result(Code.SYSTEM_UNKNOWN_ERR, null, "系統繁忙請稍後再試"); } }
-
4、前後臺協議聯調
4.1 環境準備
-
頁面準備
該模塊使用到了 Axios 和 ElementUI
<!DOCTYPE html> <html> <head> <!-- 頁面meta --> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>SpringMVC案例</title> <meta content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no" name="viewport"> <!-- 引入樣式 --> <link rel="stylesheet" href="../plugins/elementui/index.css"> <link rel="stylesheet" href="../plugins/font-awesome/css/font-awesome.min.css"> <link rel="stylesheet" href="../css/style.css"> </head> <body class="hold-transition"> <div id="app"> <div class="content-header"> <h1>圖書管理</h1> </div> <div class="app-container"> <div class="box"> <div class="filter-container"> <el-input placeholder="圖書名稱" v-model="pagination.queryString" style="width: 200px;" class="filter-item"></el-input> <el-button @click="getAll()" class="dalfBut">查詢</el-button> <el-button type="primary" class="butT" @click="handleCreate()">新建</el-button> </div> <el-table size="small" current-row-key="id" :data="dataList" stripe highlight-current-row> <el-table-column type="index" align="center" label="序號"></el-table-column> <el-table-column prop="type" label="圖書類別" align="center"></el-table-column> <el-table-column prop="name" label="圖書名稱" align="center"></el-table-column> <el-table-column prop="description" label="描述" align="center"></el-table-column> <el-table-column label="操作" align="center"> <template slot-scope="scope"> <el-button type="primary" size="mini" @click="handleUpdate(scope.row)">編輯</el-button> <el-button type="danger" size="mini" @click="handleDelete(scope.row)">刪除</el-button> </template> </el-table-column> </el-table> <!-- 新增標簽彈層 --> <div class="add-form"> <el-dialog title="新增圖書" :visible.sync="dialogFormVisible"> <el-form ref="dataAddForm" :model="formData" :rules="rules" label-position="right" label-width="100px"> <el-row> <el-col :span="12"> <el-form-item label="圖書類別" prop="type"> <el-input v-model="formData.type"/> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="圖書名稱" prop="name"> <el-input v-model="formData.name"/> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="24"> <el-form-item label="描述"> <el-input v-model="formData.description" type="textarea"></el-input> </el-form-item> </el-col> </el-row> </el-form> <div slot="footer" class="dialog-footer"> <el-button @click="dialogFormVisible = false">取消</el-button> <el-button type="primary" @click="handleAdd()">確定</el-button> </div> </el-dialog> </div> <!-- 編輯標簽彈層 --> <div class="add-form"> <el-dialog title="編輯檢查項" :visible.sync="dialogFormVisible4Edit"> <el-form ref="dataEditForm" :model="formData" :rules="rules" label-position="right" label-width="100px"> <el-row> <el-col :span="12"> <el-form-item label="圖書類別" prop="type"> <el-input v-model="formData.type"/> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="圖書名稱" prop="name"> <el-input v-model="formData.name"/> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="24"> <el-form-item label="描述"> <el-input v-model="formData.description" type="textarea"></el-input> </el-form-item> </el-col> </el-row> </el-form> <div slot="footer" class="dialog-footer"> <el-button @click="dialogFormVisible4Edit = false">取消</el-button> <el-button type="primary" @click="handleEdit()">確定</el-button> </div> </el-dialog> </div> </div> </div> </div> </body> <!-- 引入組件庫 --> <script src="../js/vue.js"></script> <script src="../plugins/elementui/index.js"></script> <script type="text/javascript" src="../js/jquery.min.js"></script> <script src="../js/axios-0.18.0.js"></script> <script> var vue = new Vue({ el: '#app', data:{ pagination: {}, dataList: [],//當前頁要展示的列表數據 formData: {},//表單數據 dialogFormVisible: false,//控製表單是否可見 dialogFormVisible4Edit:false,//編輯表單是否可見 rules: {//校驗規則 type: [{ required: true, message: '圖書類別為必填項', trigger: 'blur' }], name: [{ required: true, message: '圖書名稱為必填項', trigger: 'blur' }] } }, //鉤子函數,VUE對象初始化完成後自動執行 created() { this.getAll(); }, methods: { //列表 getAll() { }, //彈出添加視窗 handleCreate() { }, //重置表單 resetForm() { }, //添加 handleAdd () { }, //彈出編輯視窗 handleUpdate(row) { }, //編輯 handleEdit() { }, // 刪除 handleDelete(row) { } } }) </script> </html>
-
SpringMVC 攔截時放行頁面請求
需要在 SpringMVC配置類中配置包掃描
@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/"); } }
4.2 列表功能
-
非同步請求
//列表 getAll() { // 發送ajax請求 axios.get("/books").then((res)=>{ // 註意第二個data是實體類中封裝的屬性data,二者意義不同 this.dataList = res.data.data; }); },
4.3 添加功能
-
頁面元素
//彈出添加視窗 handleCreate() { this.dialogFormVisible = true; // 跳出新增視窗時對視窗中輸入框的表單內容進行一次清除 this.resetForm(); }, //重置表單,清理之前添加的數據 resetForm() { this.formData = {}; },
-
非同步請求
//添加 handleAdd () { // 發送ajax請求 axios.post("/books", this.formData).then((res)=>{ // alert(res.data.code); if (res.data.code == 20011) { // 如果操作成功,關閉彈層 this.dialogFormVisible = false; this.$message.success("添加成功"); }else if (res.data.code == 20010) { // 此處後臺沒有給出響應的msg,手動補充 this.$message.error("添加失敗,數據不合法"); }else { this.$message.error(res.data.msg); } }).finally(()=>{ // 重新顯示數據 this.getAll(); }); },
4.4 添加、修改、刪除功能狀態處理
-
存在問題
需要判斷SQL語句執行成功或者失敗條件
-
解決方案:對Dao和Service部分進行修改
-
Dao
@Insert("insert into tbl_book values(null,#{type},#{name},#{description})") // @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);
-
Service
@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; }
-
4.5 修改功能
-
頁面元素
//彈出編輯視窗 handleUpdate(row) { // console.log(row); // 根據id查詢數據 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); } }); },
-
非同步請求
//編輯 handleEdit() { // 發送ajax請求 axios.put("/books", this.formData).then((res)=>{ // alert(res.data.code); 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(); }); },
4.6 刪除功能
// 刪除
handleDelete(row) {
// 彈出提示框
this.$confirm("此操作將永久刪除數據,請確認","註意",{
type:'info'
}).then(()=>{
// 確認刪除
// 發送Ajax請求
axios.delete("/books/" + row.id).then((res)=>{
if (res.data.code == 20021) {
this.$message.success("刪除成功");
} else {
this.$message.error("刪除失敗");
}
}).finally(()=>{
// 重新顯示數據
this.getAll();
});
}).catch(()=>{
// 取消刪除
this.$message.info("刪除已取消");
});
}
5、攔截器
5.1 攔截器概念
-
攔截器概念:攔截器(Interceptor)是一種動態攔截方法調用的機制,在SpringMVC中動態攔截控制器方法的執行
-
攔截器作用:(做增強)
- 在指定的方法調用前後執行預先設定的代碼
- 阻止原始方法的執行
-
攔截器和過濾器的區別
- 歸屬不同:Filter屬於Servlet技術,Interceptor屬於SpringMVC技術
- 攔截內容不同:Filter對所有訪問進行增強,Interceptor僅針對SpringMVC的訪問進行增強(取決於 web 伺服器配置類中 SpringMVC 的訪問內容設置)
-
攔截器圖示
-
SpringMVC與攔截器:訪問前後執行操作
-
工作流程:棧順序
-
5.2 攔截器入門案例
5.2.1 環境準備
- 需要 SpringMVC 配置類和已經配置好的Controller,筆者使用上面的 SSM 整合案例的代碼進行演示
5.2.2 攔截器開發
-
創建攔截器類
- 攔截器一般寫在 controller 包下,一般只給 controller 用
- 攔截器也可以寫在其他位置,但是要保證 SpringMVC 配置類的包掃描可以掃描到
package priv.dandelion.controller.interceptor; // import ... @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"); } }
-
配置攔截器類
- 此處寫在了 SpringMvcSupport 中,註意配置類註解
- 要保證 SpringMVC 配置類的包掃描可以掃描到
@Configuration public class SpringMvcSupport extends WebMvcConfigurationSupport { @Autowired private ProjectInterceptor projectInterceptor; // 配置攔截器 @Override protected void addInterceptors(InterceptorRegistry registry) { // 使用到的兩個參數均為可變參數,可以直接寫多個,addResourceHandlers()中相同,不再贅述 // registry.addInterceptor(projectInterceptor).addPathPatterns("/books", "/books/*"); // 也可以採用這種形式,攔截/books,/books/*,/books/*/*... registry.addInterceptor(projectInterceptor).addPathPatterns("/books/**"); } @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/"); } }
5.2.3 攔截器攔截規則
-
執行順序
- 執行
preHandle()
【返回 true】 - 執行原始方法
- 執行
postHandle()
- 執行
afterCompletion()
- 執行
-
boolean preHandle()
方法preHandle()
返回 true 時,按照上面的執行順序執行preHandle()
返回 true 時,中止原始操作的執行,原始操作後的攔截器操作也不執行
-
添加攔截並設定攔截的訪問路徑時
- 假設攔截內容為
/book
,當使用 Rest 風格時,GET/books
與 POST/book/1
不同,/book/1
不會被攔截 - 假設攔截內容為
/book*
,當使用 Rest 風格時,PUT/books/1
與 POST/book/1
都會被攔截
- 假設攔截內容為
-
補充:執行流程圖解
5.2.4 簡化SpringMvcSupport的編寫
- 可以直接在 SpringMvcConfig 中繼承 WebMvcConfigurer 介面,覆寫相應方法,效果相同
- 相比 SpringMvcSupport 具有一定侵入性
@Configuration
@ComponentScan("priv.dandelion.controller")
@EnableWebMvc
public class SpringMvcConfig implements WebMvcConfigurer {
@Autowired
private ProjectInterceptor projectInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(projectInterceptor).addPathPatterns("/books/**");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
}
}
5.3 攔截器參數
攔截器代碼見 5.2.2
5.3.1 前置處理方法
-
request:請求對象,獲取請求數據中的內容,如獲取請求頭的
Content-Type
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String contentType = request.getHeader("Content-Type"); System.out.println("preHandle..."+contentType); return true; }
-
response:響應對象,同 request
-
handler:被調用的處理器對象,本質上是一個方法對象,對反射中的Method對象進行了再包裝。可以獲取方法的相關信息
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HandlerMethod hm = (HandlerMethod)handler; String methodName = hm.getMethod().getName();//可以獲取方法的名稱 System.out.println("preHandle..."+methodName); return true; }
5.3.2 後置處理方法
- ModelAndView modelAndView:如果處理器執行完成具有返回結果,可以讀取到對應數據與頁面信息,併進行調整,目前開發返回 JSON 數據較多,其使用率不高
5.3.3 完成處理方法
- Exception ex:如果處理器執行過程中出現異常對象,可以針對異常情況進行單獨處理,如表現層拋出的異常。現在已經有全局異常處理器類,所以該參數的使用率也不高。
5.4 攔截器鏈運行順序
-
規則
- 當配置多個攔截器時,形成攔截器鏈
- 攔截器鏈的運行順序參照攔截器添加順序為準
- 當攔截器中出現對原始處理器的攔截,後面的攔截器均終止運行
- 當攔截器運行中斷,僅運行配置在前面的攔截器的 afterCompletion 操作
-
圖解