過濾器(Filter)和攔截器(Interceptor)是Web專案中常用的兩個功能,本文將簡單介紹在Spring Boot中使用過濾器和攔截器來計算Controller中方法的執行時長,並且簡單對比兩者的區別。
現有如下Controller:
@RestController @RequestMapping(“user”) public class UserController { @GetMapping(“/{id:\\d+}”) public void get(@PathVariable String id) { System。out。println(id); } }
下面透過配置過濾器和攔截器來實現對
get
方法執行時間計算的功能。
一、過濾器
定義一個
TestFilter
類,實現
javax。servlet。Filter
:
public class TestFilter implements Filter{ @Override public void init(FilterConfig filterConfig) throws ServletException { System。out。println(“過濾器初始化”); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System。out。println(“開始執行過濾器”); Long start = new Date()。getTime(); filterChain。doFilter(servletRequest, servletResponse); System。out。println(“【過濾器】耗時 ” + (new Date()。getTime() - start)); System。out。println(“結束執行過濾器”); } @Override public void destroy() { System。out。println(“過濾器銷燬”); } }
TestFilter
重寫了
Filter
的三個方法,方法名稱已經很直白的描述了其作用,這裡不再贅述。
要使該過濾器在Spring Boot中生效,還需要一些配置。這裡主要有兩種配置方式。
配置方式一
可透過在
TestFilter
上加上如下註解:
@Component @WebFilter(urlPatterns = {“/*”}) public class TestFilter implements Filter { 。。。 }
@Component
註解讓
TestFilter
成為Spring上下文中的一個Bean,
@WebFilter
註解的
urlPatterns
屬性配置了哪些請求可以進入該過濾器,
/*
表示所有請求。
啟動專案時可以看到控制檯輸出了
過濾器初始化
,啟動後訪問http://localhost:8080/user/1,控制檯輸出如下:
開始執行過濾器 1 【過濾器】耗時 31 結束執行過濾器
配置方式二
除了在過濾器類上加註解外,我們也可以透過
FilterRegistrationBean
來註冊過濾器。
定義一個
WebConfig
類,加上
@Configuration
註解表明其為配置類,然後透過
FilterRegistrationBean
來註冊過濾器:
@Configuration public class WebConfig { @Bean public FilterRegistrationBean testFilter() { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); TestFilter testFilter = new TestFilter(); filterRegistrationBean。setFilter(testFilter); List
FilterRegistrationBean
除了註冊過濾器
TestFilter
外還透過
setUrlPatterns
方法配置了URL匹配規則。重啟專案訪問http://localhost:8080/user/1,我們可以看到和上面一樣的效果。
透過過濾器我們只可以獲取到servletRequest物件,所以並不能獲取到方法的名稱,所屬類,引數等額外的資訊。
二、攔截器
定義一個
TestInterceptor
類,實現
org。springframework。web。servlet。HandlerInterceptor
介面:
public class TestInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception { System。out。println(“處理攔截之前”); httpServletRequest。setAttribute(“startTime”, new Date()。getTime()); System。out。println(((HandlerMethod) o)。getBean()。getClass()。getName()); System。out。println(((HandlerMethod) o)。getMethod()。getName()); return true; } @Override public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { System。out。println(“開始處理攔截”); Long start = (Long) httpServletRequest。getAttribute(“startTime”); System。out。println(“【攔截器】耗時 ” + (new Date()。getTime() - start)); } @Override public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { System。out。println(“處理攔截之後”); Long start = (Long) httpServletRequest。getAttribute(“startTime”); System。out。println(“【攔截器】耗時 ” + (new Date()。getTime() - start)); System。out。println(“異常資訊 ” + e); } }
TestInterceptor
實現了
HandlerInterceptor
介面的三個方法。
preHandle
方法在處理攔截之前執行,
postHandle
只有當被攔截的方法沒有丟擲異常成功時才會處理,
afterCompletion
方法無論被攔截的方法丟擲異常與否都會執行。
透過這三個方法的引數可以看到,相較於過濾器,攔截器多了Object和Exception物件,所以可以獲取的資訊比過濾器要多的多。但過濾器仍無法獲取到方法的引數等資訊,我們可以透過切面程式設計來實現這個目的。
要使攔截器在Spring Boot中生效,還需要如下兩步配置:
1。在攔截器類上加入
@Component
註解;
2。在
WebConfig
中透過
InterceptorRegistry
註冊過濾器:
@Configuration public class WebConfig extends WebMvcConfigurerAdapter { @Autowired private TestInterceptor testInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry。addInterceptor(testInterceptor) 。addPathPatterns(“/**”) 。excludePathPatterns(“/**”, “/”, “/login”, “/register”); } }
啟動專案,訪問http://localhost:8080/user/1,控制檯輸出如下:
處理攔截之前 cn。ycf。controller。UserController get 1 開始處理攔截 【攔截器】耗時 24 處理攔截之後 【攔截器】耗時 24 異常資訊 null
從輸出中我們可以瞭解到三個方法的執行順序,並且三個方法都被執行了。
我們在
UserController
的
get
方法中手動丟擲一個異常:
@GetMapping(“/{id:\\d+}”) public void get(@PathVariable String id) { System。out。println(id); throw new RuntimeException(“user not exist”); }
重啟專案後,訪問http://localhost:8080/user/1,控制檯輸出如下:
處理攔截之前 cn。ycf。controller。UserController get 1 處理攔截之後 【攔截器】耗時 0 異常資訊 java。lang。RuntimeException: user not exist
可看到,
postHandle
方法並沒有被執行。
三、執行時機對比
我們將過濾器和攔截器都配置上,然後啟動專案訪問http://localhost:8080/user/1:
開始執行過濾器 處理攔截之前 cn。ycf。controller。UserController get 1 開始處理攔截 【攔截器】耗時 25 處理攔截之後 【攔截器】耗時 25 異常資訊 null 【過濾器】耗時 34 結束執行過濾器
可看到過濾器要先於攔截器執行,晚於攔截器結束。下圖很好的描述了它們的執行時間區別: