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 之後攔截器的配置支援兩種方式,可以根據實際情況選擇不同的配置方式。最後結合實際中的使用,舉了兩個常用的場景,以此來掌握攔截器的使用。