一、什么是攔截器,及其作用
攔截器(Interceptor): 用於在某個方法被訪問之前進行攔截,然后在方法執行之前或之后加入某些操作,其實就是AOP的一種實現策略。它通過動態攔截Action調用的對象,允許開發者定義在一個action執行的前后執行的代碼,也可以在一個action執行前阻止其執行。同時也是提供了一種可以提取action中可重用的部分的方式。
攔截器的使用場景越來越多,尤其是面向切片編程流行之后。那通常攔截器可以做什么呢?
之前我們在Agent介紹中,提到過統計函數的調用耗時。這個思路其實和AOP的環繞增強如出一轍。
那一般來說,場景如下:
1、日志記錄:
記錄請求信息的日志,以便進行信息監控、信息統計、計算PV(Page View)等。
2、權限檢查:
如登錄檢測,進入處理器檢測檢測是否登錄,如果沒有直接返回到登錄頁面;
3、函數增強:比如對一個函數進行參數檢查,或者結果過濾等。甚至可以對函數就行權限認證。
4、性能監控:統計函數性能,
有時候系統在某段時間莫名其妙的慢,可以通過攔截器在進入處理器之前記錄開始時間,在處理完后記錄結束時間,從而得到該請求的處理時間(如果有反向代理,如apache可以自動記錄);
5、通用行為:
讀取cookie得到用戶信息並將用戶對象放入請求,從而方便后續流程使用,還有如提取Locale、Theme信息等,只要是多個處理器都需要的即可使用攔截器實現。
6、OpenSessionInView:
如Hibernate,在進入處理器打開Session,在完成后關閉Session。
…………本質也是AOP(面向切面編程),也就是說符合橫切關注點的所有功能都可以放入攔截器實現。
二、springmvc攔截器相關接口和實現類
package org.springframework.web.servlet; public interface HandlerInterceptor { boolean preHandle( HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; void postHandle( HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception; void afterCompletion( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception; }
public interface AsyncHandlerInterceptor extends HandlerInterceptor { void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; }
public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor { // 在目標方法執行前執行
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } // 在目標方法執行后執行,但在請求返回前,我們仍然可以對 ModelAndView進行修改
@Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {} // 在請求已經返回之后執行
@Override public void afterCompletion( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {} // 用來處理異步請求, 當Controller中有異步請求方法的時候會觸發該方法
@Override public void afterConcurrentHandlingStarted( HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {} }
三、如何配置攔截器
1、springboot項目中如何配置攔截器
實現自定義攔截器只需要3步:
1、創建我們自己的攔截器類並實現 HandlerInterceptor 接口。
2、創建一個Java類繼承WebMvcConfigurerAdapter,並重寫 addInterceptors 方法。
3、實例化我們自定義的攔截器,然后將對像手動添加到攔截器鏈中(在addInterceptors方法中添加)。
package com.springboot.study.interceptors; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; public class MyInterceptor1 implements HandlerInterceptor{ @Override public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3) throws Exception { System.out.println("=====>(1)在整個請求之后調用,即在dispatcherServlet渲染了對應的視圖之后(主要是進行資源清理工作)"); } @Override public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3) throws Exception { System.out.println("=====>(1)在請求處理之后調用,即在controller方法執行之后調用"); } @Override public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2) throws Exception { System.out.println("=====>(1)在請求處理之前調用,即在Controller方法調用之前!"); return true;//只有返回true才會往下執行,返回FALSE的話就會取消當前請求 } }
package com.springboot.study.interceptors; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; public class MyInterceptor2 implements HandlerInterceptor{ @Override public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3) throws Exception { System.out.println("=====>(2)在整個請求之后調用,即在dispatcherServlet渲染了對應的視圖之后(主要是進行資源清理工作)"); } @Override public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3) throws Exception { System.out.println("=====>(2)在請求處理之后調用,即在controller方法執行之后調用"); } @Override public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2) throws Exception { System.out.println("=====>(2)在請求處理之前調用,即在Controller方法調用之前!"); return true;//只有返回true才會往下執行,返回FALSE的話就會取消當前請求 } }
package com.springboot.study.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class MyController { @RequestMapping("/index") public String index(){ return "hello!"; } }
package com.springboot.study.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import com.springboot.study.interceptors.MyInterceptor1; import com.springboot.study.interceptors.MyInterceptor2; @Configuration public class MyWebAppConfigurer extends WebMvcConfigurerAdapter{ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**"); registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/**"); super.addInterceptors(registry); } }
運行結果:
=====>(1)在請求處理之前調用,即在Controller方法調用之前! =====>(2)在請求處理之前調用,即在Controller方法調用之前! =====>(2)在請求處理之后調用,即在controller方法執行之后調用 =====>(1)在請求處理之后調用,即在controller方法執行之后調用 =====>(2)在整個請求之后調用,即在dispatcherServlet渲染了對應的視圖之后(主要是進行資源清理工作) =====>(1)在整個請求之后調用,即在dispatcherServlet渲染了對應的視圖之后(主要是進行資源清理工作)
四、實例
自定義權限注解
定義一個@interface
類
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Access { String[] value() default {}; String[] authorities() default {}; String[] roles() default {}; }
@Target
注解是標注這個類它可以標注的位置:
常用的元素類型(ElementType):
public enum ElementType { /** Class, interface (including annotation type), or enum declaration */
// TYPE類型可以聲明在類上或枚舉上或者是注解上
TYPE, /** Field declaration (includes enum constants) */
// FIELD聲明在字段上
FIELD, /** Method declaration */
// 聲明在方法上
METHOD, /** Formal parameter declaration */
// 聲明在形參列表中
PARAMETER, /** Constructor declaration */
// 聲明在構造方法上
CONSTRUCTOR, /** Local variable declaration */
// 聲明在局部變量上
LOCAL_VARIABLE, /** Annotation type declaration */ ANNOTATION_TYPE, /** Package declaration */ PACKAGE, /** * Type parameter declaration * * @since 1.8 */ TYPE_PARAMETER, /** * Use of a type * * @since 1.8 */ TYPE_USE }
@Retention
注解表示的是本注解(標注這個注解的注解保留時期)
public enum RetentionPolicy { /** * Annotations are to be discarded by the compiler. */
// 源代碼時期
SOURCE, /** * Annotations are to be recorded in the class file by the compiler * but need not be retained by the VM at run time. This is the default * behavior. */
// 字節碼時期, 編譯之后
CLASS, /** * Annotations are to be recorded in the class file by the compiler and * retained by the VM at run time, so they may be read reflectively. * * @see java.lang.reflect.AnnotatedElement */
// 運行時期, 也就是一直保留, 通常也都用這個
RUNTIME }
@Documented
是否生成文檔的標注, 也就是生成接口文檔是, 是否生成注解文檔
注解說完了, 下面需要到對應的controller的方法中取添加注解, 配置該方法允許的權限
在方法上配置權限
@RestController public class HelloController { @RequestMapping(value = "/admin", produces = MediaType.APPLICATION_JSON_UTF8_VALUE, method = RequestMethod.GET) // 配置注解權限, 允許身份為admin, 或者說允許權限為admin的人訪問
@Access(authorities = {"admin"}) public String hello() { return "Hello, admin"; } }
編寫權限攔截邏輯
// 自定義一個權限攔截器, 繼承HandlerInterceptorAdapter類
public class AuthenticationInterceptor extends HandlerInterceptorAdapter { // 在調用方法之前執行攔截
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 將handler強轉為HandlerMethod, 前面已經證實這個handler就是HandlerMethod
HandlerMethod handlerMethod = (HandlerMethod) handler; // 從方法處理器中獲取出要調用的方法
Method method = handlerMethod.getMethod(); // 獲取出方法上的Access注解
Access access = method.getAnnotation(Access.class); if (access == null) { // 如果注解為null, 說明不需要攔截, 直接放過
return true; } if (access.authorities().length > 0) { // 如果權限配置不為空, 則取出配置值
String[] authorities = access.authorities(); Set<String> authSet = new HashSet<>(); for (String authority : authorities) { // 將權限加入一個set集合中
authSet.add(authority); } // 這里我為了方便是直接參數傳入權限, 在實際操作中應該是從參數中獲取用戶Id // 到數據庫權限表中查詢用戶擁有的權限集合, 與set集合中的權限進行對比完成權限校驗
String role = request.getParameter("role"); if (StringUtils.isNotBlank(role)) { if (authSet.contains(role)) { // 校驗通過返回true, 否則攔截請求
return true; } } } // 攔截之后應該返回公共結果, 這里沒做處理
return false; } }
攔截器詳解源碼地址:https://www.cnblogs.com/fangjian0423/p/springMVC-interceptor.html