• 探析Spring AOP(三):Spring AOP的底层实现原理


    https://blog.csdn.net/jeffleo/article/details/61208404

    探析Spring AOP(三):Spring AOP的底层实现原理

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jeffleo/article/details/61208404
    一、前言
      前面第一篇我们讲到了AOP的概念和使用,第二篇也讲到了 AOP的实现机制,在第一篇,讲到了joinpoint,pointcut,aspect,weave等AOP的核心概念,接下来我们详解分析他们的实现原理!
      在动态代理 和 CGLIB 的支持下, Spring AOP 框架的实现经过了两代。从 Spring AOP 框架第一次发布,到 Spring 2.0 发布之前的 AOP 实现,是 Spring 第一代 AOP 实现。Spring 2.0 发布后的 AOP 实现是第二代。但是,Spring AOP 的底层实现机制一直没有变,唯一改变的,是各种 AOP 概念实现的表现形式以及 Spring AOP 的使用方式。
      此篇介绍Joinpoint和PointCut:
      

    一、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;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
      为什么要分开匹配?是为了可以重用不同级别的匹配定义,并且可以在不同的级别或者相同的级别上进行组合操作。

    ClassFilter
    package org.springframework.aop;

    public interface ClassFilter {

    boolean matches(Class<?> clazz);

    ClassFilter TRUE = TrueClassFilter.INSTANCE;
    }
    1
    2
    3
    4
    5
    6
    7
    8
      当织入的目标对象和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);
    }

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
      如果类型对所捕捉的 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;

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
      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");
    }

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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;
    }

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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"});
    1
    2
    3
    4
    5
    6
    7
      但是, NameMatchMethodPointcut 无法对重载的方法名进行匹配, 因为它仅对方法名匹配,不考虑参数信息。

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

    JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();

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

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

    public class Foo {
    public void doSomething() {

    }
    }
    1
    2
    3
    4
    5
      如果使用正则表达式 .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 {

    }
    1
    2
    3
    4
    5
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface MethodLevelAnnotation {

    }
    1
    2
    3
    4
    5
      用法:

    //仅指定类级别的注解, 标注了 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);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
      实例:

    @ClassLevelAnnotation
    public class TargetObject {

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

    public void method2() {
    System.out.println("target : method2");
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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();
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    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);
    1
    2
    3
    4
    5
    6
    7
    8
    9
      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);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    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();
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
      结果:

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

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

    三、总结
      1. Pointcut最底层的是一个Pointcut接口,里面有对象匹配方法getClassFilter和方法匹配方法getMethodMatcher()
      2. MethodMatcher接口中有三个方法,两参数和三参数的matches方法,和一个isRuntime方法,根据isRuntime返回值决定是否匹配方法参数从而决定调用哪个matches方法
      3. Point中分为6种Point实现,分别是:NameMatchMethodPointcut、JdkRegexpMethodPointcut、Perl5RegexpMethodPoint、AnnotationMatchingPointcut、ComposablePointcut、ControlFlowPointcut,各自的特性在各自的名字中体现

    参考:《Spring揭秘》
    ---------------------
    作者:JeffCoding
    来源:CSDN
    原文:https://blog.csdn.net/jeffleo/article/details/61208404
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    用vue ui创建的项目怎么关闭eslint校验
    SQL修改表约束实现
    获取微信公众号的粉丝openid以及用openid获取unionID
    怎么停掉或关闭运行的npm run dev
    .NET解密得到UnionID
    微信获取信息发生错误(两个access_token的区别),错误代码:40001,说明:invalid credential, access_token is invalid or not latest hints
    微信获取不了用户头像等信息
    微信sdk上传图片大小1k,损坏的问题以及微信上传图片需要的配置
    微信订阅号中获取openid以及个人信息
    Bootstrap中宽度大于指定宽度时有空白的解决方法
  • 原文地址:https://www.cnblogs.com/handsome1013/p/10120811.html
Copyright © 2020-2023  润新知