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

猜你喜欢

转载自blog.csdn.net/weixin_43364172/article/details/84537098