1. 程式人生 > >Spring 5 - Advisors和Pointcuts

Spring 5 - Advisors和Pointcuts

Spring 5 - Advisors和Pointcuts

ProxyFactory類提供了獲取和配置AOP代理例項的簡單的辦法。ProxyFactory.addAdvice()方法配置代理的advice,它代表後面的addAdvisor(),增加DefaultPointcutAdvisor例項,使用pointcut配置它。這樣,advice作用於目標的所有方法。有時候,比如你使用AOP做日誌,可能希望這樣,但是有時候,你想限制advice作用的方法。
當然,你可以檢查方法是否適合advised,但是,這樣做有幾個缺點。首先,接受方法的硬編碼降低了advice的可重用性。二,會導致效能問題。有時候,需要硬編碼檢查,比如以前的檢查弱key的例子。

Pointcut

實現Pointcut介面可以增加Pointcuts:

package org.springframework.aop;

public interface Pointcut {
    ClassFilter getClassFilter();
    MethodMatcher getMethodMatcher();
}

Pointcut介面定義了兩個方法,getClassFilter()和getMethodMatcher(),他們分別返回ClassFilter和MethodMatcher的例項。Spring提供了一些實現,一般不用自己定製。
當決定Pointcut是否應用於某方法的時候,Spring使用ClassFilter先檢查方法的類。

package org.springframework.aop;

public interface ClassFilter {
    boolean matches(Class<?> clazz);
}

如果可用,返回true。MethodMatcher介面比較複雜:

package org.springframework.aop;

public interface MethodMatcher {
    boolean matches(Method m, Class<?> targetClass);
    boolean isRuntime()
; boolean matches(Method m, Class<?> targetClass, Object[] args); }

Spring支援兩種MethodMatcher-靜態和動態-由isRuntime()決定。使用MethodMatcher之前,Spring呼叫isRuntime()做決定-
如果MethodMatcher是靜態的,返回false;如果是動態的,返回true。
對於靜態pointcut,Spring為目標的每個方法呼叫matches(Method, Class)方法,並且快取返回值。這樣,每個方法只檢查一次。
對於動態pointcuts,方法第一次呼叫的時候,Spring仍然使用matches(Method, Class)執行靜態檢查,決定方法的整體適用性。如果靜態檢查返回true,Spring使用matches(Method, Class, Object[])為每次呼叫執行進一步的檢查。這樣,動態的MethodMatcher基於特定的方法呼叫決定是否應用pointcut。比如,只在某整數引數大於100時才應用某pointcut,每次呼叫時,都要做檢查。
所以,儘可能使用靜態檢查。

有效的Pointcut實現

Spring提供了Pointcut的八個實現:兩個抽象類(用來快捷建立靜態和動態pointcuts),六個具體類。

Implementation Class Description
org.springframework.aop.support.annotation.AnnotationMatchingPointcut 查詢類或者方法的特定註解
org.springframework.aop.aspectj.AspectJExpressionPointcut 使用AspectJ weaver評估pointcut表示式
org.springframework.aop.support.ComposablePointcut 使用union()和intersection()這樣的方法,組合兩個或者多個pointcuts
org.springframework.aop.support.ControlFlowPointcut 檢查控制流內的所有方法,就是說,要呼叫一個方法,而直接或者間接呼叫的其他任何方法
org.springframework.aop.support.DynamicMethodMatcherPointcut 用來構造動態pointcuts
org.springframework.aop.support.JdkRegexpMethodPointcut 使用正則表示式定義pointcuts
org.springframework.aop.support.NameMatchMethodPointcut 簡單的方法名稱列表的匹配
org.springframework.aop.support.StaticMethodMatcherPointcut 用來構造靜態pointcuts

Pointcut

使用DefaultPointcutAdvisor

使用任何Pointcut實現前,你首先要增加一個Advisor介面(或者PointcutAdvisor)的例項。Spring的Advisor代表一個aspect-耦合的advice和pointcuts-管理那些方法應該被advised,以及怎麼advised。Spring提供了一些PointcutAdvisor的實現,現在我們只關心一個DefaultPointcutAdvisor-它是個簡單的PointcutAdvisor,關聯一個Pointcut和一個Advice。

使用StaticMethodMatcherPointcut增加靜態Pointcut

下來,我們擴充套件StaticMethodMatcherPointcut,增加一個簡單的靜態pointcut。StaticMethodMatcherPointcut擴充套件了StaticMethodMatcher類,
它實現了MethodMatcher介面,你需要實現matches(Method, Class<?>)。同時,我們覆蓋了getClassFilter()方法,確保方法所在的型別正確。

public class GoodGuitarist implements Singer {
        @Override public void sing() {
                System.out.println("Who says  I can't be free \n" +
                                "From  all of the things that I used to be");
        }
}
public class GreatGuitarist implements Singer {
        @Override public void sing() {
                System.out.println("I shot the sheriff, \n" +
                                "But I did not shoot the deputy");
        }
}

interface Singer {
    void sing();
}

下來是SimpleStaticPointcut類,使DefaultPointcutAdvisor只能應用於GoodGuitarist類的sing()方法:

import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.StaticMethodMatcherPointcut;

import java.lang.reflect.Method;

public class SimpleStaticPointcut extends StaticMethodMatcherPointcut {
    @Override
    public boolean matches(Method method, Class<?> cls) {
        return ("sing".equals(method.getName()));
    }

    @Override
    public ClassFilter getClassFilter() {
        return cls -> (cls == GoodGuitarist.class);
    }


}

看matches(Method, Class<?>),只有方法名稱是sing才返回true。
下來是SimpleAdvice,在方法呼叫前後寫訊息:

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class SimpleAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println(">> Invoking " + invocation.getMethod().getName());
        Object retVal = invocation.proceed();
        System.out.println(">> Done\n");
        return retVal;
    }
}

下來是測試,使用SimpleAdvice和SimpleStaticPointcut增加一個DefaultPointcutAdvisor的例項。因為兩個類實現了相同的介面,代理可以基於介面增加:

import org.aopalliance.aop.Advice;
import org.springframework.aop.Advisor;
import org.springframework.aop.Pointcut;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;

public class StaticPointcutDemo {
    public static void main(String... args) {
        GoodGuitarist johnMayer = new GoodGuitarist();
        GreatGuitarist ericClapton = new GreatGuitarist();
        Singer proxyOne;
        Singer proxyTwo;
        Pointcut pc = new SimpleStaticPointcut();
        Advice advice = new SimpleAdvice();
        Advisor advisor = new DefaultPointcutAdvisor(pc, advice);
        ProxyFactory pf = new ProxyFactory();
        pf.addAdvisor(advisor);
        pf.setTarget(johnMayer);
        proxyOne = (Singer) pf.getProxy();
        pf = new ProxyFactory();
        pf.addAdvisor(advisor);
        pf.setTarget(ericClapton);
        proxyTwo = (Singer) pf.getProxy();
        proxyOne.sing();
        proxyTwo.sing();
    }
}

輸出是

>> Invoking sing
Who says  I can't be free 
From  all of the things that I used to be
>> Done

I shot the sheriff, 
But I did not shoot the deputy

使用DyanmicMethodMatcherPointcut增加動態Pointcut

增加動態pointcut和增加靜態的差不多:

class SampleBean {
    void foo(int x) {
        System.out.println("Invoked foo() with: " + x);
    }

    void bar() {
        System.out.println("Invoked bar()");
    }
}

比如,我們只advise foo()方法,傳入的整數引數不等於100。
DynamicMethodMatcherPointcut有一個抽象方法,matches(Method, Class<?>, Object[]),還必須實現matches(Method, Class<?>)方法控制靜態檢查行為:

import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.DynamicMethodMatcherPointcut;

import java.lang.reflect.Method;

public class SimpleDynamicPointcut extends DynamicMethodMatcherPointcut {

    @Override
    public boolean matches(Method method, Class<?> cls) {
        System.out.println("Static check for " + method.getName());
        return "foo".equals(method.getName());
    }

    @Override
    public boolean matches(Method method, Class<?> cls, Object... args) {
        System.out.println("Dynamic check for " + method.getName());
        if (args.length == 0) {
            throw new RuntimeException("args length error.");
        } else {
            int x = (int) args[0];
            return x != 100;
        }
    }

    @Override
    public ClassFilter getClassFilter() {
        return cls -> (cls == SampleBean.class);
    }
}

使用matches(Method, Class<?>)做方法檢查,使用matches(Method, Class<?>, Object[])做引數檢查。通過覆蓋matches(Method method, Class<?> cls)方法,我們只對SampleBean類的foo(int x)方法做動態檢查,而不會對bar()方法做動態檢查。
現在做測試:

import org.springframework.aop.Advisor;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;

public class DynamicPointcutDemo {
    public static void main(String... args) {
        SampleBean target = new SampleBean();
        Advisor advisor = new DefaultPointcutAdvisor(new SimpleDynamicPointcut(), new SimpleAdvice());
        ProxyFactory pf = new ProxyFactory();
        pf.setTarget(target);
        pf.addAdvisor(advisor);
        SampleBean proxy = (SampleBean)pf.getProxy();
        proxy.foo(1);
        proxy.foo(10);
        proxy.foo(100);
        proxy.bar();
        proxy.bar();
        proxy.bar();
    }
}

輸出是

Static check for foo
Static check for bar
Static check for toString
Static check for clone
Static check for foo
Dynamic check for foo
>> Invoking foo
Invoked foo() with: 1
>> Done

Dynamic check for foo
>> Invoking foo
Invoked foo() with: 10
>> Done

Dynamic check for foo
Invoked foo() with: 100
Static check for bar
Invoked bar()
Invoked bar()
Invoked bar()

正如我們期望的,只有前兩個foo()是advised。bar()呼叫都不是動態的。有意思的是,foo()方法執行兩次靜態檢查:初始化期間所有方法都被檢查,和第一次呼叫的時候。

簡單的名稱匹配

我們增加pointcut的時候,經常是隻匹配方法的名稱,而忽略它的簽名和返回型別。現在,我們使用NameMatchMethodPointcut(StaticMethodMatcherPointcut的子類)匹配方法的名稱列表。
先看GrammyGuitarist類:

class GrammyGuitarist implements Singer {
    @Override
    public void sing() {
        System.out.println("sing: Gravity is working against me\n" +
                "And gravity wants to bring me down");
    }

    public void sing(Guitar guitar) {
        System.out.println("play: " + guitar.play());
    }

    void rest() {
        System.out.println("zzz");
    }

    void talk() {
        System.out.println("talk");
    }
}

我們打算匹配sing()、sing(Guitar)和rest()方法:

import org.springframework.aop.Advisor;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.NameMatchMethodPointcut;

public class NamePointcutDemo {
    public static void main(String... args) {
        GrammyGuitarist johnMayer = new GrammyGuitarist();
        NameMatchMethodPointcut pc = new NameMatchMethodPointcut();
        pc.addMethodName("sing");
        pc.addMethodName("rest");
        Advisor advisor = new DefaultPointcutAdvisor(pc, new SimpleAdvice());
        ProxyFactory pf = new ProxyFactory();
        pf.setTarget(johnMayer);
        pf.addAdvisor(advisor);
        GrammyGuitarist proxy = (GrammyGuitarist) pf.getProxy();
        proxy.sing();
        proxy.sing(new Guitar());
        proxy.rest();
        proxy.talk();
    }
}

不需要增加類,簡單地增加一個NameMatchMethodPointcut例項就可以了。我們使用addMethodName()方法給兩個方法增加了pointcut。執行結果是:

>> Invoking sing
sing: Gravity is working against me
And gravity wants to bring me down
>> Done

>> Invoking sing
play: G C G C Am D7
>> Done

>> Invoking rest
zzz
>> Done

talk

可以看到,只有talk()是unadvised。

使用正則表示式增加Pointcuts

也可以使用JdkRegexpMethodPointcut,通過正則表示式匹配方法名,增加pointcut:

class Guitarist implements Singer {
    @Override
    public void sing() {
        System.out.println("Just keep me where  the light is");
    }

    void sing2() {
        System.out.println("Just keep me where  the light is");
    }

    void rest() {
        System.out.println("zzz");
    }
}

測試程式碼:

import org.springframework.aop.Advisor;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.JdkRegexpMethodPointcut;

public class RegexpPointcutDemo {

    public static void main(String... args) {
        Guitarist johnMayer = new Guitarist();
        JdkRegexpMethodPointcut pc = new JdkRegexpMethodPointcut();
        pc.setPattern(".*sing.*");
        Advisor advisor = new DefaultPointcutAdvisor(pc, new SimpleAdvice());
        ProxyFactory pf = new ProxyFactory();
        pf.setTarget(johnMayer);
        pf.addAdvisor(advisor);
        Guitarist proxy = (Guitarist) pf.getProxy();
        proxy.sing();
        proxy.sing2();
        proxy.rest();
    }
}

我們也不需要增加類。正則表示式的前導“.*”表示匹配任何包和類的方法。輸出是

>> Invoking sing
Just keep me where  the light is
>> Done

>> Invoking sing2
Just keep me where  the light is
>> Done

zzz

增加註解匹配的Pointcuts

如果你的程式是基於註解的,就可能想使用註解定義pointcuts。為此,Spring提供了AnnotationMatchingPointcut。我們修改一下前面的例子,看怎麼使用註解定義pointcut。
先定義一個註解,叫AdviceRequired,用它宣告pointcut:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface AdviceRequired {
}

修改Guitarist類:

class Guitarist implements Singer {

    @Override
    public void sing() {
        System.out.println("Dream of ways to throw it all away");
    }

    @AdviceRequired
    void sing(Guitar guitar) {
        System.out.println("play: " + guitar.play());
    }

    void rest() {
        System.out.println("zzz");
    }
}

現在做測試:

import org.springframework.aop.Advisor;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;

/**
 * Created by leishu on 18-11-26.
 */
public class AnnotationPointcutDemo {
    
    public static void main(String... args) {
        Guitarist johnMayer = new Guitarist();
        AnnotationMatchingPointcut pc = AnnotationMatchingPointcut.forMethodAnnotation(AdviceRequired.class);
        Advisor advisor = new DefaultPointcutAdvisor(pc, new SimpleAdvice());
        ProxyFactory pf = new ProxyFactory();
        pf.setTarget(johnMayer);
        pf.addAdvisor(advisor);
        Guitarist proxy = (Guitarist) pf.getProxy();
        proxy.sing(new Guitar());
        proxy.rest();
    }
}

forMethodAnnotation()方法應用於方法級。如果使用forClassAnnotation(),可應用於類級。輸出是

>> Invoking sing
play: G C G C Am D7
>> Done

zzz