Java開發學習(二十八)----攔截器(Interceptor)詳細解析

2022-08-30 21:01:36

一、攔截器概念

講解攔截器的概念之前,我們先看一張圖:

(1)瀏覽器傳送一個請求會先到Tomcat的web伺服器

(2)Tomcat伺服器接收到請求以後,會去判斷請求的是靜態資源還是動態資源

(3)如果是靜態資源,會直接到Tomcat的專案部署目錄下去直接存取

(4)如果是動態資源,就需要交給專案的後臺程式碼進行處理

(5)在找到具體的方法之前,我們可以去設定過濾器(可以設定多個),按照順序進行執行

(6)然後進入到到中央處理器(SpringMVC中的內容),SpringMVC會根據設定的規則進行攔截

(7)如果滿足規則,則進行處理,找到其對應的controller類中的方法進行執行,完成後返回結果

(8)如果不滿足規則,則不進行處理

(9)這個時候,如果我們需要在每個Controller方法執行的前後新增業務,具體該如何來實現?

這個就是攔截器要做的事。

  • 攔截器(Interceptor)是一種動態攔截方法呼叫的機制,在SpringMVC中動態攔截控制器方法的執行

  • 作用:

    • 在指定的方法呼叫前後執行預先設定的程式碼,例如在方法前後增加功能

    • 阻止原始方法的執行,例如許可權校驗

    • 總結:攔截器就是用來做增強

看完以後,大家會發現

  • 攔截器和過濾器在作用和執行順序上也很相似

攔截器和過濾器之間的區別是什麼? 

  • 歸屬不同:Filter屬於Servlet技術,Interceptor屬於SpringMVC技術

  • 攔截內容不同:Filter對所有存取進行增強,Interceptor僅針對SpringMVC的存取進行增強

二、攔截器入門案例

2.1 環境準備

  • 建立一個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'}";
        }
    }

最終建立好的專案結構如下:

2.2 攔截器開發

步驟1:建立攔截器類

讓類實現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容器掃描到。

步驟2:設定攔截器類
@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" );
    }
}
步驟3:SpringMVC新增SpringMvcSupport包掃描
@Configuration
@ComponentScan({"com.itheima.controller","com.itheima.config"})
@EnableWebMvc
public class SpringMvcConfig{
   
}
步驟4:執行程式測試

使用PostMan傳送http://localhost/books

如果傳送http://localhost/books/100會發現攔截器沒有被執行,原因是攔截器的addPathPatterns方法設定的攔截路徑是/books,我們現在傳送的是/books/100,所以沒有匹配上,因此沒有攔截,攔截器就不會執行。

步驟5:修改攔截器攔截規則
@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,則代表攔截,後面的就不會再執行了。

步驟6:簡化SpringMvcSupport的編寫
@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,則直接跳過後面方法的執行。

三、攔截器引數

3.1 前置處理方法

原始方法之前執行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;
}

3.2 後置處理方法

原始方法執行後執行,如果原始方法被攔截,則不執行

public void postHandle(HttpServletRequest request,
                       HttpServletResponse response,
                       Object handler,
                       ModelAndView modelAndView) throws Exception {
    System.out.println("postHandle");
}

前三個引數和上面的是一致的。

modelAndView:如果處理器執行完成具有返回結果,可以讀取到對應資料與頁面資訊,並進行調整

因為咱們現在都是返回json資料,所以該引數的使用率不高。

3.3 完成處理方法

攔截器最後執行的方法,無論原始方法是否執行

public void afterCompletion(HttpServletRequest request,
                            HttpServletResponse response,
                            Object handler,
                            Exception ex) throws Exception {
    System.out.println("afterCompletion");
}

前三個引數與上面的是一致的。

ex:如果處理器執行過程中出現異常物件,可以針對異常情況進行單獨處理,該引數的使用率也不高。

這三個方法中,最常用的是preHandle,在這個方法中可以通過返回值來決定是否要進行放行,我們可以把業務邏輯放在該方法中,如果滿足業務則返回true放行,不滿足則返回false攔截。

四、攔截器鏈設定

目前,我們在專案中只新增了一個攔截器,如果有多個,該如何設定?設定多個後,執行順序是什麼?

4.1 設定多個攔截器

步驟1:建立攔截器類

實現介面,並重寫介面中的方法

@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");
    }
}
步驟2:設定攔截器類
@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操作