SpringAOP之切點和切面的深入理解

在看這篇文章之前需要首先理解 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






發佈了53 篇原創文章 · 獲贊 65 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章