SpringMvc 攔截器開發和使用總結


我們在開發 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 包文件。

搭建好的項目工程整體目錄比較簡單,具體如下圖所示:

image

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM