全站资源开放下载,感谢广大网友的支持
链接失效请移步职业司平台
非盈利平台

非盈利平台

只为分享一些优质内容

Java帮帮-微信公众号

Java帮帮-微信公众号

将分享做到极致

微信小程序

微信小程序

更方便的阅读

职业司微信公众号

职业司微信公众号

实时动态通知

安卓APP

安卓APP

我们从此不分开

程序员生活志-公众号

程序员生活志-公众号

程序员生活学习圈,互联网八卦黑料

支付宝赞助-Java帮帮社区
微信赞助-Java帮帮社区

Spring AOP的底层实现原理(二)[云图智联]

2
发表时间:2020-07-06 10:15

免费学习视频欢迎关注云图智联:https://e.yuntuzhilian.com/

在动态代理 和 CGLIB 的支持下, Spring AOP 框架的实现经过了两代。从 Spring AOP 框架第一次发布,到 Spring 2.0 发布之前的 AOP 实现,是 Spring 第一代 AOP 实现。Spring 2.0 发布后的 AOP 实现是第二代。但是,Spring AOP 的底层实现机制一直没有变,唯一改变的,是各种 AOP 概念实现的表现形式以及 Spring AOP 的使用方式。

一、Joinpoint
  Spring AOP 中,仅支持方法级别的 Joinpoint ,更确切的说,只支持方法执行 (Method Execution )类型的 Joinpoint,虽然 Spring AOP 仅提供方法拦截,但是实际的开发过程中,这已经可以满足 80% 的开发需求了。

  Spring AOP 之所以如此,主要有以下几个原因:

  1. Spring AOP 要提供一个简单而强大的 AOP 框架,并不想因大而全使得框架本身过于臃肿。能够仅付出 20% 的努力,就能够得到 80% 的回报。否则,事倍功半,并不是想看到的结果。
  2. 对于类中属性 (Field )级别的 Joinpoint ,如果提供这个级别的拦截,那么就破坏了面向对象的封装,而且,完全可以通过 setter 和 getter 方法的拦截达到同样的目的。
  3. 如果应用需求非常特殊,完全超出了 Spring AOP 提供的那 80% 的需求支持,可以求助于现有其他 AOP 实现产品,如 AspectJ。 目前看来, AspectJ 是 Java 平台对 AOP 支持最完善的产品,同时,Spring AOP 也提供了对 Aspect的支持。

二、Pointcut
  Spring中使用org.springframework.aop.Pointcut 作为其 AOP 框架中的所有 Pointcut 的最顶层抽象。

package org.springframework.aop;  

public interface Pointcut {  
    //用于匹配被织入操作的对象
    ClassFilter getClassFilter();  
    //用于匹配被织入操作的对象中的方法
    MethodMatcher getMethodMatcher();  

    Pointcut TRUE = TruePointcut.INSTANCE;  
}  
  为什么要分开匹配?是为了可以重用不同级别的匹配定义,并且可以在不同的级别或者相同的级别上进行组合操作。

ClassFilter
package org.springframework.aop;  

public interface ClassFilter {  

    boolean matches(Class<?> clazz);  

    ClassFilter TRUE = TrueClassFilter.INSTANCE;  
}  
  当织入的目标对象和Point指定的类型相同时,返回true,否则返回false,即意味着不会对这个类型的目标对象进行织入操作

比如,如果仅希望对系统中的 Foo 类型的对象执行织入,则可以

package prx.aop.proxy;  

import org.springframework.aop.ClassFilter;  

public class FooClassFilter implements ClassFilter{  

    public boolean matches(Class<?> clazz) {
        //只匹配Foo.class  
        return Foo.class.equals(clazz);  
    }  

}  
  如果类型对所捕捉的 Joinpoint 无所谓,那么 Pointcut 中使用的 ClassFilter 可以直接使用ClassFilter TRUE = TrueClassFilter.INSTANCE

MethodMatcher
package org.springframework.aop;  

import java.lang.reflect.Method;  


public interface MethodMatcher {  

    boolean matches(Method method, Class<?> targetClass);  

    boolean isRuntime();  

    boolean matches(Method method, Class<?> targetClass, Object[] args);  

    MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;  

}  
MethodMatcher 通过重载,定义了两个 matches 方法,而这两个方法的分界线就是 isRuntime 方法,这里要特别注意!
  
注意到三参数的matches方法中,最后一个参数是args,因此也就可以想到:两个 mathcers 方法的区别在于,在进行方法拦截的时候,是否匹配方法的参数
  比如:现在要对 登录方法 login(String username, String passwod) 进行拦截
  1. 只想在 login 方法之前插入计数功能,那么 login 方法的参数对于 Joinpoint 捕捉就是可以忽略的。
  2. 在用户登录的时候对某个用户做单独处理(拒绝登录 或 给予特殊权限),那么方法的参数在匹配 Joinpoint 时必须要考虑到

根据是否对方法的参数进行匹配,Pointcut可以分为StaticMethodMatcher和DynamicMethodMatcher,当isRuntime()返回false,表明不对参数进行匹配,为StaticMethodMatcher,返回true时,表示要对参数进行匹配,为DynamicMethodMatcher。

一般情况下,DynamicMethodMatcher会影响性能,所以我们一般使用StaticMethodMatcher就行了

StaticMethodMatcher
  前一种情况下, isRuntime 返回 false , 表示不会考虑具体 Joinpoint 的方法参数, 这种类型的 MethodMatcher称之为 StaticMethodMatcher。因为不用每次都检查参数,那么对于同样的类型的方法匹配结果,就可以在框架内部缓存以提高性能。


package org.springframework.aop.support;  

import java.lang.reflect.Method;  

import org.springframework.aop.MethodMatcher;  

public abstract class StaticMethodMatcher implements MethodMatcher {  

    public final boolean isRuntime() {  
        return false;  
    }  
    //三参数matches抛出异常,使其不被调用
    public final boolean matches(Method method, Class<?> targetClass, Object[] args) {  
        // should never be invoked because isRuntime() returns false  
        throw new UnsupportedOperationException("Illegal MethodMatcher usage");  
    }  

}  
DynamicMethodMatcher
  当 isRuntime 方法返回 true 时, 表明 MethodMatcher 将会每次都对方法调用的参数进行匹配检查,这种类型的MethodMatcher 称之为 DynamicMethodMatcher。 因为每次都要对方法参数进行检查,无法对匹配结果进行缓存,所以,匹配效率相对 StatisMethodMatcher 来说要差。

  注意:
  如果一个 MethodMatcher 为 DynamicMethodMatcher , 那么只有 isRuntime 返回 true, 而且matchers(Method method, Class targetClass) 也返回 true 的时候, 三个参数的 matchers 方法将被执行,进行进一步检查匹配条件。否则不会执行 三个参数的 matchers 方法,直接返回 false 了。


package org.springframework.aop.support;  

import java.lang.reflect.Method;  

import org.springframework.aop.MethodMatcher;  

public abstract class DynamicMethodMatcher implements MethodMatcher {  

    public final boolean isRuntime() {  
        return true;  
    }  

    //DynamicMethodMatcher 中只有两参数的mathches方法return true,三参数才能被调用
    public boolean matches(Method method, Class<?> targetClass) {  
        return true;  
    }  

}  
Pointcut家族
  在 MethodMatcher 类型的基础上, Pointcut 可以分为两类, 即 StaticMethodMatcherPointcut ,DynamicMethodMatcherPointcut。
  


1. NameMatchMethodPointcut
  最简单的 Pointcut 实现,根据自身指定的一组方法名称与 Joinpoint 处的方法的名称进行匹配,支持“*”通配符实现简单模糊查询


NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();  

pointcut.setMappedName("methodName");  

pointcut.setMappedNames(new String[]{"methodName1", "methodName2"});  

pointcut.setMappedNames(new String[]{"method*", "*Name", "method*Num"});
  但是, NameMatchMethodPointcut 无法对重载的方法名进行匹配, 因为它仅对方法名匹配,不考虑参数信息。

2. JdkRegexpMethodPointcut
  专门用于Java的正则表达式匹配型Point


JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();  

pointcut.setPattern(".*method.*");  

pointcut.setPatterns(new String[]{".*method.*", ".*name.*"});  
  注意:使用正则表达式来匹配对应的 Joinpoint 所处的方法时, 正则表达式的匹配模式必须以匹配整个方法签名的形式指定,而不能像 NameMatchMethodPointcut 那样仅给出匹配的方法名称。

public class Foo {  
    public void doSomething() {  

    }  
}  
  如果使用正则表达式 .doS. 则会匹配 Foo 的 doSomething 方法, 即完整签名:prx.aop.proxy.Foo.doSomething 。 但是如果 Pointcut 使用 doS.* 作为匹配的正则表达式模式,就无法捕捉到Foo 的 doSomething 方法的执行。

3. AnnotationMatchingPointcut
  根据对象是否有指定类型的注解来匹配Pointcut
  有两种注解,类级别注解ClassLevelAnnotation,和方法级别注解MethodLevelAnnotation :


@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.TYPE)  
public @interface ClassLevelAnnotation {  

}  
@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.METHOD)  
public @interface MethodLevelAnnotation {  

}
  用法:

//仅指定类级别的注解, 标注了 ClassLevelAnnotation 注解的类中的所有方法执行的时候,将全部匹配。  
AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(ClassLevelAnnotation.class);  

//还可以使用静态方法创建 pointcut 实例  
AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forClassAnnotation(ClassLevelAnnotation.class);  


//仅指定方法级别的注解,标注了 MethodLeavelAnnotaion 注解的方法(忽略类匹配)都将匹配  
AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forMethodAnnotation(MethodLevelAnnotation.class);  


//同时限定类级别和方法级别的注解,只有标注了 ClassLevelAnnotation 的类中 同时标注了 MethodLevelAnnotation 的方法才会匹配  
AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(ClassLevelAnnotation.class, MethodLevelAnnotation.class);  
  实例:


@ClassLevelAnnotation  
public class TargetObject {  

    @MethodLevelAnnotation  
    public void method1() {  
        System.out.println("target : method1");  
    }  

    public void method2() {  
        System.out.println("target : method2");  
    }  
}  
public class Client {  

    public static void main(String[] args) {     
        //pointcut 定义, 匹配方式可以按上面的说明修改,   这里是注解类的所有方法都匹配  
        AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forClassAnnotation(ClassLevelAnnotation.class);  

        // advice 定义, 根据前面的介绍知道 这个是 横切逻辑的定义, 这里是 方法执行前插入横切逻辑  
        BeforeAdvice advice = new MethodBeforeAdvice() {  
            public void before(Method method, Object[] args, Object target) throws Throwable {  
                System.out.println(target.getClass().getSimpleName() + ":" + method.getName() + " - before logic ");  
            }     
        };  

        // Spring 中的 Aspect , pointcut 和 advice 的封装类  
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();  
        advisor.setPointcut(pointcut);  
        advisor.setAdvice(advice);  

        // Spring 基本织入器 weaving 和 weaver  
        ProxyFactory weaver = new ProxyFactory();  
        weaver.setTarget(new TargetObject());   //指定代理目标对象  
        weaver.addAdvisor(advisor);             //指定 Aspect  

        Object proxyObject = weaver.getProxy(); //生成代理对象 (这里没接口, Spring 使用 CGLIB 创建子类)  

        ((TargetObject) proxyObject).method1();  
        ((TargetObject) proxyObject).method2();  
    }  
}  
4. ComposablePointcut
  ComposablePointcut 是 Spring AOP 提供的可以进行 Pointcut 逻辑运算的 Pointcut 实现, 它可以进行 Pointcut之间的 “并” 以及 “交” 运算。

ComposablePointcut pointcut1 = new ComposablePointcut(classFilter1, methodMatcher1);  
ComposablePointcut pointcut2 = new ComposablePointcut(classFilter2, methodMatcher2);  

//求并集  
ComposablePointcut unitedPointcut       = pointcut1.union(pointcut2);  
//求交集  
ComposablePointcut intersectionPointcut = pointcut1.intersection(unitedPointcut);  

assertEquals(pointcut1, intersectionPointcut);
  Spring AOP 还提供了 工具类: org.springframework.aop.support.Pointcuts

Pointcut pointcut1 = ...;
Pointcut pointcut2 = ...;

//求并集  
Pointcut unitedPointcut = Pointcuts.union(pointcut1, pointcut2);  
//求交集  
Pointcut intersectionPointcut = Pointcuts.intersection(pointcut1, unitedPointcut);  

assertEquals(pointcut1, intersectionPointcut);  
5. ControlFlowPointcut
  非常有个性的 Pointcut 类型, 不是很常用。 指定只有当 Joinpoint 指定的某个方法 在 某个特定的 类中被调用时,才对其进行拦截。
  而一般情况是,Joinpoint 指定的方法,无论被谁调用,都会被拦截。
  举例:

class TargetObject {

    public void targetMethod() {
        System.out.println("TargetObject : method1");
    }
}


//目标类
class TargetCaller {

    private TargetObject target;

    public void callMethod() {
        target.targetMethod();
    }

    public void setTarget(TargetObject target) {
        this.target = target;
    }
}

public class TestControlFlowPointcut {

    public static void main(String[] args) {
        //只有TargetCaller中的方法才会被拦截
        ControlFlowPointcut pointcut = new ControlFlowPointcut(TargetCaller.class);
        BeforeAdvice beforeAdvice = new MethodBeforeAdvice() {
            public void before(Method method, Object[] objects, Object o) throws Throwable {
                System.out.println(method.getClass().getSimpleName() + ":" +
                        method.getName() + " - before logic ");
            }
        };

        // Spring 中的 Aspect
        PointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, beforeAdvice);

        // Spring 基本织入器 weaving 和 weaver
        ProxyFactory weaver = new ProxyFactory();
        weaver.setTarget(new TargetObject());   //指定代理目标对象
        weaver.addAdvisor(advisor);   //指定方面

        Object proxy = weaver.getProxy();

        //直接调用Targetobject的方法不会被拦截
        ((TargetObject)proxy).targetMethod();

        //使用ControlFlowPointcut指定的类中的方法才会被拦截
        TargetCaller caller = new TargetCaller();
        caller.setTarget((TargetObject)proxy);
        caller.callMethod();
    }
}
  结果:

TargetObject : method1
Method:targetMethod - before logic
TargetObject : method1
  如果在 ControlFlowPointcut 的构造方法中单独指定 Class 类型的参数,如上面的例子,那么 ControlFlowPointcut将尝试匹配指定的 Class 中声明的所有方法,跟目标对象的 Joinpoint 处的方法流程组合。 所以,如果是想要做到“只有 TargetCaller 类的 callMethod 方法调用 TargetObject.method1() 才拦截,而 TargetCaller 的其他方法全都忽略” 的话,可以在构造时,传入第二个参数

ControlFlowPointcut pointcut = new ControlFlowPointcut(TargetCaller.class, "callMethod");
  因为 ControlFlowPointcut 类型的 Pointcut 需要在运行期间检查程序的调用栈,而且每次方法调用都需要检查,所以性能比较差,应该尽量避免使用。

三、总结
  1. Pointcut最底层的是一个Pointcut接口,里面有对象匹配方法getClassFilter和方法匹配方法getMethodMatcher()
 
  2. MethodMatcher接口中有三个方法,两参数和三参数的matches方法,和一个isRuntime方法,根据isRuntime返回值决定是否匹配方法参数从而决定调用哪个matches方法

  3. Point中分为6种Point实现,分别是:NameMatchMethodPointcut、JdkRegexpMethodPointcut、Perl5RegexpMethodPoint、AnnotationMatchingPointcut、ComposablePointcut、ControlFlowPointcut,各自的特性在各自的名字中体现

免费学习视频欢迎关注云图智联:https://e.yuntuzhilian.com/


Java帮帮学习群生态

Java帮帮学习群生态

总有一款能帮到你

Java学习群

Java学习群

与大牛一起交流

大数据学习群

大数据学习群

在数据中成长

九点编程学习群

九点编程学习群

深夜九点学编程

python学习群

python学习群

人工智能,爬虫

测试学习群

测试学习群

感受测试的魅力

Java帮帮生态承诺

Java帮帮生态承诺

一直坚守,不负重望

初心
勤俭
诚信
正义
分享
友链交换:加帮主QQ2524138991 留言即可 24小时内答复  
业司
满吉教育资讯
会员登录
获取验证码
登录
登录
我的资料
留言
回到顶部