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>