SpringBoot中Interceptor和Filter的使用


SpringBoot中Interceptor和Filter的使用

如何使用攔截器和Filter

FIlter:過濾器,它是Servlet中的一個概念,主要的作用是對數據進行過濾、校驗、記錄日志,權限驗證等操作。

使用Filter

創建類,實現javax.servlet.Filter接口。

package cn.rayfoo.common.util.FileterController;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 在SpringBoot中通過注解注冊的方式簡單的使用Filter
 * @author rayfoo
 */
@WebFilter(urlPatterns = "/*", filterName = "myfilter")
public class FileterController implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("Filter初始化中");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        System.out.println("開始進行過濾處理");
        //調用該方法后,表示過濾器經過原來的url請求處理方法
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("處理后的操作");
    }

    @Override
    public void destroy() {
        System.out.println("Filter銷毀中");
    }
}

上述代碼中,重寫了Filter的三個方法,分別是:

  • init:在此Filter被創建時執行
  • doFilter:處理Filter的真正業務邏輯,可以在這個方法中對請求進行放行,在放行前后都可以執行代碼, 也可以在此方法中進行重定向和請求轉發,但是一旦使用了請求轉發、重定向,拋出異常,出現異常,被攔截的路徑對應的業務方法就不會被執行。
  • destory:在此FIlter被銷毀時執行

SpringBoot中使用Filter

1、在Filter上加入@WebFilter(urlPatterns = "/path 也可以是*", filterName = "filterName")注解,配置urlPatterns和filterName

2、在啟動類上加入@ServletComponentScan注解

在SpringBoot中使用Interceptor

1、創建類,實現HandlerInterceptor接口

2、Interceptor和Filter有所不同,HandlerInterceptor中有三個方法,由於JDK8之后支持了default關鍵字,其內的方

法都是使用default修飾的,不會提示我們手動重寫,需要點擊進入源碼找到並復制到上面創建的類中,修改defalut為public。

3、在攔截器上加上@Component注解

package cn.rayfoo.common.interceptor;

import cn.rayfoo.common.exception.MyException;
import cn.rayfoo.common.response.HttpStatus;
import cn.rayfoo.common.util.net.ClientUtil;
import cn.rayfoo.common.util.redis.RedisUtil;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.Nullable;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

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

/**
 * @author rayfoo@qq.com
 * @version 1.0
 * @date 2020/8/6 11:49
 * @description 登錄次數校驗攔截器
 */
@NoArgsConstructor
@Getter
@Slf4j
public class AccessInterceptor implements HandlerInterceptor {

    @Autowired
    private RedisUtil redisUtil;

    /**
     * 需要攔截的URL
     */
    private String InterceptorUrl;

    /**
     * 規定的時間范圍
     */
    private Long timeBound;

    /**
     * 在規定時間按內的訪問量
     */
    private Integer requestNum;


    /**
     * @param InterceptorUrl 要攔截的URL
     * @param timeBound      規定的時間范圍
     * @param requestNum     時間范圍內的請求次數超過多少時禁止訪問
     */
    public AccessInterceptor(String InterceptorUrl, Long timeBound, Integer requestNum) {
        this.InterceptorUrl = InterceptorUrl;
        this.timeBound = timeBound;
        this.requestNum = requestNum;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判斷該ip地址五分鍾內注冊了多少次 如果超過五次,將其ip列入黑名單十分鍾。 每注冊一次給此id加1
        String ipAddr = ClientUtil.getIpAddr(request) + InterceptorUrl;
        //判斷ip地址是否存在
        if (redisUtil.hasKey(ipAddr)) {
            //獲取五分鍾內獲取驗證碼的次數
            Integer count = (Integer) redisUtil.get(ipAddr);
            //如果在x分鍾內獲取了超過x次
            if (count > requestNum) {
                //獲取過期時間
                long expire = redisUtil.getExpire(ipAddr);
                log.error(ClientUtil.getIpAddr(request) + "對" + InterceptorUrl + "操作頻繁,此接口已將其暫時列入黑名單");
                //告知用戶限制時間還有多久
                throw MyException.builder().code(HttpStatus.INTERNAL_SERVER_ERROR.value()).msg("您的操作過於頻繁,請" + expire + "秒后重試!").build();
            }
            //不到五次就累加
            redisUtil.incr(ipAddr, 1L);
        } else {
            //不存在的話 創建
            redisUtil.set(ipAddr, 1L);
            //過期時間設置為time秒
            redisUtil.expire(ipAddr, timeBound);
        }

        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
    }


}

在Interceptor中有三個方法,

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception 
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception 

其中preHandle是在業務方法執行之前執行,返回true表示放行請求,返回false表示不執行攔截的方法, 在此方法中同樣可以進行重定向、轉發(此時都會返回false),異常拋出等操作,出現此三種情況,都不會執行業務方法。

postHandle是在業務方法執行之后,但是在視圖渲染之前執行,可以進行視圖的一些操作,其參數中提供了一個modelAndView可以修改視圖的路徑和渲染的值。

afterCompletion是在視圖渲染完成后執行的,可以進行關閉資源、日志記錄等操作。

注冊Interceptor

在SpringBoot2中,建議使用的注冊攔截器的方法有如下兩種:

  • 實現WebMvcConfigurer接口
  • 繼承WebMvcConfigurerAdapter類(此類也是實現了WebMvcConfigurer

下面介紹一下實現WebMvcConfigurer方法注冊攔截器

package cn.rayfoo.common.config;

import cn.rayfoo.common.interceptor.AccessInterceptor;
import cn.rayfoo.modules.base.interceptor.SMSValidateInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author rayfoo@qq.com
 * @version 1.0
 * @date 2020/8/6 9:43
 * @description 攔截器配置類
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    /**
     * 由於使用了其他依賴 將自定義的攔截器作為Bean寫入配置
     * @return
     */
    @Bean
    public SMSValidateInterceptor getSMSValidateInterceptor(){
        return new SMSValidateInterceptor();
    }

    /**
     * 短信接口的攔截器
     * @return
     */
    @Bean
    public AccessInterceptor getCodeAccessInterceptor(){
        return new AccessInterceptor("/user/code",300L,5);
    }

    /**
     * 注冊攔截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        //注冊短信驗證碼接口的請求次數攔截器
        AccessInterceptor codeAccessInterceptor = getCodeAccessInterceptor();
        registry.addInterceptor(codeAccessInterceptor)
                .addPathPatterns(codeAccessInterceptor.getInterceptorUrl());

        //注冊手機號校驗攔截器
        registry.addInterceptor(getSMSValidateInterceptor())
                .addPathPatterns("/user/code");

    }

}

在此類中,可以使用@Bean來創建多個攔截器對象,使用addInterceptors進行注冊。注冊時,需要提供攔截的路徑、不攔截的路徑。均為可選參數。

執行順序

Interceptor和Filter的執行順序是不同的,下面的圖很清晰的描述了他們的執行順序。

圖2來自博客

攔截器鏈的執行順序

當攔截器有2個或者兩個以上的時候,他們的順序如何指定,又如何指定攔截器的順序呢?

在攔截器注冊時,注冊的順序決定着他們執行的順序。先注冊的攔截器會先執行。具體的執行鏈可以參考下圖:

什么時候使用Filter?什么時候使用Interceptor

攔截器和Filter都是對AOP思想的一種體現,都可以進行權限校驗,日志記錄等工作。

  1. 對於沒有使用Spring的框架肯定是使用Filter
  2. 對於Spring項目可以根據Filter和Interceptor的執行順序來靈活使用
  3. 大量的請求塊信息處理使用filter, 特別的內部邏輯處理所使用aspect
  4. filter的作用范圍中可以包含aspect和interceptor
  5. 在攔截器中可以注入Spring中的Bean對象

Interceptor中如何使用SpringBean?

在攔截器中可以直接通過@Autowired注入Spring中的Bean對象,但是一定要注意一點:

注冊攔截器時,必須通過new的形式(包括上面用到的通過方法的返回值new+@Bean注解放入容器)創建攔截器。不能在攔截器上加入@Component注解在WebMvcConfigurer中配置。

Interceptor可以使用全局異常處理嗎?

當然是可以的,也可以在攔截器中拋出自定義異常交由全局異常處理處理,這樣就可以返回和Controller方法相同的返回值類型啦~


免責聲明!

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



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