我們在開發 SpringMvc 網站或接口時,肯定會遇到這樣的情況:有些頁面或者接口時需要登錄后才能訪問的,或者需要有權限才能訪問的,在不改變原有 Controller 方法代碼的情況下,使用 SpringMvc 攔截器是一個很不錯的選擇。
SpringMvc 的攔截器也是 Aop 切面編程思想的一種體現。SpringMvc 攔截器非常類似於 Servlet 的過濾器。兩者之間的區別在於:過濾器依賴於 Servlet 容器,在實現上是基於函數的回調,只能在 Servlet 請求的前后起作用。攔截器是依賴於 SpringMvc 框架,在實現上是基於 Java 的反射機制,可以使用 Spring 的依賴注入的任何資源、事務管理、異常處理等相關技術,比 Servlet 的功能更加強大。因此在使用 SpringMvc 框架構建的應用程序中,優先使用攔截器。
本篇博客采用純注解的方式編寫簡單的 Demo ,通過代碼的方式展示 SpringMvc 攔截器的運行過程,以及演示如何采用攔截器解決實際開發中驗證權限的處理方案。在本篇博客的最后會提供源代碼的下載地址。
一、搭建工程
新建一個 maven 項目,導入相關 jar 包,我所導入的 jar 包都是最新的,內容如下:
有關具體的 jar 包地址,可以在 https://mvnrepository.com 上進行查詢。
<dependencies>
<!--導入 servlet 相關的 jar 包-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
<scope>provided</scope>
</dependency>
<!--導入 Spring 核心 jar 包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.18</version>
</dependency>
<!--導入 SpringMvc 的 jar 包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.18</version>
</dependency>
<!--導入 jackson 相關的 jar 包-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.1</version>
</dependency>
</dependencies>
配置好引用的 jar 包后,打開右側的 Maven 窗口,刷新一下,這樣 Maven 會自動下載所需的 jar 包文件。
搭建好的項目工程整體目錄比較簡單,具體如下圖所示:

com.jobs.config 包下存儲的是 SpringMvc 的配置文件和 Servlet 的初始化文件
com.jobs.controller 包下存儲的是用於提供 api 接口的類
com.jobs.domain 包下存儲的是 JavaBean 實體類
web 目錄下放置的是網站文件,只有一個靜態頁面和一些 js 文件
二、SpringMvc 配置相關
com.jobs.config 下的 SpringMvcConfig 類是 SpringMvc 的配置類,具體內容如下:
package com.jobs.config;
import com.jobs.interceptor.MyInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
@Configuration
@ComponentScan("com.jobs")
//啟用 mvc 功能,配置了該注解之后,SpringMvc 攔截器放行相關資源的設置,才會生效
@EnableWebMvc
public class SpringMvcConfig implements WebMvcConfigurer {
//配置 SpringMvc 連接器放行常用資源的格式(圖片,js,css)
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
//配置響應數據格式所對應的數據處理轉換器
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
//如果響應的是 application/json ,則使用 jackson 轉換器進行自動處理
MappingJackson2HttpMessageConverter jsonConverter =
new MappingJackson2HttpMessageConverter();
jsonConverter.setDefaultCharset(Charset.forName("UTF-8"));
List<MediaType> typelist1 = new ArrayList<>();
typelist1.add(MediaType.APPLICATION_JSON);
jsonConverter.setSupportedMediaTypes(typelist1);
converters.add(jsonConverter);
//如果響應的是 text/html 和 text/plain ,則使用字符串文本轉換器自動處理
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
stringConverter.setDefaultCharset(Charset.forName("UTF-8"));
List<MediaType> typelist2 = new ArrayList<>();
typelist2.add(MediaType.TEXT_HTML);
typelist2.add(MediaType.TEXT_PLAIN);
stringConverter.setSupportedMediaTypes(typelist2);
converters.add(stringConverter);
}
//配置攔截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
//添加攔截器(可以添加多個攔截器,攔截器的執行順序,就是添加順序)
MyInterceptor myInterceptor = new MyInterceptor();
//設置攔截器攔截的請求路徑(此處配置攔截 test 下的所有請求)
registry.addInterceptor(myInterceptor).addPathPatterns("/test/*");
//設置攔截器排除的攔截路徑
//registry.addInterceptor(testInterceptor).excludePathPatterns("/");
/*
設置攔截器的攔截路徑,支持 * 和 ** 通配
配置值 /** 表示攔截所有映射
配置值 /* 表示攔截所有 / 開頭的映射
配置值 /test/* 表示攔截所有 /test/ 開頭的映射
配置值 /test/get* 表示攔截所有 /test/ 開頭,且具體映射名稱以 get 開頭的映射
配置值 /test/*job 表示攔截所有 /test/ 開頭,且具體映射名稱以 job 結尾的映射
*/
}
//注解配置 SpringMvc 返回配置的字符串所表示的頁面,從哪些去找
//可以注釋掉下面的方法,這樣需要在 SpringMvc 方法返回時,指定全局路徑的頁面地址
//這里配置的是:根據 SpringMvc 方法返回的字符串,到 /WEB-INF/pages/ 下找對應名稱的 jsp 頁面
@Bean
public InternalResourceViewResolver getViewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/pages/");
viewResolver.setSuffix(".jsp");
//如果頁面需要使用JSTL標簽庫的話
//viewResolver.setViewClass(JstlView.class);
return viewResolver;
}
}
如上面的代碼所示,在 SpringMvc 的配置類中通過重寫 addInterceptors 方法來添加自定義的攔截器,設置攔截器生效的請求地址和忽略的請求地址。根據實際業務需要,可以添加多個攔截器,攔截器的執行順序依賴於添加的順序。
ServletInitConfig 類初始化 Servlet 容器,裝載 SpringMvc 的配置,具體如下:
package com.jobs.config;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer;
import javax.servlet.*;
import java.util.EnumSet;
public class ServletInitConfig extends AbstractDispatcherServletInitializer {
//初始化 Servlet 容器,加載 SpringMvc 配置類
//創建 web 專用的 Spring 容器對象:WebApplicationContext
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext cwa = new AnnotationConfigWebApplicationContext();
cwa.register(SpringMvcConfig.class);
return cwa;
}
//注解配置 SpringMvc 的 DispatcherServlet 攔截地址,攔截所有請求
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
@Override
protected WebApplicationContext createRootApplicationContext() {
return null;
}
//添加過濾器
@Override
protected Filter[] getServletFilters() {
//采用 utf-8 作為統一請求的編碼
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
//該過濾器,能夠讓 web 頁面通過 _method 參數將 Post 請求轉換為 Put、Delete 等請求
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
return new Filter[]{characterEncodingFilter, hiddenHttpMethodFilter};
}
}
三、攔截器開發細節
首先列出 domain 包下的 MyResult 類的細節,主要用來定義返回的 Json 數據結構:
package com.jobs.domain;
public class MyResult {
private Integer status;
private String msg;
public MyResult() {
}
public MyResult(Integer status, String msg) {
this.status = status;
this.msg = msg;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
@Override
public String toString() {
return "MyResult{" +
"status=" + status +
", msg='" + msg + '\'' +
'}';
}
}
編寫 TestController 類用來接收請求,驗證攔截器的功能開發,內容如下:
package com.jobs.controller;
import com.jobs.domain.MyResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/test")
@RestController
public class TestController {
//測試 SpringMvc 攔截器的執行流程
//該請求可以成功運行,在控制台打印出攔截器執行的全過程
@RequestMapping("/success")
public MyResult testInterceptor1() {
System.out.println("Controller 中的 success 請求執行...");
MyResult result = new MyResult(0, "接口請求成功");
//攔截器的 3 個方法 preHandle、postHandle、afterCompletion 都運行完畢后,
//才會運行 return 語句,返回結果給調用者
return result;
}
//該請求不能成功執行,因為會被 MyInterceptor 攔截器給攔截了,會跳轉到 fail.jsp 頁面
@RequestMapping("/fail")
public MyResult testInterceptor2() {
//下面的代碼,由於攔截器攔截了,所以不會執行。
System.out.println("Controller 中的 fail 請求執行...");
MyResult result = new MyResult(0, "接口請求成功");
return result;
}
//模擬用戶沒有權限訪問該資源
//如果是 get 請求,將跳轉到 fail.jsp 頁面
//如果是 post 或 其它請求方式,將返回 json 數據(內容為沒有權限,不允許訪問)
@RequestMapping("/deny")
public MyResult testInterceptor3()
{
//下面的代碼,由於攔截器攔截了,所以不會執行。
System.out.println("Controller 中的 deny 請求執行...");
MyResult result = new MyResult(0, "接口請求成功");
return result;
}
}
下面列出攔截器 MyInterceptor 的開發細節,需要實現 HandlerInterceptor 接口即可,內容如下:
package com.jobs.interceptor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jobs.domain.MyResult;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//該攔截器被配置為攔截 /test1 下的所有請求
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String uri = request.getRequestURI();
if (uri.contains("success")) {
System.out.println("攔截器前置執行...放行 /test1/success 請求");
return true;
} else if (uri.contains("fail")) {
System.out.println("攔截器前置執行...攔截 /test1/fail 請求");
request.setAttribute("data",
"我請求 /test1/fail,被 MyInterceptor 攔截器攔截,跳轉到這里的");
//跳轉到指定頁面
request.getRequestDispatcher("/WEB-INF/pages/fail.jsp").forward(request, response);
//此處返回 false 后,將不會再執行 postHandle 和 afterCompletion
return false;
} else {
System.out.println("攔截器前置執行...攔截 "
+ uri + " 請求,當前的請求方式為:" + request.getMethod());
//獲取用戶的請求方式 get,post,put,delete 等等
String method = request.getMethod().toLowerCase();
if ("get".equals(method)) {
//如果是 get 請求的話,則直接跳轉到相關頁面
request.setAttribute("data", "我請求 "
+ uri + ",被 MyInterceptor 攔截器攔截,跳轉到這里的");
//跳轉到指定頁面
request.getRequestDispatcher("/WEB-INF/pages/fail.jsp").forward(request, response);
} else {
//如果是 post,put,delete 等相關請求,則直接返回 json 數據
MyResult result = new MyResult(1, "你沒有權限,不能訪問");
//返回 json數據
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(result);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(json);
}
//此處返回 false 后,將不會再執行 postHandle 和 afterCompletion
return false;
}
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("攔截器后置執行...");
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
System.out.println("攔截器執行完畢...");
}
}
四、驗證攔截器的頁面細節
網站的主頁 index.html 靜態頁面,以及所引用的 js 文件的內容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>SpringMvc攔截器演示</title>
</head>
<body>
<h1>該 Demo 演示 SpringMvc 攔截器的使用</h1>
請求 /test/success 接口,從控制台可以看到攔截器執行的全過程:<br/>
<a href="/test/success">請求test下的success接口</a><br/>
<hr/>
請求 /test/fail 接口,被攔截器攔截,會跳轉到 fail.jsp 頁面,從控制台能夠看到攔截器的執行過程<br/>
<a href="/test/fail">請求test下的fail接口</a><br/>
<hr/>
請求 /test/deny 模擬用戶沒有權限,不能訪問的應用場景。<br>
如果是 get 請求的話,會跳轉到 fail.jsp 頁面:<br/>
<a href="/test/deny">get請求test下的deny接口</a><br/>
如果是非 get 請求的化,會返回 json 數據,告訴用戶沒有訪問權限:<br/>
<a href="javascript:void(0);" id="test1">Post請求test下的deny接口</a><br/>
<a href="javascript:void(0);" id="test2">Put請求test下的deny接口</a><br/>
<a href="javascript:void(0);" id="test3">Delete請求test下的deny接口</a><br/>
<script src="./js/jquery-3.6.0.min.js"></script>
<script src="./js/apitest.js"></script>
</body>
</html>
$(function () {
$('#test1').click(function () {
$.ajax({
type: "post",
url: "/test/deny",
//如果沒有指定 dataType ,
//則服務器會自動根據接口返回的 mime 類型,推斷返回的數據類型
success: function (data) {
alert("post請求返回的數據:" + data.status + "," + data.msg);
}
});
});
$('#test2').click(function () {
$.ajax({
type: "post",
url: "/test/deny",
data: {_method: "put"},
dataType: "json", //服務器接口返回的是 json 類型的數據
success: function (data) {
alert("put請求返回的數據:" + data.status + "," + data.msg);
}
});
});
$('#test3').click(function () {
$.ajax({
type: "post",
url: "/test/deny",
data: {_method: "delete"},
dataType: "json", //服務器接口返回的是 json 類型的數據
success: function (data) {
alert("delete請求返回的數據:" + data.status + "," + data.msg);
}
});
});
})
Get 請求被攔截后,跳轉的頁面 fail.jsp 內容為:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>這里是 fail.jsp 頁面,攔截器未放行后,會跳轉到該頁面</h1>
獲取到的 data 值為:${data}
</body>
</html>
然后運行網站,在首頁 index.html 靜態頁面中,即可進行攔截器的測試。
如果攔截器正常全部運行的話,執行的順序為:
preHandle ---> controller 的方法 ---> postHandle -> afterCompletion ---> 返回結果給調用者
如果在攔截器的 preHandle 方法中返回 false ,則后面的方法都不會執行。
此時在 preHandle 方法中可以跳轉頁面或者直接返回數據給調用者。
到此為止,有關從 SpringMvc 攔截器的開發使用,已經介紹完畢。希望對大家有所幫助。
本篇博客的 Demo 源代碼下載地址為:https://files.cnblogs.com/files/blogs/699532/SpringMvc_Interceptor.zip
