1. 程式人生 > 其它 >springboot攔截器總結

springboot攔截器總結

Springboot 攔截器總結

攔截器大體分為兩類 : handlerInterceptor 和 methodInterceptor

而methodInterceptor 又有XML 配置方法AspectJ配置方法

1. handlerInterceptor用法

handlerInterceptor 主要用於攔截有url對映的方法(即controller中與url有關聯的方法)

[1] 先建立一個攔截器

package com.grady.interceptordemo.interceptor;


import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 自定義攔截器 - 給予 springmvc
 * @ClassName: CustomInterceptor
 * @Description: springMVC專案中的攔截器,它攔截的目標是請求的地址,比MethodInterceptor先執行。
 *                  該攔截器只能過濾action請求(即有url對映的函式,其他的不行),
 *                  spring允許有多個攔截器存在,由攔截器鏈管理
 *                  當preHandle return true時,執行下一個攔截器,直到所有攔截器執行完,再執行 被攔截的請求
 *                  當preHandle return false時, 不再執行 後續的攔截器鏈 及 被攔截的請求。
 */


@Slf4j
public class CustomInterceptor implements HandlerInterceptor {
    /**
     * 我未實現一個方法卻沒有報錯,因為使用了Java8的語法,預設函式
     */

    /**
     * 進入對應的controller前
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("CustomInterceptor : preHandle");
        // 這裡如果return false,不會執行後面的攔截器,也不會執行請求的方法了
        //return false;

        return true;
    }

    /**
     * 執行完controller方法後,但在返回對應檢視前(返回json就沒有返回檢視的過程了)
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("CustomInterceptor : postHandle");
    }

    /**
     * 整個請求呼叫結束後,檢視返回後,做資源清理工作
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("CustomInterceptor : afterCompletion");
    }
}

[2] 將攔截器新增到InterceptorRegistry配置中

package com.grady.interceptordemo.config;

import com.grady.interceptordemo.interceptor.CustomInterceptor;
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;

/**
 * WebMvcConfigurerAdapter 實際被 Deprecated 所以就不用糾結去不去繼承他了,直接實現WebMvcConfigurer
 */
@Configuration
public class WebAppConfigurer implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
      	//這裡配置了customInterceptor這個攔截器攔截所有url
        registry.addInterceptor(customInterceptor()).addPathPatterns("/**");
    }

    @Bean
    public CustomInterceptor customInterceptor() {
        CustomInterceptor customInterceptor = new CustomInterceptor();
        return customInterceptor;
    }
}

之後訪問url介面,可以看到以下日誌

c.g.i.interceptor.CustomInterceptor      : CustomInterceptor : preHandle
c.g.i.interceptor.CustomInterceptor      : CustomInterceptor : postHandle
c.g.i.interceptor.CustomInterceptor      : CustomInterceptor : afterCompletion

2. 使用 methodInterceptor

[1] 使用XML配置方法

​ XML 配置 先要建立自己的xxMethodInterceptor (實現MethodInterceptor)

@Slf4j
public class CustomMethodInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        log.info("CustomMethodInterceptor ==> invoke method: process method name is {}", methodInvocation.getMethod().getName());
        log.info("CustomMethodInterceptor ==> invoke method: process class name is {}", methodInvocation.getMethod().getDeclaringClass());
        // 這裡做自定義操作

        return methodInvocation.proceed();
    }
}

然後再xml中進行配置

spring-aop.xml (註釋部分是開啟對註解的支援,暫時未開)

<?xml version = "1.0" encoding = "UTF-8"?>
<beans xmlns = "http://www.springframework.org/schema/beans"
       xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop = "http://www.springframework.org/schema/aop"
       xsi:schemaLocation = "http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/aop
   http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">

  <bean id="customMethodInterceptor" class="com.grady.interceptordemo.interceptor.CustomMethodInterceptor"/>

  <aop:config proxy-target-class="false">
  <!-- 方法攔截器,基於spring aop 實現配置 -->
  <!-- 掃描使用了註解的方法進行攔截 -->
<!--  <aop:advisor pointcut="@annotation(com.grady.interceptordemo.annotation.CustomAnnotation)" advice-ref="customMethodInterceptor" />-->
  <!-- 指定包路徑下的方法 -->
  <aop:advisor pointcut="execution(* com.grady.interceptordemo.controller.*.*(..))" advice-ref="customMethodInterceptor" />
  </aop:config>
</beans>

在xxxApplication.java匯入XML @ImportResource("classpath:spring-aop.xml")

日誌

 c.g.i.interceptor.CustomInterceptor      : CustomInterceptor : preHandle
 c.g.i.i.CustomMethodInterceptor          : CustomMethodInterceptor ==> invoke method: process method name is hello
 c.g.i.i.CustomMethodInterceptor          : CustomMethodInterceptor ==> invoke method: process class name is class com.grady.interceptordemo.controller.HelloController
 c.g.i.interceptor.CustomInterceptor      : CustomInterceptor : postHandle
 c.g.i.interceptor.CustomInterceptor      : CustomInterceptor : afterCompletion

從這裡也可以看出,handlerInterceptor 的prehandle方法先於methodInterceptor 的invoke方法執行,其他的方法再invoke 方法之後執行

再在spring-aop.xml中開啟註解,看是否支援註解版本(這裡註釋掉了包掃描的攔截方式)

<?xml version = "1.0" encoding = "UTF-8"?>
<beans xmlns = "http://www.springframework.org/schema/beans"
       xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop = "http://www.springframework.org/schema/aop"
       xsi:schemaLocation = "http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/aop
   http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">

  <bean id="customMethodInterceptor" class="com.grady.interceptordemo.interceptor.CustomMethodInterceptor"/>

  <aop:config proxy-target-class="false">
  <!-- 方法攔截器,基於spring aop 實現配置 -->
  <!-- 掃描使用了註解的方法進行攔截 -->
  <aop:advisor pointcut="@annotation(com.grady.interceptordemo.annotation.CustomAnnotation)" advice-ref="customMethodInterceptor" />
  <!-- 指定包路徑下的方法 -->
<!--  <aop:advisor pointcut="execution(* com.grady.interceptordemo.controller.*.*(..))" advice-ref="customMethodInterceptor" />-->
  </aop:config>
</beans>

日誌一致,說明是生效

c.g.i.interceptor.CustomInterceptor      : CustomInterceptor : preHandle
c.g.i.i.CustomMethodInterceptor          : CustomMethodInterceptor ==> invoke method: process method name is hello
c.g.i.i.CustomMethodInterceptor          : CustomMethodInterceptor ==> invoke method: process class name is class com.grady.interceptordemo.manager.impl.HelloManagerImpl
c.g.i.interceptor.CustomInterceptor      : CustomInterceptor : postHandle
c.g.i.interceptor.CustomInterceptor      : CustomInterceptor : afterCompletion

[2] 再看AspectJ版本(推薦方式)

SpectJ 不需要XML 配置,比較方便

直接宣告切面切點就搞定問題了

@Component
@Slf4j
public class AspectJInterceptor {

    // 宣告一個切點,表示從哪裡切入增強,這裡是切controller目錄下的所有public 方法
    @Pointcut("execution (* com.grady.interceptordemo.controller.*.*(..))")
    public void logPoint() {

    }

  	//宣告切面的執行方式,這裡是環繞切面,環繞切是有返回值的Object
    @Around("logPoint()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long startTime = System.currentTimeMillis();
        log.info("AspectJInterceptor around的函式為:" + point.getSignature());
        log.info("AspectJInterceptor around 方法開始時間 : " + startTime);
        Object obj = point.proceed();// ob 為方法的返回值
        log.info("AspectJInterceptor  around 耗時 : " + (System.currentTimeMillis() - startTime));
        return obj;
    }
}

這樣就可以了

檢視日誌

AspectJInterceptor around的函式為:String com.grady.interceptordemo.controller.HelloController.hello()
c.g.i.interceptor.AspectJInterceptor     : AspectJInterceptor around 方法開始時間 : 1567261903109
c.g.i.interceptor.AspectJInterceptor     : AspectJInterceptor  around 耗時 : 9

說明生效了

上面是根據表示式尋找函式切的;現在換基於註解切

修改程式碼

@Aspect
@Component
@Slf4j
public class AspectJInterceptor {
    // 宣告一個切點 基於註解
    @Pointcut("@annotation(com.grady.interceptordemo.annotation.CustomAnnotation)")
    public void logPoint2() {

    }

    @Around("logPoint2()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long startTime = System.currentTimeMillis();
        log.info("AspectJInterceptor around的函式為:" + point.getSignature());
        log.info("AspectJInterceptor around 方法開始時間 : " + startTime);
        Object obj = point.proceed();// ob 為方法的返回值
        log.info("AspectJInterceptor  around 耗時 : " + (System.currentTimeMillis() - startTime));
        return obj;
    }
}

日誌:

2019-08-31 22:45:52.275  INFO 4516 --- [nio-8080-exec-1] c.g.i.interceptor.AspectJInterceptor     : AspectJInterceptor around的函式為:String com.grady.interceptordemo.manager.impl.HelloManagerImpl.hello()
2019-08-31 22:45:52.276  INFO 4516 --- [nio-8080-exec-1] c.g.i.interceptor.AspectJInterceptor     : AspectJInterceptor around 方法開始時間 : 1567262752274

可以看的是HelloManagerImpl.hello 方法上有註解,所以切面方法中列印的是這個方法;而之前的版本切的位置是controller包中的方法,所以列印的是HelloController.hello

PS:

可以在application.properties 中開啟這個開關,開啟CGlib ,(這個實驗中不開是可以的,原因是當spring aop 無效時(比如沒有實現介面的類controller),會自動使用CGLib嘗試)

# 啟用CGLib 來實現aop
# spring.aop.proxy-target-class=true