Spring 的 AOP 做了什么

AOP概念

Joinpoint

程序运行过程中,运行执行横切逻辑的时机点,或者说允许横切逻辑织入的运行时机,都可以成为Joinpoint。

常见的Joinpoint:

方法调用(Method Call):当某个方法被调用的时候所处的程序执行。Spring仅支持。

方法调用执行( Method Call execution)。称之为方法执行或许更简洁,该 Joinpoint类型代表的是某个方法内部执行开始时点,应该与方法调用类型的 Joinpoint进行区分。

方法调用( method call)是在调用对象上的执行点,而方法执行( method execution)则是在被调用到的方法逻辑执行的时点。对于同一对象,方法调用要先于方法执行。

构造方法调用( Constructor Call)。程序执行过程中对某个对象调用其构造方法进行初始化的时
点。
构造方法执行( Constructor Call Execution)。构造方法执行和构造方法调用之间的关系类似于方
法执行和方法调用之间的关系,指的是某个对象构造方法内部执行的开始时点
字段设置( Field Set)。对象的某个属性通过ee法被设置或者直接被设置的时点。该 Joinpoint
的本质是对象的属性被设置,而通过 setter法设置还是直接设置触发的时点是相同的。
字段获取( Field Get)。相对于字段设置型的 Joinpoint,字段获取型的 Joinpoint,对应的是某个对
象相应属性被访问的时点。可以通过 getter方法访问,当然也可以直接访问
异常处理执行( Exception Handler Execution)。该类型的 Joinpoint对应程序执行过程中,在某些
类型异常抛出后,对应的异常处理逻辑执行的时点。

类初始化( Class initialization)。类初始化型的 Joinpoint,指的是类中某些静态类型或者静态块的
初始化时点。

Pointcut

Pointcut概念代表的是 Joinpoint的表述方式。将横切逻辑织入当前系统的过程中,需要参照 Pointcut
规定的 Joinpoint信息,才可以知道应该往系统的哪些 Joinpoint上织入横切逻辑。

Pointcut表达式:

​ 直接指定 Joinpoint所在方法名称。

​ 正则表达式。

​ 使用特定的 Pointcut表述语言。

Advice

Advice是单一横切关注点逻辑的载体,它代表将会织入到 Joinpoint的横切逻辑。如果将 Aspect比
作OOP中的 Class,那么 Advice就相当于Clas中的 Method
按照 Advice在 Joinpoin位置执行时机的差异或者完成功能的不同,Advice可以分成多种具体形式。

  1. Before Advice
    Before advice是在 Joinpoint指定位置之前执行的 Advice类型。通常,它不会中断程序执行流程。但如果必要,可以通过在 Before Advice中抛出异常的方式来中断当前程序流程。

如果当前 Before advice将被织入到方法执行类型的 Joinpoint,那么这个 Before Advice就会先于方法执行而执行。
通常,可以使用 Before Advice做一些系统的初始化工作,比如设置系统初始值,获取必要系统资源等。当然,并非就限于这些情况。如果要用 Before advice来封装安全检查的逻辑,也不是不可以的。

但通常情况下,我们会使用另一种形式的 Advice

  1. After Advice

顾名思义, After Advice就是在相应连接点之后执行的 advice类型,但该类型的 Advice还可以细分为以下三种:

returning Advice。只有当前 Joinpoint处执行流程正常完成后, After returning Advice才会。比如方法执行正常返回而没有抛出异常。

After throwing Advice。又称 Throws Advice,只有在当前 Joinpoint执行过程中抛出异常的情况下,才会执行。比如某个方法执行类型的 Joinpoint抛出某异常而没有正常返回。

After Advice。或许叫After( Finally) Advice更为确切,该类型 Advice不管 Joinpoint处执行流程是正常终了还是抛出异常都会执行,就好像Java中的finally块一样。

​ 3.Around Advice

​ AOP Alliance属下的AOP实现大都采用拦截器( Interceptor)的叫法,但完成的功能是一样的。

​ Around Advice对附加其上的 Joinpoint进行“包裹”,可以在 Joinpoint.之前和之后都指定相应的逻辑,甚至于中断或者忽略 Joinpoint处原来程序流程的执行。

  1. Introduction
    在 Aspect中称 Inter- Type Declaration,在 JBOss AoP中称 Mix-in,都指的是这同一种类型的 Advice。

与之前的几种 Advice类型不同,Introduction不是根据横切逻辑在 Joinpoint处的执行时机来区分的,,而是根据它可以完成的功能而区别于其他 Advice类型。

Aspect

Aspect是对系统中的横切关注点逻辑进行模块化封装的AOP概念实体。通常情况下, Aspect可以包含多个 Pointcut以及相关 Advice定义。

在这里插入图片描述

Spring实现

Joinpoint

Spring 仅支持方法执行的Joinpoint

第一.20%的工作,得到80%的回报。仅支持方法执行,已经能够覆盖80%的使用场景,就没有必要费时费力的支持更多。

第二.对于属性级别的Joinpoint,本身会破坏其类的封装性。而且可以通过对Setter、Getter的Joinpoint绕过去。

第三.对于剩下20%的场景,则可以通过其他框架去实现,比如 AspectJ,并且Spring 也提供对于其的支持。

Pointcut

Spring 提供了一个顶级接口Pointcut,以供实现。具体的结构如下:

public interface Pointcut {
   /**
    * 获取一个类过滤器,判断目标类是否满足切面应用
    */
   ClassFilter getClassFilter();
   /**
    * 获取类匹配器,判断目标类的目标方法是否满足切面应用
    */
   MethodMatcher getMethodMatcher();
   /**
    * 一个默认Pointcut,所有类和方法的匹配结果都返回true
    * 内部的ClassFilter 和 MethodMatcher 也是各自的True默认实现。
    */
   Pointcut TRUE = TruePointcut.INSTANCE;
}
public interface ClassFilter {
   boolean matches(Class<?> clazz);

   ClassFilter TRUE = TrueClassFilter.INSTANCE;

}

方法匹配器分为静态匹配和运行时匹配。

如果是静态匹配,则每次调用的结果都是相同,永远不会涉及到方法参数。即不会调用三参数的方法。并且会在框架里缓存以提供性能。

如果是动态匹配,则三参数的方法将被调用。

public interface MethodMatcher {
   boolean matches(Method method, Class<?> targetClass);
	// 判断方法匹配是静态还是动态
   boolean isRuntime();

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

   MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;

}
常见的Pointcut
  • NameMatchMethodPointcut

该类继承自StaticMethodMatcherPointcut。顾名思义,StaticMethodMatcherPointcut是仅支持静态方法匹配的Matcher,本身身兼MethodMatcherPointcut的特性。其中ClassFilter 设置为 ClassFilter.TRUE,即仅通过方法的静态匹配实现Pointcut的匹配。

NameMatchMethodPointcut则显而易见,通过方法名称匹配。内部维护了一个List<String>的方法名称集合,开发人员则手动设置方法名称(方法名称支持 '*'通配符,比如‘*method’能够匹配到‘anymethod’这个名称。)

public void setMappedName(String mappedName) {
   setMappedNames(mappedName);
}

public void setMappedNames(String... mappedNames) {
   this.mappedNames = new LinkedList<String>();
   if (mappedNames != null) {
      this.mappedNames.addAll(Arrays.asList(mappedNames));
   }
}
  • JdkRegexpMethodPointcut

    支持正则表达式。继承自AbstractRegexpMethodPointcut,抽象类维护了两个数组:patterns 和 excludedPatterns,支持普遍性的正则匹配。

    JdkRegexpMethodPointcut则支持JDK版本的正则匹配,Pattern类。

  • AnnotationMatchingPointcut

虽然说是带着Annotation的,但是别想太多。该类只是为了支持1.5注解特性而增加的,源码注解对它评价只是Simple Pointcut。当然,确实简单,允许自定义支持的类注解和方法注解。

通过传入的类注解类型和方法注解类型,构建支持注解的ClassFilter和MethodMatcher。

public AnnotationMatchingPointcut(
      Class<? extends Annotation> classAnnotationType, Class<? extends Annotation> methodAnnotationType) {

   if (classAnnotationType != null) {
      this.classFilter = new AnnotationClassFilter(classAnnotationType);
   }
   else {
      this.classFilter = ClassFilter.TRUE;
   }

   if (methodAnnotationType != null) {
      this.methodMatcher = new AnnotationMethodMatcher(methodAnnotationType);
   }
   else {
      this.methodMatcher = MethodMatcher.TRUE;
   }
}

AnnotationClassFilter有个特殊的属性:checkInherited。如果为true,则会检查父类是否有指定注解。

public AnnotationMatchingPointcut(Class<? extends Annotation> classAnnotationType, boolean checkInherited) {
   this.classFilter = new AnnotationClassFilter(classAnnotationType, checkInherited);
   this.methodMatcher = MethodMatcher.TRUE;
}
  • ComposablePointcut

这个Pointcut平平无奇,且看构造函数:

UTOOLS1575469552760.png

指定Ponintcut、或者ClassFilter、或者MethodMatcher、或者全部指定。当然,如果不指定任何配置,则按默认的来,默认是什么,.TRUE啊。

但是,它给了一点点玩法,允许逻辑运算。看!

UTOOLS1575469698104.png

仅提供这些逻辑运算的方法,方法名我想不用赘述了,参数的话,嗯,也很明显。

这样子的做法主要是提高Ponint或者其中ClassFilter和MethodMatcher的复用性。

混进了奇怪的方法请忽略,真的是懒得画图,直接截图了。

  • ControlFlowPointcut

这个类很有意思,严格来讲它是一个动态匹配的Pointcut,但是从匹配来讲,又仅仅只是一个静态的类型匹配。

一般来讲,一旦配置了某个Pointcut,则匹配的方法,被任何地方调用,都会执行切面。但是这个类,嗯,它指定了只有当某个类调用Pointcut的方法时,才会执行切面。

public ControlFlowPointcut(Class<?> clazz) {
   this(clazz, null);
}

public ControlFlowPointcut(Class<?> clazz, String methodName) {
   Assert.notNull(clazz, "Class must not be null");
   this.clazz = clazz;
   this.methodName = methodName;
}

如果构造函数指定了TargetCaller,则该类调用任何方法都将应用切面。

如果同时指定了方法名,则只有当该类调用指定方法名的方法时,才执行切面。

注意:该类会遍历当前调用栈,所以性能极差。

public boolean matches(Method method, Class<?> targetClass, Object... args) {
   this.evaluations++;

   for (StackTraceElement element : new Throwable().getStackTrace()) {
      if (element.getClassName().equals(this.clazz.getName()) &&
            (this.methodName == null || element.getMethodName().equals(this.methodName))) {
         return true;
      }
   }
   return false;
}

Advice

比较简单,平常使用中都有所接触。
在这里插入图片描述

Aspect

Spring 中的 Aspect 一般被称为Advisor。

Advisor代表 Spring中的 Aspect,但是,与正常的 Aspect不同, Advisor通常只持有一个 Pointcut和一个 Advice。而理论上, Aspect定义中可以有多个 Pointcut和多个 Advice,所以,我们可以认为 Advisor是一种特殊的 Aspect。

Spring 的 Advisor 体系如下:
在这里插入图片描述

PointcutAdvisor

PointcutAdvisor是一个标准的 Advisor,为什么呢?因为正儿八经的维护了一个 Pointcut 和一个 Advice。

DefaultPointcutAdvisor

DefaultPointcutAdvisor是一个最为通用且常用的 PointcutAdvisor,其本身的构造与使用也是最为简单。

直接通过构造函数指定使用的 Pointcut 和 Advice。

public DefaultPointcutAdvisor() {
}

public DefaultPointcutAdvisor(Advice advice) {
   this(Pointcut.TRUE, advice);
}

public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice) {
   this.pointcut = pointcut;
   setAdvice(advice);
}

如果是使用默认构造函数,则使用setter设置即可。

public void setPointcut(Pointcut pointcut) {
   this.pointcut = (pointcut != null ? pointcut : Pointcut.TRUE);
}
public void setAdvice(Advice advice) {
    this.advice = advice;
}
NameMatchMethodPointcutAdvisor

NameMatchMethodPointcutAdvisor是细化之后的DefaultPointcutAdvisor,限定了 Pointcut 只能是NameMatchMethodPointcut,而 Advice 则通用支持。

而暴露的方法也是十分眼熟,仔细一看,就是给NameMatchMethodPointcut加了一层包装,只为了用起来省事点罢了。

public void setClassFilter(ClassFilter classFilter) {
   this.pointcut.setClassFilter(classFilter);
}

public void setMappedName(String mappedName) {
   this.pointcut.setMappedName(mappedName);
}

public void setMappedNames(String... mappedNames) {
   this.pointcut.setMappedNames(mappedNames);
}

public NameMatchMethodPointcut addMethodName(String name) {
   return this.pointcut.addMethodName(name);
}
RegexpMethodPointcutAdvisor

RegexpMethodPointcutAdvisor也只是大同小异罢了,内部维护的是AbstractRegexpMethodPointcut,放以前也许这个Pointcut还能有多种实现。现在嘛,也其实就是JdkRegexpMethodPointcut.

DefaultBeanFactoryPointcutAdvisor

DefaultBeanFactoryPointcutAdvisor有点不一样,只是一点点。首先顾名思义,这玩意怎么也得和BeanFactory 沾亲带故了。实际呢, DefaultBeanFactoryPointcutAdvisor必须和 BeanFactory 绑定销售了,为什么?因为人家的 Advice 是从BeanFactory拿的,只需要事先确定好 Advice 的 BeanName即可。

看看:

public void setAdviceBeanName(String adviceBeanName) {
   this.adviceBeanName = adviceBeanName;
}
public void setAdvice(Advice advice) {
   synchronized (this.adviceMonitor) {
      this.advice = advice;
   }
}

@Override
public Advice getAdvice() {
   Advice advice = this.advice;
   if (advice != null || this.adviceBeanName == null) {
      return advice;
   }

   Assert.state(this.beanFactory != null, "BeanFactory must be set to resolve 'adviceBeanName'");
   if (this.beanFactory.isSingleton(this.adviceBeanName)) {
      // Rely on singleton semantics provided by the factory.
      advice = this.beanFactory.getBean(this.adviceBeanName, Advice.class);
      this.advice = advice;
      return advice;
   }
   else {
      // No singleton guarantees from the factory -> let's lock locally but
      // reuse the factory's singleton lock, just in case a lazy dependency
      // of our advice bean happens to trigger the singleton lock implicitly...
      synchronized (this.adviceMonitor) {
         if (this.advice == null) {
            this.advice = this.beanFactory.getBean(this.adviceBeanName, Advice.class);
         }
         return this.advice;
      }
   }
}

其他行为,则与 DefaultPointcutAdvisor一般无二了。

IntroductionAdvisor

IntroductionAdvisor 与 PointcutAdvisor 最本质上的区别就是, IntroductionAdvisor只能应
用于类级别的拦截,只能使用 Introduction型的 Advice,而不能像 Pointcutadvisor那样,可以使用任
何类型的 Pointcut,以及差不多任何类型的 Advice。也就是说, IntroductionAdvisor纯粹就是为
Introduction而生的。

织入

术语应该称之为织入,当然,简单意义上理解的话,就是将我们定义的一系列切面的配置和增强实际应用到我们想要增强的地方,从而在用户无感的角度,达到使用增强对象的效果。

《Spring 揭秘》 9.5 9.6

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值