在看這篇文章之前需要首先理解 Spring AOP 增強的知識,如果你想先了解增強的知識可以移步到 我另外一篇博客《Spring動態代理之詳細DEBUG日誌模式》裏面有關於增強的知識 如果除了增強還有 關於CGlib 和 Proxy 代理的知識不太理解可以再移步到 《動態代理之詳細DEBUG日誌模式》好了,目前是建基於你都明白 動態代理 和 Spring 增強的基礎上進行對Spring 中的 切點(pointcut) 和 切面(advisor)的介紹
如果只有advice 我們一般情況下將一個目標類中的所有方法都進行代理,但是我們希望可以定位的某些類的特定方法,這個時候我們就需要使用到切點(pointcut),然而切面就是切點和增強(advice)的結合。
首先我們通過一個UserService的業務去理解切面和切點的知識,一個有兩個方法一個是註冊 一個是登錄,我們會對這兩個方法進行織入演示,所以首先提出這個Service的代碼,非常簡單的代碼:
/** * Created by yanzhichao on 27/04/2017. */ public class UserService { public void login(String username,String password){ System.out.println("Login : " + username + ", pwd : " + password); } public void register(String username,String password){ System.out.println("register : " + username + " , pwd " + password); } }
首先我們先說StaticMethodMatcherPointcutAdvice(這個類名長到我想吐)的使用,主要我們是希望通過這個靜態方法匹配切面(原諒我直譯)對UserService中的login進行織入。而register不織入:
/** * Created by yanzhichao on 27/04/2017. */ public class StaticLoginMethodAdvisor extends StaticMethodMatcherPointcutAdvisor { //匹配login方法進行織入 public boolean matches(Method method, Class<?> aClass) { return "login".equals(method.getName()); } //定義對什麼類有效 @Override public ClassFilter getClassFilter() { return new ClassFilter() { public boolean matches(Class<?> aClass) { //定義對UserService類或者它的派生類有效 return UserService.class.isAssignableFrom(aClass); } }; } }
然後定義advice 增強,就在方法執行的時候打印一下執行的什麼方法的LOG
/** * Created by yanzhichao on 27/04/2017. */ public class UserAdvice implements MethodBeforeAdvice { public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println(">>>>>>>>>>" + method.getName() + " invoked !!!"); } }
由於我想測試一下advisor中的getClassFilter方法是否有用,所以我再定義了一個AdminService
/** * Created by yanzhichao on 27/04/2017. */ public class AdminService { public void login(String username,String password){ System.out.println("Admin Login : " + username + ", pwd : " + password); } public void register(String username,String password){ System.out.println("Admin register : " + username + " , pwd " + password); } }
同樣有login方法,所以再執行兩個Service的login方法的時候可以看看輸出結果。
最後配置文件的代碼如下:
<?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:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 未代理的類 --> <bean id="userService" class="com.maxfunner.service.impl.UserService" /> <bean id="adminService" class="com.maxfunner.service.impl.AdminService" /> <bean id="UserAdvice" class="com.maxfunner.advice.UserAdvice" /> <bean id="LoginMethodAdvisor" class="com.maxfunner.advisor.StaticLoginMethodAdvisor"> <property name="advice"> <bean class="com.maxfunner.advice.UserAdvice" /> </property> </bean> <!-- 注意使用了abstract主要是作爲一個配置模板去使用,由於我們沒有接口實現方式去寫Service所以這裏使用proxyTargetClass --> <bean id="factoryBean" class="org.springframework.aop.framework.ProxyFactoryBean" abstract="true" p:proxyTargetClass="true" p:interceptorNames="LoginMethodAdvisor" /> <!-- 定義被proxy bean --> <bean id="userServiceProxy" parent="factoryBean" p:target-ref="userService" /> <bean id="adminServiceProxy" parent="factoryBean" p:target-ref="adminService" /> </beans>
上面註釋上已經寫的很清楚了,接下來我們使用main方法進行測試一下 然後再貼出一下輸出的Log
public static void main(String[] args){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) context.getBean("userServiceProxy"); AdminService adminService = (AdminService) context.getBean("adminServiceProxy"); userService.login("TONY","PWD"); userService.register("TONY","PWD"); adminService.login("TONY","PWD"); adminService.register("TONY","PWD"); }
輸出可以看到只有userService.login纔有被成功增強:
>>>>>>>>>>login invoked !!!
Login : TONY, pwd : PWD
register : TONY , pwd PWD
Admin Login : TONY, pwd : PWD
Admin register : TONY , pwd PWD
但是你會問 既然我們寫定義proxy bean的時候就已經定義好那個類進行代理了爲什麼還有在advisor定義classFilter的方法呢,因爲是爲了做自動代理,這個留最後說~ 接下來我們來做一個更加簡單的靜態方法切面,通過正則表達式進行限定方法名匹配,這裏用到RegexpMethodPointcutAdvisor類,什麼都不用改變就該spring配置文件即可:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 未代理的類 --> <bean id="userService" class="com.maxfunner.service.impl.UserService" /> <bean id="adminService" class="com.maxfunner.service.impl.AdminService" /> <bean id="UserAdvice" class="com.maxfunner.advice.UserAdvice" /> <!-- pattern是正則表達式,現在我們只針對service包下的所有類的log開頭的方法進行織入 --> <bean id="LoginMethodAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <property name="patterns"> <list> <value>.*\.service\..*\.log.*</value> </list> </property> <property name="advice"> <bean class="com.maxfunner.advice.UserAdvice" /> </property> </bean> <!-- 注意使用了abstract主要是作爲一個配置模板去使用,由於我們沒有接口實現方式去寫Service所以這裏使用proxyTargetClass --> <bean id="factoryBean" class="org.springframework.aop.framework.ProxyFactoryBean" abstract="true" p:proxyTargetClass="true" p:interceptorNames="LoginMethodAdvisor" /> <!-- 定義被proxy bean --> <bean id="userServiceProxy" parent="factoryBean" p:target-ref="userService" /> <bean id="adminServiceProxy" parent="factoryBean" p:target-ref="adminService" /> </beans>
然後我們的main方法不用改變然後查看輸出結果,可以發現按照我們的正則表達式的意思,已經成功織入了service包中的所有方法名爲log開頭的方法:
>>>>>>>>>>login invoked !!!
Login : TONY, pwd : PWD
register : TONY , pwd PWD
>>>>>>>>>>login invoked !!!
Admin Login : TONY, pwd : PWD
Admin register : TONY , pwd PWD
動態切面,根據方法中的不同參數進行選擇性織入,這個是已經非常耗資源的織入方法,後面打印完所有的LOG 我們就清楚爲什麼是耗資源的織入方法了,首先我們用到了DynamicMethodMatcherPointcut,我們會在這個方法中限定類,限定方法,限定方法的參數進行織入:
/** * Created by yanzhichao on 27/04/2017. */ public class DynamicLoginMethodPointcut extends DynamicMethodMatcherPointcut { @Override public ClassFilter getClassFilter() { return new ClassFilter() { public boolean matches(Class<?> aClass) { System.err.println("getClassFilter invoked !!! aClass : " + aClass.getName()); return UserService.class.isAssignableFrom(aClass); } }; } @Override public boolean matches(Method method, Class<?> targetClass) { System.err.println("matches(static) invoked !!! method : " + method.getName()); return "login".equals(method.getName()); } public boolean matches(Method method, Class<?> aClass, Object... objects) { //如果用戶名爲TONY 就織入 System.err.println("matches(dynamic) invoked !!! objects[0] : " + objects[0]); return "TONY".equals(objects[0]); } }
注意 一會我們會打印一堆日誌,你會發現一個問題,Spring採用了一種非常好的機制,靜態檢查第一次調用的時候發現匹配和不匹配後 之後就不對這些連接點跑靜態檢查的方法了,而動態檢查matches的就會每次都調用都會跑,所以我們必須在靜態檢查的方法中先過濾一批,靜態檢查非常高效,而動態檢查每次調用都會檢查所以只檢查參數部分~
然後我們在配置文件使用了DefaultPointcutAdvisor 引用我們實現的 DynamicLoginMethodPointcut:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 未代理的類 --> <bean id="userService" class="com.maxfunner.service.impl.UserService" /> <bean id="adminService" class="com.maxfunner.service.impl.AdminService" /> <bean id="UserAdvice" class="com.maxfunner.advice.UserAdvice" /> <!-- DefaultPointcutAdvisor是一個默認的advisor然後使用它來應用我們的DynamicLoginMethodPointcut --> <bean id="LoginMethodAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"> <property name="pointcut"> <bean class="com.maxfunner.advisor.DynamicLoginMethodPointcut" /> </property> <property name="advice"> <bean class="com.maxfunner.advice.UserAdvice" /> </property> </bean> <!-- 注意使用了abstract主要是作爲一個配置模板去使用,由於我們沒有接口實現方式去寫Service所以這裏使用proxyTargetClass --> <bean id="factoryBean" class="org.springframework.aop.framework.ProxyFactoryBean" abstract="true" p:proxyTargetClass="true" p:interceptorNames="LoginMethodAdvisor" /> <!-- 定義被proxy bean --> <bean id="userServiceProxy" parent="factoryBean" p:target-ref="userService" /> <bean id="adminServiceProxy" parent="factoryBean" p:target-ref="adminService" /> </beans>
最後我們 跟以後不一樣的是 我們在main方法連續調用一樣的方法兩次:
public static void main(String[] args){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) context.getBean("userServiceProxy"); AdminService adminService = (AdminService) context.getBean("adminServiceProxy"); userService.login("TONY","PWD"); userService.register("TONY","PWD"); adminService.login("TONY","PWD"); adminService.register("TONY","PWD"); userService.login("TONY","PWD"); userService.register("TONY","PWD"); adminService.login("TONY","PWD"); adminService.register("TONY","PWD"); }
最後運行的時候會發現 我們剛剛所說的檢查機制,然而跟我們預計的一樣 只織入UserService中的login方法 而且只織入 username參數爲TONY的情況:
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.UserService
matches(static) invoked !!! method : register
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.UserService
matches(static) invoked !!! method : login
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.UserService
matches(static) invoked !!! method : toString
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.UserService
matches(static) invoked !!! method : clone
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.AdminService
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.AdminService
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.AdminService
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.AdminService
>>>>>>>>>>login invoked !!!
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.UserService
matches(static) invoked !!! method : login
matches(dynamic) invoked !!! objects[0] : TONY
Login : TONY, pwd : PWD
register : TONY , pwd PWD
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.UserService
matches(static) invoked !!! method : register
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.AdminService
Admin Login : TONY, pwd : PWD
Admin register : TONY , pwd PWD
>>>>>>>>>>login invoked !!!
Login : TONY, pwd : PWD
register : TONY , pwd PWD
Admin Login : TONY, pwd : PWD
Admin register : TONY , pwd PWD
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.AdminService
matches(dynamic) invoked !!! objects[0] : TONY
還有一種比較少用的pointcut 中文叫 流程切面 (ControlFlowPointcut) 我也不知道怎麼解釋他的主要功能,還是直接看代碼吧,我們會定義一個TestUserService的類,裏面有個Test的方法,會跑UserService中的Login 和 Register方法,然後我希望是Test方法中跑中Login和Register被織入,但是在其他地方跑Login 和 Register不會被織入,這個我完全想不到使用場景,可能我入世未深,但是我還是把他寫下來 希望以後用到的時候可以看看這份東西,說實話當我這段時間複習AOP的時候,我完全對這個切面完全不記得了,所以寫下希望下次用的時候可以讓我回憶道有這樣一個切面~
首先定義TestUserService 類
/** * Created by yanzhichao on 27/04/2017. */ public class TestUserService { public void test(UserService service){ service.login("TONY","PWD"); service.register("TONY","PWD"); } public void test(AdminService service){ service.login("TONY","PWD"); service.register("TONY","PWD"); } }
然後就是配置文件:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 未代理的類 --> <bean id="userService" class="com.maxfunner.service.impl.UserService" /> <bean id="adminService" class="com.maxfunner.service.impl.AdminService" /> <bean id="UserAdvice" class="com.maxfunner.advice.UserAdvice" /> <!-- DefaultPointcutAdvisor是一個默認的advisor然後使用它來應用我們的DynamicLoginMethodPointcut --> <bean id="LoginMethodAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"> <property name="pointcut"> <!-- 使用了ControlFlowPoint 在它的構造函數中 第一個參數是TestUserSerivce的類 然後就是織入的方法 --> <bean class="org.springframework.aop.support.ControlFlowPointcut"> <constructor-arg type="java.lang.Class" value="com.maxfunner.service.impl.TestUserService" /> <constructor-arg type="java.lang.String" value="test" /> </bean> </property> <property name="advice"> <bean class="com.maxfunner.advice.UserAdvice" /> </property> </bean> <!-- 注意使用了abstract主要是作爲一個配置模板去使用,由於我們沒有接口實現方式去寫Service所以這裏使用proxyTargetClass --> <bean id="factoryBean" class="org.springframework.aop.framework.ProxyFactoryBean" abstract="true" p:proxyTargetClass="true" p:interceptorNames="LoginMethodAdvisor" /> <!-- 定義被proxy bean --> <bean id="userServiceProxy" parent="factoryBean" p:target-ref="userService" /> <bean id="adminServiceProxy" parent="factoryBean" p:target-ref="adminService" /> </beans>
然後重點在調用和輸入,你會發現正常執行兩個service 的時候不會被織入,而是在TestUserService中調用test方法的時候 adminService 和 userService 中的兩個方法都會被織入:
public static void main(String[] args){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) context.getBean("userServiceProxy"); AdminService adminService = (AdminService) context.getBean("adminServiceProxy"); userService.login("TONY","PWD"); userService.register("TONY","PWD"); adminService.login("TONY","PWD"); adminService.register("TONY","PWD"); TestUserService testUserService = new TestUserService(); testUserService.test(userService); testUserService.test(adminService); }
最後看看輸出可以看到在TestUserService中test方法執行的方法都有織入:
Login : TONY, pwd : PWD
register : TONY , pwd PWD
Admin Login : TONY, pwd : PWD
Admin register : TONY , pwd PWD
>>>>>>>>>>login invoked !!!
Login : TONY, pwd : PWD
>>>>>>>>>>register invoked !!!
register : TONY , pwd PWD
>>>>>>>>>>login invoked !!!
Admin Login : TONY, pwd : PWD
>>>>>>>>>>register invoked !!!
Admin register : TONY , pwd PWD
最後我們想是否能夠指定test方法中執行那個類中的那個方法會纔會被織入呢,答案是肯定的。這時候我們使用複合切點切面ComposablePointcut,因爲這個切面有兩種特性第一種是當兩個切面,一個指定類的,第二個切面指定方法的,可以取兩者的交際,就是說 又要是這個類的 還要是這個 方法的 連接點纔會被織入, 另外一種是 有兩個切面,一個指定類的,第二個切面指定方法的,取兩者的並集,就是說如果是指定的類的所有方法都織入,還有某一個沒有指定的類中的被指定的方法名的方法都會被織入。
當然 ComposablePointcut 本質上它也是切面,所以在構造方法上也提供了基礎切面
ComposablePointcut() 沒有定義基礎的切面
ComposablePointcut(ClassFilter classFilter) 類過濾
ComposablePointcut(MethodMatcher methodMatcher) 方法過過濾
ComposablePointcut(ClassFilter classFilter,MethodMatcher methodMatcher) 基礎複合過濾
交集複合的方法是:
ComposablePointcut intersection(ClassFilter classFilter)
ComposablePointcut intersection(MethodMatcher methodMatcher)
ComposablePointcut intersection(Pointcut pointcut)
並集複合的方法是:
ComposablePointcut union(ClassFilter classFilter)
ComposablePointcut union(MethodMatcher methodMatcher)
你會發現一個坑爹的現象是沒有提供一個參數爲Pointcut的union方法,可以使用org.springframework.aop.support.Pointcuts工具類,有兩個靜態方法可以返回一個Pointcut這個Pointcut 正是ComposablePointcut (絕對坑爹)
Pointcut intersection(Pointcut a,Pointcut b);
Pointcut union(Pointcut a,Pointcut b);
不說別的實現我們的上面說的情況“最後我們想是否能夠指定test方法中執行那個類中的那個方法會纔會被織入呢,答案是肯定的!”看看怎麼做~
首先我們創建一個工具類去把多個切面組裝成一個ComposablePointcut ,然而我們這次使用了之前實現的兩個pointcut 第一個是剛剛實現的ControlFlowPointcut 然後通過交集複合的方式 在複合一個 DynamicLoginMethodPointcut 這樣就可以實現 在UserService 中的Login方法 實參爲TONY 在 TestUserService中的test方法中調用的織入,非常複雜的關係,我覺得看輸出會比較直接點,而且我真的想不出使用場景:
/** * Created by yanzhichao on 27/04/2017. */ public class ComposablePointcutService { public Pointcut getComposablePointcut(){ ComposablePointcut composablePointcut = new ComposablePointcut(); //ControlFlowPointcut實現TestUserService.test中的方法織入 Pointcut p1 = new ControlFlowPointcut(TestUserService.class,"test"); //我們一開始實現的動態切面,只有在UserService login方法中的參數爲TONY 纔會被織入 Pointcut p2 = new DynamicLoginMethodPointcut(); return composablePointcut.intersection(p1).intersection(p2); } }
然後我們通過在Spring容器註冊一個ComposablePointcutService,然後繼續使用DefaultPointcutAdivsor ,然後這次我們裝配的pointcut不是ControlFlowPointcut而是通過ComposablePointcutService中getComposablePointcut方法獲得的複合pointcut對象:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 未代理的類 --> <bean id="userService" class="com.maxfunner.service.impl.UserService" /> <bean id="adminService" class="com.maxfunner.service.impl.AdminService" /> <bean id="UserAdvice" class="com.maxfunner.advice.UserAdvice" /> <!-- 剛剛實現的一個複合切面的工具類 有個getComposablePointcut 獲得切面 --> <bean id="composablePointcutService" class="com.maxfunner.advisor.ComposablePointcutService" /> <!-- DefaultPointcutAdvisor是一個默認的advisor然後使用它來應用我們的composablePointcutService的複合切面 --> <bean id="LoginMethodAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"> <property name="pointcut" value="#{composablePointcutService.composablePointcut}" /> <property name="advice"> <bean class="com.maxfunner.advice.UserAdvice" /> </property> </bean> <!-- 注意使用了abstract主要是作爲一個配置模板去使用,由於我們沒有接口實現方式去寫Service所以這裏使用proxyTargetClass --> <bean id="factoryBean" class="org.springframework.aop.framework.ProxyFactoryBean" abstract="true" p:proxyTargetClass="true" p:interceptorNames="LoginMethodAdvisor" /> <!-- 定義被proxy bean --> <bean id="userServiceProxy" parent="factoryBean" p:target-ref="userService" /> <bean id="adminServiceProxy" parent="factoryBean" p:target-ref="adminService" /> </beans>
最後調用然後輸出結果:
public static void main(String[] args){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) context.getBean("userServiceProxy"); AdminService adminService = (AdminService) context.getBean("adminServiceProxy"); userService.login("TONY","PWD"); userService.register("TONY","PWD"); adminService.login("TONY","PWD"); adminService.register("TONY","PWD"); TestUserService testUserService = new TestUserService(); testUserService.test(userService); testUserService.test(adminService); }
輸出結果如下:
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.UserService
matches(static) invoked !!! method : register
matches(static) invoked !!! method : register
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.UserService
matches(static) invoked !!! method : login
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.UserService
matches(static) invoked !!! method : toString
matches(static) invoked !!! method : toString
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.UserService
matches(static) invoked !!! method : clone
matches(static) invoked !!! method : clone
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.AdminService
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.AdminService
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.AdminService
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.AdminService
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.UserService
matches(static) invoked !!! method : login
matches(dynamic) invoked !!! objects[0] : TONY
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.UserService
Login : TONY, pwd : PWD
matches(static) invoked !!! method : register
register : TONY , pwd PWD
matches(static) invoked !!! method : register
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.AdminService
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.AdminService
matches(dynamic) invoked !!! objects[0] : TONY
Admin Login : TONY, pwd : PWD
Admin register : TONY , pwd PWD
>>>>>>>>>>login invoked !!!
Login : TONY, pwd : PWD
register : TONY , pwd PWD
Admin Login : TONY, pwd : PWD
Admin register : TONY , pwd PWD
最後一個切面是引介切面(IntroductionAdvisor) 由於引介切面只能代理類級別的,所以無法實現引介切面定位代理到某一個方法,使用IntroductionAdvisor 的效果其實和IntroductionAdvice差不多,但是最大的好處是他提供了ClassFilter 也就是說可以實現自動代理,當然我們可以使用我上一篇博客的引介advice,但是由於業務不太一樣,以免混淆寫一些和業務相關的吧,首先我們會對類進行一個日誌的輸出,我們定義一個接口,可以讓代理對象自己去控制是否打印當前登錄的密碼,先定義一個接口:
/** * Created by yanzhichao on 27/04/2017. */ public interface HiddenPasswordController { public void hidden(boolean isHidden); }
然後我們通過DelegatingIntroductionInterceptor實現一個advice:
/** * Created by yanzhichao on 27/04/2017. */ public class HiddenPasswordAdvice extends DelegatingIntroductionInterceptor implements HiddenPasswordController { ThreadLocal<Boolean> threadLocal = new ThreadLocal<Boolean>(); public void hidden(boolean isHidden) { threadLocal.set(isHidden); } @Override public Object invoke(MethodInvocation mi) throws Throwable { System.out.print( "advice ->>>>>> " + mi.getMethod().getName() + " method Username : " + mi.getArguments()[0] + " Password : "); if(threadLocal.get() == null || threadLocal.get()){ System.out.println("*********"); }else{ System.out.println(mi.getArguments()[1]); } return super.invoke(mi); } }
然後我們不直接使用DefaultIntroductionAdvisor,而是自己繼承DefaultIntroductionAdvisor 然後重寫 getClassFilter 因爲DefaultIntroductionAdvisor的getClassFilter永遠返回true,所以我這裏會過濾屌UserService 只有 AdminService 纔會被增強,而且我這裏用一個List<Class>的熟悉去定義,意味着在配置文件可以對ClassFilter對那些類產生增強進行定義:
/** * Created by yanzhichao on 27/04/2017. */ public class HiddenPasswordAdvisor extends DefaultIntroductionAdvisor implements ClassFilter { private List<Class> targetClasses = new ArrayList<Class>(); public List<Class> getTargetClasses() { return targetClasses; } public void setTargetClasses(List<Class> targetClasses) { this.targetClasses = targetClasses; } //直接調用我實現的HiddenPasswordAdvice,並指明目標類的實現接口HiddenPasswordController public HiddenPasswordAdvisor() { super(new HiddenPasswordAdvice(), HiddenPasswordController.class); } //傳入一個引介增強,繼承於DefaultIntroductionAdvisor public HiddenPasswordAdvisor(Advice advice) { super(advice); } //第二個參數主要描述主要實現目標類實現的新接口,繼承於DefaultIntroductionAdvisor public HiddenPasswordAdvisor(Advice advice, IntroductionInfo introductionInfo) { super(advice, introductionInfo); } //第二個參數直接指定新增的接口,繼承於DefaultIntroductionAdvisor public HiddenPasswordAdvisor(DynamicIntroductionAdvice advice, Class<?> intf) { super(advice, intf); } //重寫了getClassFilter 由於DefaultIntroductionAdvisor沒有定義過濾那些Class @Override public ClassFilter getClassFilter() { return this; } //如果想不過濾任何類 可以直接使用DefaultIntroductionAdvisor @Override public boolean matches(Class<?> clazz) { for (Class clz: targetClasses) { if(clz.isAssignableFrom(clazz)) return true; } return false; } }
上面的構造函數尤其重要,注意看上面各個構造函數的解釋,然後我們在看看配置文件:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 未代理的類 --> <bean id="userService" class="com.maxfunner.service.impl.UserService" /> <bean id="adminService" class="com.maxfunner.service.impl.AdminService" /> <bean id="UserAdvice" class="com.maxfunner.advice.UserAdvice" /> <!-- 剛剛實現的一個複合切面的工具類 有個getComposablePointcut 獲得切面 --> <bean id="hiddenPasswordAdvice" class="com.maxfunner.advice.HiddenPasswordAdvice" /> <!-- 使用我們自定義的HiddenPasswordAdvisor 繼承自 DefaultIntroductionAdvisor --> <!-- 雖然我們定義了一個默認的構造方法,但是爲了演示我這裏也使用DefaultIntroductionAdvisor原來的構造方法 --> <bean id="LoginIntroductionAdvisor" class="com.maxfunner.advisor.HiddenPasswordAdvisor"> <constructor-arg ref="hiddenPasswordAdvice" /> <constructor-arg type="java.lang.Class" value="com.maxfunner.service.HiddenPasswordController" /> <property name="targetClasses"> <list> <value>com.maxfunner.service.impl.AdminService</value> </list> </property> </bean> <!-- 注意使用了abstract主要是作爲一個配置模板去使用,由於我們沒有接口實現方式去寫Service所以這裏使用proxyTargetClass --> <bean id="factoryBean" class="org.springframework.aop.framework.ProxyFactoryBean" abstract="true" p:proxyTargetClass="true" p:interceptorNames="LoginIntroductionAdvisor" /> <!-- 定義被proxy bean --> <bean id="userServiceProxy" parent="factoryBean" p:target-ref="userService" /> <bean id="adminServiceProxy" parent="factoryBean" p:target-ref="adminService" /> </beans>
然後開始調用看結果:
public static void main(String[] args){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) context.getBean("userServiceProxy"); AdminService adminService = (AdminService) context.getBean("adminServiceProxy"); userService.login("TONY","PWD"); userService.register("TONY","PWD"); adminService.login("TONY","PWD"); adminService.register("TONY","PWD"); //由於我們重寫了classfilter所以我們將UserService進行類型轉換的時候就會報錯 // HiddenPasswordController controller = (HiddenPasswordController) userService; // controller.hidden(false); HiddenPasswordController controller = (HiddenPasswordController) adminService; controller.hidden(false); userService.login("TONY","PWD"); userService.register("TONY","PWD"); adminService.login("TONY","PWD"); adminService.register("TONY","PWD"); }
可以看到如果沒有在getClassFilter中定義的類進行類型轉換會出錯的,以下是輸出:
Login : TONY, pwd : PWD
register : TONY , pwd PWD
advice ->>>>>> login method Username : TONY Password : *********
Admin Login : TONY, pwd : PWD
advice ->>>>>> register method Username : TONY Password : *********
Admin register : TONY , pwd PWD
advice ->>>>>> hidden method Username : false Password : *********
Login : TONY, pwd : PWD
register : TONY , pwd PWD
advice ->>>>>> login method Username : TONY Password : PWD
Admin Login : TONY, pwd : PWD
advice ->>>>>> register method Username : TONY Password : PWD
Admin register : TONY , pwd PWD
然後最後我們來看看自動代理,有兩種方案實現自動代理,第一種是通過BeanNameAutoProxyCreator,另外一種是DefaultAdvisorAutoProxyCreator
其實都非常簡單BeanNameAutoProxyCreator 就是通過名字進行自動代理,而DefaultAdvisorAutoProxyCreator是通過advisor中的classFilter進行自動代理,沒有什麼非常多的概念,然後我們現在先使用一下DefaultAdvisorAutoProxyCreator,這次我們使用最爲原始的StaticMethodMatcherPointcutAdvice,然後繼續使用回我們一開頭實現的StaticLoginMethodAdvisor 忘記了上拖上去看看,然後把bean名稱爲p開頭都自動創建代理:
<!-- 自動被代理 --> <bean id="userService" class="com.maxfunner.service.impl.UserService" /> <bean id="adminService" class="com.maxfunner.service.impl.AdminService" /> <bean id="UserAdvice" class="com.maxfunner.advice.UserAdvice" /> <bean id="staticLoginMethodAdvisor" class="com.maxfunner.advisor.StaticLoginMethodAdvisor"> <property name="advice" ref="UserAdvice" /> </bean> <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator" p:beanNames="userService" p:interceptorNames="staticLoginMethodAdvisor" p:optimize="true" />
其中beanNames熟悉支持通配符*,同時如果想匹配多個用,號進行分割。
測試一下:
public static void main(String[] args){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) context.getBean("userService"); AdminService adminService = (AdminService) context.getBean("adminService"); userService.login("TONY","PWD"); userService.register("TONY","PWD"); adminService.login("TONY","PWD"); adminService.register("TONY","PWD"); }>>>>>>>>>>login invoked !!!
Login : TONY, pwd : PWD
register : TONY , pwd PWD
Admin Login : TONY, pwd : PWD
Admin register : TONY , pwd PWD
沒毛病,繼續~ 最後一個DefaultAdvisorAutoProxyCreator,只要看Advisor中定義的ClassFilter,比上面更加簡單的配置(由於我們使用的StaticLoginMethodAdvisor 已經定義了classFilter了 只所以只自定創建userService的代理):
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 自動被代理 --> <bean id="userService" class="com.maxfunner.service.impl.UserService" /> <bean id="adminService" class="com.maxfunner.service.impl.AdminService" /> <bean id="UserAdvice" class="com.maxfunner.advice.UserAdvice" /> <bean id="staticLoginMethodAdvisor" class="com.maxfunner.advisor.StaticLoginMethodAdvisor"> <property name="advice" ref="UserAdvice" /> </bean> <!-- 就一行沒有什麼配置,簡單到不敢相信 --> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" /> </beans>
測試一下:
public static void main(String[] args){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) context.getBean("userService"); AdminService adminService = (AdminService) context.getBean("adminService"); userService.login("TONY","PWD"); userService.register("TONY","PWD"); adminService.login("TONY","PWD"); adminService.register("TONY","PWD"); }>>>>>>>>>>login invoked !!!
Login : TONY, pwd : PWD
register : TONY , pwd PWD
Admin Login : TONY, pwd : PWD
Admin register : TONY , pwd PWD