Spring AOP切点匹配

切点匹配

本笔记基于黑马程序员 Spring高级源码解读

更美观清晰的版本在:Github

1. 两种切点匹配模式

我们经常这样装配我们的AOP:

@Component
@Aspect
public class MyAspect {
    @Before("execution(* bar())")
    public void before() {/*...*/}
    @After(@annotation(org.springframework.transaction.annotation.Transactional))
    public void after() {/*...*/}
}

一种是根据execution表达式进行方法的匹配,另一种是根据@annotation寻找方法上是否有特定的注解进行匹配。
我们可以手动装配切点:

public class PointcutMatching {

    public static void main(String[] args) throws Exception {
        AspectJExpressionPointcut pointcut1 = new AspectJExpressionPointcut();
        // 根据方法名字匹配
        pointcut1.setExpression("execution(* bar())");
        System.out.println(pointcut1.matches(T1.class.getMethod("foo"), T1.class)); // false
        System.out.println(pointcut1.matches(T1.class.getMethod("bar"), T1.class)); // true

        AspectJExpressionPointcut pointcut2 = new AspectJExpressionPointcut();
        // 根据注解匹配
        pointcut2.setExpression("@annotation(org.springframework.transaction.annotation.Transactional)");
        System.out.println(pointcut2.matches(T1.class.getMethod("foo"), T1.class)); // true
        System.out.println(pointcut2.matches(T1.class.getMethod("bar"), T1.class)); // false
    }

    static class T1 {
        @Transactional
        public void foo() {}
        public void bar() {}
    }
}

2. 特殊的注解:@Transactional

@Transactional是一个在生产中很常用的注解,用来标注方法是一个事务。@Transactional不仅可以标注在方法上,还可以标注在一个类上,用来表示这个类的所有方法都是事务;
甚至可以标注在一个接口上,用来表示所有实现了该接口的类的所有方法都是事务。对于后两种情况,我们使用AspectJExpressionPointcut肯定是做不到的。

有没有一种切点是可以一次性“切”到方法、类和接口的呢?这就是StaticMethodMatcherPointcut一系列类所提供的功能:通过重写match方法,我们就能精准地识别方法、类甚至是接口上的注解。

我们首先来看一下StaticMethodMatcherPointcut等一系列类的继承结构。StaticMethodMatcherPointcut本身是一个抽象类,match方法继承自StaticMethodMatcher这个父抽象类:

接口        MethodMatcher                 Pointcut
                ^                           ^
                |                           |
        StaticMethodMatcher                 |
                ^                           |
                |                           |
    StaticMethodMatcherPointcut -------------
                ^
                |
       其他一些更加具体的实现类

对于继承了StaticMethodMatcherPointcut的一些更具的的实现类,其本质上也是将match方法进行重写。例如NameMatchMethodPointcutmatch方法重写从而实现切点的名称匹配。

那么我们现在来重写一下我们的match方法。我们希望先从方法检测起,如果没有发现注解则检测类,再没有发现则检测其实现的接口(如果有的话),再没发现则返回false

StaticMethodMatcherPointcut pointcut3 = new StaticMethodMatcherPointcut() {
    @Override
    public boolean matches(Method method, Class<?> targetClass) {
        if (method.isAnnotationPresent(Transactional.class)) {
            return true;
        } else if (targetClass.isAnnotationPresent(Transactional.class)) {
            return true;
        } else if (targetClass.getInterfaces().length != 0) {
            Class<?>[] interfaces = targetClass.getInterfaces();
            for (Class<?> anInterface : interfaces) {
                if (anInterface.isAnnotationPresent(Transactional.class)) {
                    return true;
                }
            }
        }
        return false;
    }
};

可以测试一下是否正确:除了刚才已经创建了的T1,我们再如下创建T2T3并让T3实现一个接口I1

public class PointcutMatching {
    public static void main(String[] params) {
        /*...*/
        System.out.println(pointcut3.matches(T1.class.getMethod("foo"), T1.class));
        System.out.println(pointcut3.matches(T2.class.getMethod("foo"), T2.class));
        System.out.println(pointcut3.matches(T2.class.getMethod("bar"), T2.class));
        System.out.println(pointcut3.matches(T3.class.getMethod("foo"), T3.class));
        System.out.println(pointcut3.matches(T3.class.getMethod("bar"), T3.class));
    }
    static class T1 {
        @Transactional
        public void foo() {}
        public void bar() {}
    }

    @Transactional
    static class T2 {
        public void foo() {}
        public void bar() {}
    }

    @Transactional
    interface I1 { void foo(); void bar();}
    static class T3 implements I1 {
        public void foo() {}
        public void bar() {}
    }
}

打印结果应当都是true

true
true
true
true
true

当然,我们可以不自己写反射,而是采用Spring为我们提供的MergedAnnotations。这个类的from方法可以一次性获取方法和类、甚至接口上的所有注解:

StaticMethodMatcherPointcut pointcut3 = new StaticMethodMatcherPointcut() {
    @Override
    public boolean matches(Method method, Class<?> targetClass) {
        // 方法上是否加了@Transactional
        MergedAnnotations annotation = MergedAnnotations.from(method);
        if (annotation.isPresent(Transactional.class)) {
            return true;
        }
        // 类上是否加了@Transactional
        annotation = MergedAnnotations.from(targetClass);
        if (annotation.isPresent(Transactional.class)) {
            return true;
        }
        // 接口上是否加了@Transactional
        annotation = MergedAnnotations.from(targetClass.getInterfaces());
        if (annotation.isPresent(Transactional.class)) {
            return true;
        }
        return false;
    }
};

由于我们只给from方法传入了一个参数,所以默认采用的搜索策略是SearchStrategy.DIRECT:顾名思义,就是只查找当前等级的注解。
如果我们再给from指定SearchStrategy.TYPE_HIERARCHY,那么它就会对整个类型层次结构进行全面搜索,包括超类和已实现的接口:

StaticMethodMatcherPointcut pointcut3 = new StaticMethodMatcherPointcut() {
    @Override
    public boolean matches(Method method, Class<?> targetClass) {
        // 方法上是否加了@Transactional
        MergedAnnotations annotation = MergedAnnotations.from(method);
        if (annotation.isPresent(Transactional.class)) {
            return true;
        }
        // 类、父类或接口上是否加了@Transactional
        annotation = MergedAnnotations.from(targetClass, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY);
        if (annotation.isPresent(Transactional.class)) {
            return true;
        }
        return false;
    }
};

同样可以打印测试一下,我们会发现都显示true,说明@Transactional注解都被完全扫描到了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值