Spring AOP中的Advice

图9-4中是Spring中各种Advice类型实现与AOPAlliance中标准接口之间的关系(Introduction型的Advice将单独讲解)。

image-20220408234724232

Advice实现了将被织入到Pointcut规定的Joinpoint处的 横切逻辑

在Spring中,Advice按照其 自身实例(instance)能否在目标对象类的所有实例中共享 这一标准,可以划分为两大类,即per-class类型的Advice和per-instance类型的Advice。

per-class类型的Advice

per-class类型的Advice是指, 该类型的Advice的实例可以在目标对象类的所有实例之间共享 。这种类型的Advice通常只是提供 方法拦截 的功能,不会为目标对象类保存任何状态或者添加新的特性。除了图9-4中没有列出的Introduction类型的Advice不属于perclass类型的Advice之外,图9-4中的所有Advice均属此列。

更多不做介绍 有兴趣可以看原文

per-intance类型的Advice

per-class类型的Advice不同,per-instance类型的Advice不会在目标类所有对象实例之间共享,而是会为不同的实例对象保存它们各自的状态以及相关逻辑。

就拿上班族为例,如果员工是一类人的话,那么公司的每一名员工就是员工类的不同对象实例。每个员工上班之前,公司设置了一个per-class类型的Advice进行“上班活动”的一个拦截,即打卡机,所有的员工都公用一个打卡机。当每个员工进入各自的位置之后,他们就会使用各自的电脑进行工作,而他们各自的电脑就好像per-instance类型的Advice-样,每个电脑保存了每个员工自己的资料。

在SpringAOP中,Introduction就是唯一的一种per-instance型Advice。

Introduction

Introduction 可以在不改动目标类定义的情况下,为目标类添加新的属性以及行为 。这就好比我们开发人员,如果公司人员紧张,没有配备测试人员,那么,通常就会给我们扣上一顶“测试人员”的帽子,让我们同时进行系统的测试工作,实际上,你还是你,只不过多了点儿事情而己。

在Spring中,为目标对象添加新的属性和行为必须声明相应的接口以及相应的实现。这样,再通过特定的拦截器将新的接口定义以及实现类中的逻辑附加到目标对象之上。之后,目标对象(确切地说是目标对象的代理对象)就拥有了新的状态和行为。这个特定的拦截器就是org.springframework.aop.IntroductionInterceptor

更多不做介绍 有兴趣可以看原文

Spring AOP中的Aspect

当所有的Pointcut和Advice准备好之后,就到了该把它们分门别类地装进箱子的时候了。你知道我说的箱子是什么,对吧?当然是Aspect。

在解释Aspect的概念的时候曾经提到过,Spring中最初没有完全明确的Aspect的概念,但是,这并不意味着就没有。只不过,Spring中的这个Aspect在实现和特性上有所特殊而已。

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

为了能够更清楚Advisor的实现结构体系,我们可以将Advisor简单划分为两个分支,一个分支以org.springframework.aop.PointcutAdvisor为首,另一个分支则以org.springframework.aop.IntroductionAdvisor为头儿,如图9-6所示。

image-20220409095701969

PointcutAdvisor家族

不做介绍 有兴趣可以看原文

IntroductionAdvisor分支

不做介绍 有兴趣可以看原文

Ordered的作用

系统中只存在单一的横切关注点的情况很少,大多数时候,都会有多个横切关注点需要处理,那么,系统实现中就会有多个Advisor存在。当其中的某些Advisor的Pointcut匹配了同一个Joinpoint的时候,就会在这同一个Joinpoint处执行多个Advice的横切逻辑。

如果这些Advisor所关联的Advice之间没有很强的优先级依赖关系,那么谁先执行,谁后执行都不会造成任何影响。

而一旦这几个需要在同一Joinpoint处执行的Advice逻辑存在优先顺序依赖的话,就需要我们来干预了,否则,系统的行为就会偏离我们的预想。记得有一天,同事突然问我,说:“头儿,这个任务初始化的时候抛出异常但没被我们的ThrowsAdvice截获,帮看一下呗?”

我心里纳闷,不能吧?查了一下Pointcut的正则表达式定义,没错啊,应该能捕获到啊。最后查到抛出异常的是应用到同一个方法的Advice所抛出的,我才猛然醒悟…

现在假设有两个Advisor,一个进行权限检查,当检查到当前调用没有权限的时候,抛出相应异常,称为PermissionAuthAdvisor;另一个Advisor使用一个ThrowsAdvice对系统中的所有需要检测的异常进行拦截,称其为ExceptionBarrierAdvisor

如果以如下形式声明这两个Advisor,就不会有问题:

 
    <bean id="exceptionBarrierAdvisor" class="...ExceptionBarrierAdvisor">
    	...
    </bean>
 
    <bean id="permissionAuthAdvisor" class="...PermissionAuthAdvisor">
    	...
    </bean>

即使PermissionAuthAdvisor的Advice抛出异常,我们的ExceptionBarrierAdvisor也可以捕获该异常并进行系统内的统一处理。而如果我们像如下这样,颠倒它们两个的声明顺序,那就有问题了:

    <bean id="permissionAuthAdvisor" class="...PermissionAuthAdvisor">
    	...
    </bean>
 
    <bean id="exceptionBarrierAdvisor" class="...ExceptionBarrierAdvisor">
    	...
    </bean>

在PermissionAuthAdvisor中的Advice抛出异常之后,ExceptionBarrierAdvisor并没有起作用,问题出在哪儿呢?

Spring在处理同一Joinpoint处的多个Advisor的时候,实际上会按照指定的顺序和优先级来执行它们,顺序号决定优先级,顺序号越小,优先级越高,优先级排在前面的,将被优先执行。我们可以从0或者1开始指定,因为小于0的顺序号原则上由SpringAOP框架内部使用。默认情况下,如果我们不明确指定各个Advisor的执行顺序,那么Spring会按照它们的声明顺序来应用它们,最先声明的顺序号最小但优先级最大,其次次之。

有了这些前提,我们就可以知道为什么仅颠倒两个Advisor的顺序就会造成某个Advisor失效。让我们来看图9-9。

image-20220409101411970

在图9-9中,左边是正常的情况,当调用流程在PermissionAuthAdvisor中出现问题时,甚至是在目标对象上出现问题时,ExceptionBarrierAdvisor会在调用流程返回的时候捕获到相应异常;而右边就是调换顺序后的结果,可以看到现在PermissionAuthAdvisor上出现问题的话,因为调用流程已经经过了ExceptionBarrierAdvisor,所以,ExceptionBarrierAdvisor根本无法捕获PermissionAuthAdvisor上的异常(虽然目标对象上的问题可以捕获到)。

虽然我们可以通过调整配置中各个Advisor声明的顺序来避免以上问题,但是,这并非最彻底的解决方法。最彻底的方法就是,为每个Advisor明确指定顺序号。在Spring框架中,我们可以通过让相应的Advisor以及其他顺序紧要的bean实现org.springframework.core.Ordered接口来明确指定相应顺序号。

不过,从图9-7中也应该看到了,各个Advisor实现类,其实已经实现了Orderd接口。我们无需自己去实现这个接口了,唯一要做的是直接在配置的时候指定顺序号。下方代码中的配置为我们的两个Advisor指定了明确的顺序号,从而避免了最初问题的出现。

 
    <bean id="permissionAuthAdvisor" class="...PermissionAuthAdvisor">
      <property name="order" value="1"/>
    	...
    </bean>
 
    <bean id="exceptionBarrierAdvisor" class="...ExceptionBarrierAdvisor">
      <property name="order" value="0"/>
    	...
    </bean>