講解攔截器的概念之前,我們先看一張圖:
(1)瀏覽器傳送一個請求會先到Tomcat的web伺服器
(2)Tomcat伺服器接收到請求以後,會去判斷請求的是靜態資源還是動態資源
(3)如果是靜態資源,會直接到Tomcat的專案部署目錄下去直接存取
(4)如果是動態資源,就需要交給專案的後臺程式碼進行處理
(5)在找到具體的方法之前,我們可以去設定過濾器(可以設定多個),按照順序進行執行
(6)然後進入到到中央處理器(SpringMVC中的內容),SpringMVC會根據設定的規則進行攔截
(7)如果滿足規則,則進行處理,找到其對應的controller類中的方法進行執行,完成後返回結果
(8)如果不滿足規則,則不進行處理
(9)這個時候,如果我們需要在每個Controller方法執行的前後新增業務,具體該如何來實現?
這個就是攔截器要做的事。
作用:
在指定的方法呼叫前後執行預先設定的程式碼,例如在方法前後增加功能
阻止原始方法的執行,例如許可權校驗
總結:攔截器就是用來做增強
看完以後,大家會發現
攔截器和過濾器在作用和執行順序上也很相似
攔截器和過濾器之間的區別是什麼?
歸屬不同:Filter屬於Servlet技術,Interceptor屬於SpringMVC技術
攔截內容不同:Filter對所有存取進行增強,Interceptor僅針對SpringMVC的存取進行增強
建立一個Web的Maven專案
pom.xml新增所需jar包
<?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>com.itheima</groupId>
<artifactId>springmvc_12_interceptor</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<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>
<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>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
建立對應的設定類
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
protected String[] getServletMappings() {
return new String[]{"/"};
}
//亂碼處理
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
return new Filter[]{filter};
}
}
@Configuration
@ComponentScan({"com.itheima.controller"})
@EnableWebMvc
public class SpringMvcConfig{
}
建立模型類Book
public class Book {
private String name;
private double price;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Book{" +
"書名='" + name + '\'' +
", 價格=" + price +
'}';
}
}
編寫Controller
@RestController
@RequestMapping("/books")
public class BookController {
@PostMapping
public String save(@RequestBody Book book){
System.out.println("book save..." + book);
return "{'module':'book save'}";
}
@DeleteMapping("/{id}")
public String delete(@PathVariable Integer id){
System.out.println("book delete..." + id);
return "{'module':'book delete'}";
}
@PutMapping
public String update(@RequestBody Book book){
System.out.println("book update..."+book);
return "{'module':'book update'}";
}
@GetMapping("/{id}")
public String getById(@PathVariable Integer id){
System.out.println("book getById..."+id);
return "{'module':'book getById'}";
}
@GetMapping
public String getAll(){
System.out.println("book getAll...");
return "{'module':'book getAll'}";
}
}
最終建立好的專案結構如下:
讓類實現HandlerInterceptor介面,重寫介面中的三個方法。
@Component
//定義攔截器類,實現HandlerInterceptor介面
//注意當前類必須受Spring容器控制
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...");
}
}
注意:攔截器類要被SpringMVC容器掃描到。
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Autowired
private ProjectInterceptor projectInterceptor;
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
}
@Override
protected void addInterceptors(InterceptorRegistry registry) {
//設定攔截器
registry.addInterceptor(projectInterceptor).addPathPatterns("/books" );
}
}
@Configuration
@ComponentScan({"com.itheima.controller","com.itheima.config"})
@EnableWebMvc
public class SpringMvcConfig{
}
使用PostMan傳送http://localhost/books
如果傳送http://localhost/books/100
會發現攔截器沒有被執行,原因是攔截器的addPathPatterns
方法設定的攔截路徑是/books
,我們現在傳送的是/books/100
,所以沒有匹配上,因此沒有攔截,攔截器就不會執行。
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Autowired
private ProjectInterceptor projectInterceptor;
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
}
@Override
protected void addInterceptors(InterceptorRegistry registry) {
//設定攔截器
registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*" );
}
}
這個時候,如果再次存取http://localhost/books/100
,攔截器就會被執行。
注意:攔截器中的preHandler
方法,如果返回true,則代表放行,會執行原始Controller類中要請求的方法,如果返回false,則代表攔截,後面的就不會再執行了。
@Configuration
@ComponentScan({"com.itheima.controller"})
@EnableWebMvc
//實現WebMvcConfigurer介面可以簡化開發,但具有一定的侵入性
public class SpringMvcConfig implements WebMvcConfigurer {
@Autowired
private ProjectInterceptor projectInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//設定多攔截器
registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*");
}
}
此後咱們就不用再寫SpringMvcSupport
類了。
最後我們來看下攔截器的執行流程:
當有攔截器後,請求會先進入preHandle方法,
如果方法返回true,則放行繼續執行後面的handle[controller的方法]和後面的方法
如果返回false,則直接跳過後面方法的執行。
原始方法之前執行preHandle
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
System.out.println("preHandle");
return true;
}
request:請求物件
response:響應物件
handler:被呼叫的處理器物件,本質上是一個方法物件,對反射中的Method物件進行了再包裝
使用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;
}
使用handler引數,可以獲取方法的相關資訊
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;
}
原始方法執行後執行,如果原始方法被攔截,則不執行
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("postHandle");
}
前三個引數和上面的是一致的。
modelAndView:如果處理器執行完成具有返回結果,可以讀取到對應資料與頁面資訊,並進行調整
因為咱們現在都是返回json資料,所以該引數的使用率不高。
攔截器最後執行的方法,無論原始方法是否執行
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
System.out.println("afterCompletion");
}
前三個引數與上面的是一致的。
ex:如果處理器執行過程中出現異常物件,可以針對異常情況進行單獨處理,該引數的使用率也不高。
這三個方法中,最常用的是preHandle,在這個方法中可以通過返回值來決定是否要進行放行,我們可以把業務邏輯放在該方法中,如果滿足業務則返回true放行,不滿足則返回false攔截。
目前,我們在專案中只新增了一個攔截器,如果有多個,該如何設定?設定多個後,執行順序是什麼?
實現介面,並重寫介面中的方法
@Component
public class ProjectInterceptor2 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle...222");
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle...222");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion...222");
}
}
@Configuration
@ComponentScan({"com.itheima.controller"})
@EnableWebMvc
//實現WebMvcConfigurer介面可以簡化開發,但具有一定的侵入性
public class SpringMvcConfig implements WebMvcConfigurer {
@Autowired
private ProjectInterceptor projectInterceptor;
@Autowired
private ProjectInterceptor2 projectInterceptor2;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//設定多攔截器
registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*");
registry.addInterceptor(projectInterceptor2).addPathPatterns("/books","/books/*");
}
}
步驟3:執行程式,觀察順序
攔截器執行的順序是和設定順序有關。先進後出。
preHandle:與設定順序相同,必定執行
postHandle:與設定順序相反,可能不執行
afterCompletion:與設定順序相反,可能不執行。
這個順序不太好記,最終只需要把握住一個原則即可:以最終的執行結果為準
當設定多個攔截器時,形成攔截器鏈
攔截器鏈的執行順序參照攔截器新增順序為準
當攔截器中出現對原始處理器的攔截,後面的攔截器均終止執行
當攔截器執行中斷,僅執行設定在前面的攔截器的afterCompletion操作
本文來自部落格園,作者:|舊市拾荒|,轉載請註明原文連結:https://www.cnblogs.com/xiaoyh/p/16444681.html