【SpringMVC】 4.3 攔截器

二七零零發表於2021-02-25

SpringMVC學習記錄

注意:以下內容是學習 北京動力節點 的SpringMVC視訊後所記錄的筆記、原始碼以及個人的理解等,記錄下來僅供學習


第4章 SpringMVC 核心技術

4.3 攔截器

  SpringMVC 中的 Interceptor 攔截器是非常重要和相當有用的,它的主要作用是攔截指定的使用者請求,並進行相應的預處理與後處理。其攔截的時間點在“處理器對映器根據使用者提交的請求對映出了所要執行的處理器類,並且也找到了要執行該處理器類的處理器介面卡,在處理器介面卡執行處理器之前”。當然,在處理器對映器對映出所要執行的處理器類時,已經將攔截器與處理器組合為了一個處理器執行鏈,並返回給了中央排程器。

4.3.1 一個攔截器的執行

自定義攔截器

package com.bjpowernode.handler;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;

//攔截器類:攔截使用者的請求。
public class MyInterceptor implements HandlerInterceptor {

    private long btime = 0;
    /*
     * preHandle叫做預處理方法。
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        btime = System.currentTimeMillis();
        System.out.println("攔截器的MyInterceptor的preHandle()");
        //計算的業務邏輯,根據計算結果,返回true或者false
        //給瀏覽器一個返回結果     //request.getRequestDispatcher("/tips.jsp").forward(request,response);
        return true;
    }

    /*
       postHandle:後處理方法。
     */
    @Override
    public void postHandle(HttpServletRequest request,
                           HttpServletResponse response,
                           Object handler, ModelAndView mv) throws Exception {
        System.out.println("攔截器的MyInterceptor的postHandle()");
        //對原來的doSome執行結果,需要調整。
        if( mv != null){
            //修改資料
            mv.addObject("mydate",new Date());
            //修改檢視
            mv.setViewName("other");
        }
    }

    /*
      afterCompletion:最後執行的方法
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                Object handler, Exception ex) throws Exception {
        System.out.println("攔截器的MyInterceptor的afterCompletion()");

        long etime = System.currentTimeMillis();
        System.out.println("計算從preHandle到請求處理完成的時間:"+(etime - btime ));
    }
}

自定義攔截器,需要實現Handlerinterceptor介面。而該介面中含有三個方法:
1、 preHandle(request,response, Object handler) 預處理方法:
  該方法在處理器方法執行之前執行。其返回值為boolean,若為true,則緊接著會執行處理器方 法,且會將afterCompletion()方法放入到一個專門的方法棧中等待執行。

重要:
是整個專案的入口,門戶。 當preHandle返回true 請求可以被處理。 preHandle返回false,請求到此方法就截止。
引數:
 Object handler : 被攔截的控制器物件
返回值boolean
  true:請求是通過了攔截器的驗證,可以執行處理器方法。
  攔截器的MyInterceptor的preHandle()
  =====執行MyController中的doSome方法=====
  攔截器的MyInterceptor的postHandle()
  攔截器的MyInterceptor的afterCompletion()
 
  false:請求沒有通過攔截器的驗證,請求到達攔截器就截止了。 請求沒有被處理
  攔截器的MyInterceptor的preHandle()

特點:
	a.方法在控制器方法(MyController的doSome)之前先執行的。 使用者的請求首先到達此方法
	b.在這個方法中可以獲取請求的資訊, 驗證請求是否符合要求。可以驗證使用者是否登入, 驗證使用者是否有許可權訪問某個連線地址(url)。
如果驗證失敗,可以截斷請求,請求不能被處理。
如果驗證成功,可以放行請求,此時控制器方法才能執行。

2、postHandle(request/response/ Object handlei,modelAndView) 後處理方法:
 該方法在處理器方法執行之後執行。處理器方法若最終未被執行,則該方法不會執行。 由於該方法是在處理器方法執行完後執行,且該方法引數中包含ModelAndView,所以該方法可以修 改處理器方法的處理結果資料,且可以修改跳轉方向。

引數:
    Object handler:被攔截的處理器物件MyController
    ModelAndView mv:處理器方法的返回值

特點:
     a.在處理器方法之後執行的(MyController.doSome())
     b.能夠獲取到處理器方法的返回值ModelAndView,可以修改ModelAndView中的
     資料和檢視,可以影響到最後的執行結果。
     c.主要是對原來的執行結果做二次修正,

     ModelAndView mv = MyController.doSome();
     postHandle(request,response,handler,mv);

3、afterCompletion(request,response, Object handler, Exception ex)最後執行的方法:

 當preHandle()方法返回true時,會將該方法放到專門的方法棧中,等到對請求進行響應的所有 工作完成之後才執行該方法。即該方法是在中央排程器渲染(資料填充)了響應頁面之後執行的,此 時對ModelAndView再操作也對響應無濟於事。
afterCompletion**最後執行的方法,清除資源,例如在Controller方法中加入資料

引數
    Object handler:被攔截器的處理器物件
    Exception ex:程式中發生的異常
  特點:
   a.在請求處理完成後執行的。框架中規定是當你的檢視處理完成後,對檢視執行了forward。就認為請求處理完成。
   b.一般做資源回收工作的, 程式請求過程中建立了一些物件,在這裡可以刪除,把佔用的記憶體回收。

攔截器中方法與處理器方法的執行順序如下圖:

換一種表現方式,也可以這樣理解:

(1) 註冊攔截器

springmvc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!--宣告元件掃描器-->
    <context:component-scan base-package="com.bjpowernode.controller" />

    <!--宣告 springmvc框架中的檢視解析器, 幫助開發人員設定檢視檔案的路徑-->
    <bean  class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--字首:檢視檔案的路徑-->
        <property name="prefix" value="/WEB-INF/view/" />
        <!--字尾:檢視檔案的副檔名-->
        <property name="suffix" value=".jsp" />
    </bean>

    <!--宣告攔截器: 攔截器可以有0或多個-->
    <mvc:interceptors>
        <!--宣告第一個攔截器-->
        <mvc:interceptor>
            <!--指定攔截的請求uri地址
                path:就是uri地址,可以使用萬用字元 **
                      ** : 表示任意的字元,檔案或者多級目錄和目錄中的檔案
                http://localhost:8080/myweb/user/listUser.do
                http://localhost:8080/myweb/student/addStudent.do
            -->
            <mvc:mapping path="/**"/>
            <!--宣告攔截器物件-->
            <bean class="com.bjpowernode.handler.MyInterceptor" />
        </mvc:interceptor>
    </mvc:interceptors>

</beans>

mvc:mapping/用於指定當前所註冊的攔截器可以攔截的請求路徑,而 /**表示攔截所 有請求。

(2) 修改 index 頁面

index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    String basePath = request.getScheme() + "://" +
            request.getServerName() + ":" + request.getServerPort() +
            request.getContextPath() + "/";
%>
<html>
<head>
    <title>Title</title>
    <base href="<%=basePath%>" />
</head>
<body>
    <p>一個攔截器</p>
    <form action="some.do" method="post">
        姓名:<input type="text" name="name"> <br/>
        年齡:<input type="text" name="age"> <br/>
        <input type="submit" value="提交請求">
    </form>
</body>
</html>

(3) 修改處理器

MyController.java

package com.bjpowernode.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

/**
 * @RequestMapping:
 *    value : 所有請求地址的公共部分,叫做模組名稱
 *    位置: 放在類的上面
 */
@Controller
public class MyController {
    @RequestMapping(value = "/some.do")
    public ModelAndView doSome(String name,Integer age)  {
        System.out.println("=====執行MyController中的doSome方法=====");
        //處理some.do請求了。 相當於service呼叫處理完成了。
        ModelAndView mv  = new ModelAndView();
        mv.addObject("myname",name);
        mv.addObject("myage",age);
        mv.setViewName("show");
        return mv;
    }
}

(4) 修改 show 頁面

show.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h3>/WEB-INF/view/show.jsp從request作用域獲取資料</h3><br/>
    <h3>myname資料:${myname}</h3><br/>
    <h3>myage資料:${myage}</h3>
</body>
</html>

4.3.2 多個攔截器的執行

專案在4.3.1的基礎上
專案結構

(1) 再定義一個攔截器

MyInterceptor2 .java

package com.bjpowernode.handler;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

//攔截器類:攔截使用者的請求。
public class MyInterceptor2 implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("22222-攔截器的MyInterceptor的preHandle()");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request,
                           HttpServletResponse response,
                           Object handler, ModelAndView mv) throws Exception {
        System.out.println("22222-攔截器的MyInterceptor的postHandle()");
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                Object handler, Exception ex) throws Exception {
        System.out.println("22222-攔截器的MyInterceptor的afterCompletion()");
    }
}

(2) 多個攔截器的註冊與執行

springmvc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!--宣告元件掃描器-->
    <context:component-scan base-package="com.bjpowernode.controller" />

    <!--宣告 springmvc框架中的檢視解析器, 幫助開發人員設定檢視檔案的路徑-->
    <bean  class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--字首:檢視檔案的路徑-->
        <property name="prefix" value="/WEB-INF/view/" />
        <!--字尾:檢視檔案的副檔名-->
        <property name="suffix" value=".jsp" />
    </bean>

    <!--宣告攔截器: 攔截器可以有0或多個
        在框架中儲存多個攔截器是ArrayList,
        按照宣告的先後順序放入到ArrayList
    -->
    <mvc:interceptors>
        <!--宣告第一個攔截器-->
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <!--宣告攔截器物件-->
            <bean class="com.bjpowernode.handler.MyInterceptor" />
        </mvc:interceptor>
        <!--宣告第二個攔截器-->
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <bean class="com.bjpowernode.handler.MyInterceptor2" />
        </mvc:interceptor>
    </mvc:interceptors>

</beans>

當有多個攔截器時,形成攔截器鏈。攔截器鏈的執行順序,與其註冊順序一致。需要再次強調一點的是,當某一個攔截器的peHandle()方法返回 true 並被執行到時,會向一個專門的方法棧中放入該攔截器的 afterCompletion()方法。
多個攔截器中方法與處理器方法的執行順序如下圖:

從圖中可以看出,只要有一個preHandle()方法返回false,則上部的執行鏈將被斷開, 其後續的處理器方法與postHandle()方法將無法執行。但,無論執行鏈執行情況怎樣,只要 方法棧中有方法,即執行鏈中只要有preHandle()方法返回true,就會執行方法棧中的 afterCompletion()方法。最終都會給出響應。
換一種表現方式,也可以這樣理解:

4.3.3 公共資源

(1) pom.xml

<?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.bjpowernode</groupId>
  <artifactId>ch11-interceptor2</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <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>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <!--servlet依賴-->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
    <!--jsp依賴-->
    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>jsp-api</artifactId>
      <version>2.2.1-b03</version>
      <scope>provided</scope>
    </dependency>
    <!--springmvc依賴-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <!-- 編碼和編譯和JDK版本 -->
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

(2) web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!--宣告,註冊springmvc的核心物件DispatcherServlet
        需要在tomcat伺服器啟動後,建立DispatcherServlet物件的例項。
        為什麼要建立DispatcherServlet物件的例項呢?
        因為DispatcherServlet在他的建立過程中, 會同時建立springmvc容器物件,
        讀取springmvc的配置檔案,把這個配置檔案中的物件都建立好, 當使用者發起
        請求時就可以直接使用物件了。

        servlet的初始化會執行init()方法。 DispatcherServlet在init()中{
           //建立容器,讀取配置檔案
           WebApplicationContext ctx = new ClassPathXmlApplicationContext("springmvc.xml");
           //把容器物件放入到ServletContext中
           getServletContext().setAttribute(key, ctx);
        }


        啟動tomcat報錯,讀取這個檔案 /WEB-INF/springmvc-servlet.xml(/WEB-INF/myweb-servlet.xml)
        springmvc建立容器物件時,讀取的配置檔案預設是/WEB-INF/<servlet-name>-servlet.xml .
    -->
    <servlet>
        <servlet-name>myweb</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

        <!--自定義springmvc讀取的配置檔案的位置-->
        <init-param>
            <!--springmvc的配置檔案的位置的屬性-->
            <param-name>contextConfigLocation</param-name>
            <!--指定自定義檔案的位置-->
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>

        <!--在tomcat啟動後,建立Servlet物件
            load-on-startup:表示tomcat啟動後建立物件的順序。它的值是整數,數值越小,
                            tomcat建立物件的時間越早。 大於等於0的整數。
        -->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>myweb</servlet-name>
        <!--
            使用框架的時候, url-pattern可以使用兩種值
            1. 使用副檔名方式, 語法 *.xxxx , xxxx是自定義的副檔名。 常用的方式 *.do, *.action, *.mvc等等
               不能使用 *.jsp
               http://localhost:8080/myweb/some.do
               http://localhost:8080/myweb/other.do

            2.使用斜槓 "/"
        -->
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
</web-app>

相關文章