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