1. 程式人生 > 實用技巧 >SpringBoot學習筆記3——使用攔截器

SpringBoot學習筆記3——使用攔截器

一、Spring Boot中使用攔截器

攔截器的原理很簡單,是 AOP 的一種實現,專門攔截對動態資源的後臺請求,即攔截對控制層的請求。使用場景比較多的是判斷使用者是否有許可權請求後臺,更拔高一層的使用場景也有,比如攔截器可以結合 websocket 一起使用,用來攔截 websocket 請求,然後做相應的處理等等。攔截器不會攔截靜態資源,Spring Boot 的預設靜態目錄為 resources/static,該目錄下的靜態頁面、js、css、圖片等等,不會被攔截(具體要看如何實現,有些情況也會攔截)。

1. 攔截器的快速使用

使用攔截器很簡單,只需要兩步即可:定義攔截器和配置攔截器。在配置攔截器中,Spring Boot 2.0 以後的版本和之前的版本有所不同,因此要注意避免可能出現的坑。

1.1 定義攔截器

定義攔截器,只需要實現HandlerInterceptor介面,HandlerInterceptor介面是所有自定義攔截器或者 Spring Boot 提供的攔截器的鼻祖,所以,首先來了解下該介面。該介面中有三個方法:preHandle(……)postHandle(……)afterCompletion(……)

preHandle(……) 方法:該方法的執行時機是,當某個 url 已經匹配到對應的 Controller 中的某個方法,且在這個方法執行之前。所以 preHandle(……) 方法可以決定是否將請求放行,這是通過返回值來決定的,返回 true 則放行,返回 false
則不會向後執行。 postHandle(……) 方法:該方法的執行時機是,當某個 url 已經匹配到對應的 Controller 中的某個方法,且在執行完了該方法,但是在 DispatcherServlet 檢視渲染之前。所以在這個方法中有個 ModelAndView 引數,可以在此做一些修改動作。 afterCompletion(……) 方法:顧名思義,該方法是在整個請求處理完成後(包括檢視渲染)執行,這時做一些資源的清理工作,這個方法只有在 preHandle(……) 被成功執行後並且返回 true 才會被執行。

瞭解了該介面,接下來自定義一個攔截器。

 1 /**
 2  * 自定義攔截器
3 * @author shengwu ni 4 * @date 2018/08/03 5 */ 6 public class MyInterceptor implements HandlerInterceptor { 7 8 private static final Logger logger = LoggerFactory.getLogger(MyInterceptor.class); 9 10 @Override 11 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 12 13 HandlerMethod handlerMethod = (HandlerMethod) handler; 14 Method method = handlerMethod.getMethod(); 15 String methodName = method.getName(); 16 logger.info("====攔截到了方法:{},在該方法執行之前執行====", methodName); 17 // 返回true才會繼續執行,返回false則取消當前請求 18 return true; 19 } 20 21 @Override 22 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { 23 logger.info("執行完方法之後進執行(Controller方法呼叫之後),但是此時還沒進行檢視渲染"); 24 } 25 26 @Override 27 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { 28 logger.info("整個請求都處理完咯,DispatcherServlet也渲染了對應的檢視咯,此時我可以做一些清理的工作了"); 29 } 30 }

到此為止,攔截器已經定義完成,接下來就是對該攔截器進行攔截配置。

1.2 配置攔截器

在 Spring Boot 2.0 之前,我們都是直接繼承 WebMvcConfigurerAdapter 類,然後重寫addInterceptors方法來實現攔截器的配置。但是在 Spring Boot 2.0 之後,該方法已經被廢棄了(當然,也可以繼續用),取而代之的是 WebMvcConfigurationSupport 方法,如下:

1 @Configuration
2 public class MyInterceptorConfig extends WebMvcConfigurationSupport {
3 
4     @Override
5     protected void addInterceptors(InterceptorRegistry registry) {
6         registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
7         super.addInterceptors(registry);
8     }
9 }

在該配置中重寫addInterceptors方法,將我們上面自定義的攔截器新增進去,addPathPatterns方法是新增要攔截的請求,這裡我們攔截所有的請求。這樣就配置好攔截器了,接下來寫一個 Controller 測試一下:

1 @Controller
2 @RequestMapping("/interceptor")
3 public class InterceptorController {
4 
5     @RequestMapping("/test")
6     public String test() {
7         return "hello";
8     }
9 }

讓其跳轉到 hello.html 頁面,直接在 hello.html 中輸出hello interceptor即可。啟動專案,在瀏覽器中輸入localhost:8080/interceptor/test看一下控制檯的日誌:

1 ====攔截到了方法:test,在該方法執行之前執行====  
2 執行完方法之後進執行(Controller方法呼叫之後),但是此時還沒進行檢視渲染  
3 整個請求都處理完咯,DispatcherServlet也渲染了對應的檢視咯,此時我可以做一些清理的工作了

可以看出攔截器已經生效,並能看出其執行順序。

1.3 解決靜態資源被攔截問題

上文中已經介紹了攔截器的定義和配置,但是這樣是否就沒問題了呢?其實不然,如果使用上面這種配置的話,我們會發現一個缺陷,那就是靜態資源被攔截了。可以在 resources/static/ 目錄下放置一個圖片資源或者 html 檔案,然後啟動專案直接訪問,即可看到無法訪問的現象。

也就是說,雖然 Spring Boot 2.0 廢棄了WebMvcConfigurerAdapter,但是 WebMvcConfigurationSupport 又會導致預設的靜態資源被攔截,這就需要我們手動將靜態資源放開。除了在 MyInterceptorConfig 配置類中重寫addInterceptors方法外,還需要再重寫一個方法:addResourceHandlers,將靜態資源放開:

1 /**
2  * 用來指定靜態資源不被攔截,否則繼承WebMvcConfigurationSupport這種方式會導致靜態資源無法直接訪問
3  * @param registry
4  */
5 @Override
6 protected void addResourceHandlers(ResourceHandlerRegistry registry) {
7     registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
8     super.addResourceHandlers(registry);
9 }

這樣配置好之後,重啟專案,靜態資源也可以正常訪問了。如果你是個善於學習或者研究的人,那肯定不會止步於此,沒錯,上面這種方式的確能解決靜態資源無法訪問的問題,但是,還有更方便的方式來配置。

我們不繼承 WebMvcConfigurationSupport 類,直接實現 WebMvcConfigurer 介面,然後重寫addInterceptors方法,將自定義的攔截器新增進去即可,如下:

1 @Configuration
2 public class MyInterceptorConfig implements WebMvcConfigurer {
3     @Override
4     public void addInterceptors(InterceptorRegistry registry) {
5         // 實現WebMvcConfigurer不會導致靜態資源被攔截
6         registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
7     }
8 }

這樣就非常方便了,實現 WebMvcConfigure 介面的話,不會攔截 Spring Boot 預設的靜態資源。

這兩種方式都可以,由於這兩種方式的不同,繼承 WebMvcConfigurationSupport 類的方式可以用在前後端分離的專案中,後臺不需要訪問靜態資源(就不需要放開靜態資源了);實現 WebMvcConfigure 介面的方式可以用在非前後端分離的專案中,因為需要讀取一些圖片、css、js檔案等等。

2. 攔截器使用例項

2.1 判斷使用者有沒有登入

一般使用者登入功能我們可以這麼做,要麼往 session 中寫一個 user,要麼針對每個 user 生成一個 token,第二種要更好一點,那麼針對第二種方式,如果使用者登入成功了,每次請求的時候都會帶上該使用者的 token,如果未登入,則沒有該 token,服務端可以檢測這個 token 引數的有無來判斷使用者有沒有登入,從而實現攔截功能。我們改造一下preHandle方法,如下:

 1 @Override
 2 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
 3 
 4     HandlerMethod handlerMethod = (HandlerMethod) handler;
 5     Method method = handlerMethod.getMethod();
 6     String methodName = method.getName();
 7     logger.info("====攔截到了方法:{},在該方法執行之前執行====", methodName);
 8 
 9     // 判斷使用者有沒有登陸,一般登陸之後的使用者都有一個對應的token
10     String token = request.getParameter("token");
11     if (null == token || "".equals(token)) {
12         logger.info("使用者未登入,沒有許可權執行……請登入");
13         return false;
14     }
15 
16     // 返回true才會繼續執行,返回false則取消當前請求
17     return true;
18 }

重啟專案,在瀏覽器中輸入localhost:8080/interceptor/test後檢視控制檯日誌,發現被攔截,如果在瀏覽器中輸入localhost:8080/interceptor/test?token=123即可正常往下走。

2.2 取消攔截操作

根據上文,如果我要攔截所有/admin開頭的 url 請求的話,需要在攔截器配置中新增這個字首,但是在實際專案中,可能會有這種場景出現:某個請求也是/admin開頭的,但是不能攔截,比如/admin/login等等,這樣的話又需要去配置。那麼,可不可以做成一個類似於開關的東西,哪裡不需要攔截,我就在哪裡弄個開關上去,做成這種靈活的可插拔的效果呢?

是可以的,我們可以定義一個註解,該註解專門用來取消攔截操作,如果某個 Controller 中的方法我們不需要攔截掉,即可在該方法上加上我們自定義的註解即可,下面先定義一個註解:

1 /**
2  * 該註解用來指定某個方法不用攔截
3  */
4 @Target(ElementType.METHOD)
5 @Retention(RetentionPolicy.RUNTIME)
6 public @interface UnInterception {
7 }

然後在 Controller 中的某個方法上新增該註解,在攔截器處理方法中新增該註解取消攔截的邏輯,如下:

 1 @Override
 2 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
 3 
 4     HandlerMethod handlerMethod = (HandlerMethod) handler;
 5     Method method = handlerMethod.getMethod();
 6     String methodName = method.getName();
 7     logger.info("====攔截到了方法:{},在該方法執行之前執行====", methodName);
 8 
 9     // 通過方法,可以獲取該方法上的自定義註解,然後通過註解來判斷該方法是否要被攔截
10     // @UnInterception 是我們自定義的註解
11     UnInterception unInterception = method.getAnnotation(UnInterception.class);
12     if (null != unInterception) {
13         return true;
14     }
15     // 返回true才會繼續執行,返回false則取消當前請求
16     return true;
17 }

重啟專案在瀏覽器中輸入http://localhost:8080/interceptor/test2?token=123測試一下,可以看出,加了該註解的方法不會被攔截。

3. 總結

本節主要介紹了 Spring Boot 中攔截器的使用,從攔截器的建立、配置,到攔截器對靜態資源的影響,都做了詳細的分析。Spring Boot 2.0 之後攔截器的配置支援兩種方式,可以根據實際情況選擇不同的配置方式。最後結合實際中的使用,舉了兩個常用的場景,以此來掌握攔截器的使用。