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思想的一種體現,都可以進行權限校驗,日志記錄等工作。
- 對於沒有使用Spring的框架肯定是使用Filter
- 對於Spring項目可以根據Filter和Interceptor的執行順序來靈活使用
- 大量的請求塊信息處理使用filter, 特別的內部邏輯處理所使用aspect
- filter的作用范圍中可以包含aspect和interceptor
- 在攔截器中可以注入Spring中的Bean對象
Interceptor中如何使用SpringBean?
在攔截器中可以直接通過@Autowired注入Spring中的Bean對象,但是一定要注意一點:
注冊攔截器時,必須通過new的形式(包括上面用到的通過方法的返回值new+@Bean注解放入容器)創建攔截器。不能在攔截器上加入@Component注解在WebMvcConfigurer中配置。
Interceptor可以使用全局異常處理嗎?
當然是可以的,也可以在攔截器中拋出自定義異常交由全局異常處理處理,這樣就可以返回和Controller方法相同的返回值類型啦~