Spring AOP官网学习

5.1 AOP概念

让我们从定义一些核心的AOP概念和术语开始。这些术语并不是spring特有的。不幸的是,AOP术语不是特别直观。

1.Aspect(方面):跨多个类的关注点的模块化。事务管理是企业级Java应用程序中横切关注点的一个很好的例子。在Spring AOP中,方面是通过使用常规类(基于模式的方法)或使用@Aspect注释的常规类(@AspectJ风格)来实现的。
2.(Join point)连接点:程序执行期间的一个点,例如方法执行或异常处理期间的一个点。在Spring AOP中,连接点总是表示方法执行。
3.Pointcut(切点):切入点:匹配连接点的谓词。通知与切入点表达式相关联,并在切入点匹配的任何连接点上运行(例如,具有特定名称的方法的执行)。由切入点表达式匹配的连接点的概念是AOP的核心,Spring在默认情况下使用AspectJ切入点表达式语言。切入点是连接点的集合。
4.Advice(通知)建议:方面在特定连接点上采取的操作。不同类型的建议包括around, before和after advice。(稍后讨论通知类型。)许多AOP框架(包括Spring)将通知建模为拦截器,并围绕连接点维护拦截器链。
5.Introduction(介绍):代表类型声明其他方法或字段。Spring AOP允许向任何被建议的对象引入新的接口(以及相应的实现)。例如,您可以使用一个介绍来让一个bean实现一个IsModified接口,以简化缓存。(在AspectJ社区中,介绍称为类型间声明。)
6.Target object(目标对象):由一个或多个方面通知的对象。也称为被建议的对象。因为Spring AOP是通过使用运行时代理实现的,所以这个对象始终是一个代理对象。
7.AOP proxy(AOP代理):AOP框架为了实现方面契约(通知方法的执行等等)而创建的对象。在Spring框架中,AOP代理是JDK动态代理或CGLIB代理。
8.weaving(编织):将方面与其他应用程序类型或对象链接,以创建建议对象。这可以在编译时(例如使用AspectJ编译器)、加载时或运行时完成。与其他纯Java AOP框架一样,Spring AOP在运行时执行编织。
Spring AOP包括以下类型的通知
a.Before advice:在连接点之前运行但不能阻止执行流继续到连接点的通知(除非它抛出异常)。
b.After returning advice:返回后通知:通知将在连接点正常完成后运行(例如,如果方法返回时没有抛出异常)。
c.After throwing advice:如果方法通过抛出异常而退出,则将运行通知。
d.After(finally) advice:无论连接点以何种方式退出(正常或异常返回),都要运行的通知。
e.环绕连接点(如方法调用)的通知。这是最有力的通知。Around通知可以在方法调用之前和之后执行自定义行为。它还负责选择是继续到连接点,还是通过返回自己的返回值或抛出异常来简化建议的方法的执行。

5.4. @AspectJ support

@AspectJ引用了一种将方面声明为带有注释的常规Java类的样式。@AspectJ样式是由AspectJ项目作为AspectJ 5发行版的一部分引入的。Spring使用AspectJ提供的用于切入点解析和匹配的库来解释与AspectJ 5相同的注释。然而,AOP运行时仍然是纯粹的Spring AOP,并且不依赖于AspectJ编译器或weaver。

5.4.1. Enabling @AspectJ Support
要在Spring配置中使用@AspectJ方面,您需要启用Spring支持来配置基于@AspectJ方面的Spring AOP,以及基于这些方面是否建议bean的自动代理bean。通过自动代理,我们的意思是,如果Spring确定一个bean被一个或多个方面通知,它会自动为该bean生成一个代理来拦截方法调用,并确保通知在需要时运行。

可以通过XML或java风格的配置启用@AspectJ支持。在这两种情况下,您还需要确保AspectJ的aspectjwever .jar库位于应用程序的类路径中(版本1.8或更高)。这个库可以在AspectJ发行版的lib目录中或从Maven中央存储库中获得。

使用Java配置启用@AspectJ支持
要用Java @Configuration启用@AspectJ支持,添加@EnableAspectJAutoProxy注释,如下面的示例所示

5.4.2 声明一个方面

//
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {}

使用XML配置启用@AspectJ支持

<aop:aspectj-autoproxy/>

启用了@AspectJ支持后,在您的应用程序上下文中定义的带有@AspectJ方面(具有@Aspect注释)类的任何bean都将被Spring自动检测并用于配置Spring AOP。接下来的两个示例展示了一个不太有用的方面所需的最小定义。
两个示例中的第一个示例展示了应用程序上下文中的常规bean定义,该定义指向具有@Aspect注释的bean类

<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect"><!-- configure properties of the aspect here -->
</bean>

两个示例中的第二个展示了NotVeryUsefulAspect类定义,它用org.aspectj.lang.annotation进行了注释。注释方面

package org.xyz;
import org.aspectj.lang.annotation.Aspect;@Aspect
public class NotVeryUsefulAspect {}

方面(用@Aspect注释的类)可以有方法和字段,与其他任何类一样。它们还可以包含切入点、通知和引入(类型间)声明。
注意点1:通过组件扫描自动检测所有的方面
您可以在Spring XML配置中将方面类注册为常规bean,或者通过类路径扫描自动检测它们,就像任何其他Spring管理的bean一样。但是,请注意,@Aspect注释对于类路径中的自动检测是不够的。为此,您需要添加一个单独的@Component注释(或者,或者,一个定制的构造型注释,它符合Spring s组件扫描器的规则)。
注意点2:对其他方面提出建议?在Spring AOP中,方面本身不能成为来自其他方面的通知的目标。类上的@Aspect注释将其标记为一个方面,因此将其从自动代理中排除。

5.4.3 声明一个切入点

切入点确定感兴趣的连接点,从而使我们能够控制通知何时运行。Spring AOP只支持Spring bean的方法执行连接点,所以您可以将切入点看作是匹配Spring bean上方法的执行。
切入点声明有两个部分:由名称和任何参数组成的签名,以及确切确定我们感兴趣的方法执行的切入点表达式。
在@AspectJ注释风格的AOP中,切入点签名是由常规方法定义提供的,切入点表达式是通过使用@Pointcut注释表示的(作为切入点签名的方法必须有一个void返回类型)。一个示例可能有助于清晰地区分切入点签名和切入点表达式。下面的示例定义了一个名为anyOldTransfer的切入点,它匹配任何名为transfer的方法的执行:

@Pointcut("execution(* transfer(..))") // the pointcut expression
private void anyOldTransfer() {} // the pointcut signature

构成@Pointcut注释值的切入点表达式是一个常规的AspectJ 5切入点表达式。有关AspectJ切入点语言的完整讨论,请参阅AspectJ编程指南(对于扩展,请参阅AspectJ 5开发人员笔记本)或一本关于AspectJ的书(比如Colyer等人写的Eclipse AspectJ,或者Ramnivas Laddad写的AspectJ in Action)。
支持的切入点指示器
Spring AOP支持在切入点表达式中使用以下AspectJ切入点指示器(PCD):
**execution:**用于匹配方法执行连接点。这是在使用Spring AOP时要使用的主要切入点指示器。
within:限制对某些类型内的连接点的匹配(使用Spring AOP时在匹配类型内声明的方法的执行)。
this:将匹配限制为连接点(使用Spring AOP时方法的执行),其中bean引用(Spring AOP代理)是给定类型的实例。
target:将匹配限制为连接点(使用Spring AOP时方法的执行),其中目标对象(被代理的应用程序对象)是给定类型的实例。
args:将匹配限制为连接点(使用Spring AOP时方法的执行),其中参数是给定类型的实例。
@target:将匹配限制为连接点(使用Spring AOP时方法的执行),其中执行对象的类具有给定类型的注释。
@args:将匹配限制为连接点(使用Spring AOP时方法的执行),其中传递的实际参数的运行时类型具有给定类型的注释。
@within:限制对具有给定注释的类型中的连接点的匹配(在使用Spring AOP时使用给定注释在类型中声明的方法的执行)。
@annotation:将匹配限制为连接点的主题(在Spring AOP中运行的方法)具有给定注释的连接点。

因为Spring AOP将匹配限制为只匹配方法执行连接点,所以前面对切入点指示器的讨论给出了比AspectJ编程指南中更窄的定义。此外,AspectJ本身具有基于类型的语义,并且在一个执行连接点上,this和target都引用同一个对象:执行方法的对象。Spring AOP是一个基于代理的系统,区分了代理对象本身(绑定到此)和代理背后的目标对象(绑定到target)。

由于Spring的AOP框架基于代理的本质,根据定义,目标对象中的调用不会被拦截。
对于JDK代理,只能拦截代理上的公共接口方法调用。
使用CGLIB,可以拦截代理上的公共和受保护的方法调用(如果需要,甚至可以拦截包可见的方法)。

但是,通过代理的常见交互应该始终通过公共签名来设计。

注意,切入点定义通常与任何截获的方法相匹配。如果严格意义上说切入点是只公开的,即使是在CGLIB代理场景中,通过代理进行潜在的非公开交互,也需要相应地定义它。

如果您的拦截需要包括目标类中的方法调用甚至构造函数,请考虑使用Spring驱动的本地AspectJ编织,而不是基于Spring代理的AOP框架。这构成了具有不同特征的不同AOP使用模式,因此在做出决定之前一定要熟悉编织。

组合切入点表达式
您可以通过使用&& ||和!来组合切入点表达式。您还可以通过名称引用切入点表达式。下面的示例显示了三个切入点表达式:

@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {} @Pointcut("within(com.xyz.myapp.trading..*)")
private void inTrading() {} @Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {}

如果方法执行连接点表示任何公共方法的执行,则anyPublicOperation匹配。
如果一个方法执行在trading包中,则匹配inTrading。
如果一个方法执行表示trading包中的任何公共方法,则tradingOperation匹配。

从较小的已命名组件构建更复杂的切入点表达式是一种最佳实践,如前面所示。
当通过名称引用切入点时,应用普通的Java可见性规则(您可以看到相同类型的私有切入点、层次结构中的受保护切入点、任何地方的公共切入点,等等)。
可见性不影响切入点匹配。

共享公共切入点定义
在使用企业应用程序时,开发人员通常希望从几个方面引用应用程序的模块和特定的操作集。
我们建议定义一个CommonPointcuts方面,它可以捕获为此目的的公共切入点表达式。
这样的方面通常类似于下面的示例

package com.xyz.myapp;import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;@Aspect
public class CommonPointcuts {/*** A join point is in the web layer if the method is defined* in a type in the com.xyz.myapp.web package or any sub-package* under that.*/@Pointcut("within(com.xyz.myapp.web..*)")public void inWebLayer() {}/*** A join point is in the service layer if the method is defined* in a type in the com.xyz.myapp.service package or any sub-package* under that.*/@Pointcut("within(com.xyz.myapp.service..*)")public void inServiceLayer() {}/*** A join point is in the data access layer if the method is defined* in a type in the com.xyz.myapp.dao package or any sub-package* under that.*/@Pointcut("within(com.xyz.myapp.dao..*)")public void inDataAccessLayer() {}/*** A business service is the execution of any method defined on a service* interface. This definition assumes that interfaces are placed in the* "service" package, and that implementation types are in sub-packages.** If you group service interfaces by functional area (for example,* in packages com.xyz.myapp.abc.service and com.xyz.myapp.def.service) then* the pointcut expression "execution(* com.xyz.myapp..service.*.*(..))"* could be used instead.** Alternatively, you can write the expression using the 'bean'* PCD, like so "bean(*Service)". (This assumes that you have* named your Spring service beans in a consistent fashion.)*/@Pointcut("execution(* com.xyz.myapp..service.*.*(..))")public void businessService() {}/*** A data access operation is the execution of any method defined on a* dao interface. This definition assumes that interfaces are placed in the* "dao" package, and that implementation types are in sub-packages.*/@Pointcut("execution(* com.xyz.myapp.dao.*.*(..))")public void dataAccessOperation() {}}

您可以在需要切入点表达式的任何地方引用这样一个方面中定义的切入点。
例如,要使服务层具有事务性,您可以编写以下内容:

<aop:config><aop:advisorpointcut="com.xyz.myapp.CommonPointcuts.businessService()"advice-ref="tx-advice"/>
</aop:config><tx:advice id="tx-advice"><tx:attributes><tx:method name="*" propagation="REQUIRED"/></tx:attributes>
</tx:advice>

aop:config;和aop:advisor;元素在基于模式的AOP支持中讨论。在事务管理中讨论了事务元素。

编写好的切入点
在编译期间,AspectJ处理切入点,以优化匹配性能。检查代码并确定每个连接点是否(静态或动态)匹配给定的切入点是一个代价高昂的过程。(动态匹配意味着不能从静态分析完全确定匹配,并且在代码中放置一个测试,以确定在代码运行时是否存在实际匹配)。在第一次遇到切入点声明时,AspectJ将其重写为用于匹配过程的最佳形式。这是什么意思?基本上,用DNF(析取范式)重写切入点,对切入点的组件进行排序,以便首先检查那些计算成本较低组件。这意味着您不必担心理解各种切入点指示器的性能,并且可以在切入点声明中以任何顺序提供它们。

但是,AspectJ只能使用它被告知的内容。为了优化匹配性能,您应该考虑它们试图实现什么,并在定义中尽可能地缩小匹配的搜索空间。现有的指示符自然分为三类:kinded、scoping和contextual.

Kinded指示器选择特定类型的连接点:执行、获取、设置、调用和处理程序,execution, get, set, call, and handler.。

Scoping作用域指示符选择一组感兴趣的连接点(可能有很多种类):在内部和内部

Contextual上下文指示符根据上下文匹配(和可选绑定):this、target和@annotation.

一个编写良好的切入点应该至少包括前两种类型(kinded和scoping)。您可以包含上下文指示符来基于连接点上下文进行匹配,或者绑定上下文以便在通知中使用。仅提供kinded指示符或仅提供上下文指示符可以工作,但由于额外的处理和分析,可能会影响编织性能(时间和内存使用)。范围指示符匹配起来非常快,使用它们意味着AspectJ可以非常迅速地删除不应该进一步处理的连接点组。一个好的切入点应该尽可能包含一个切入点。

5.4.4 声明的建议

通知与切入点表达式相关联,并在切入点匹配的方法执行之前、之后或周围运行。切入点表达式可以是对命名切入点的简单引用,也可以是在适当位置声明的切入点表达式。
Before Advice
可以使用@Before注释在方面中声明before通知

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;@Aspect
public class BeforeExample {@Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")public void doAccessCheck() {// ...}}

如果我们使用一个就地切入点表达式,我们可以将前面的示例重写为下面的示例:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;@Aspect
public class BeforeExample {@Before("execution(* com.xyz.myapp.dao.*.*(..))")public void doAccessCheck() {// ...}}

After Returning Advice
在返回通知后,当匹配的方法执行正常返回时运行。您可以使用@ afterreturn注释来声明它.

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;@Aspect
public class AfterReturningExample {@AfterReturning("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")public void doAccessCheck() {// ...}}

您可以有多个通知声明(以及其他成员),都在同一个方面内。在这些示例中,我们只展示了一个通知声明,以集中说明每个通知的效果。
有时,您需要在通知正文中访问返回的实际值。您可以使用绑定返回值的@afterreturn形式来获得访问,如下面的示例所示。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;@Aspect
public class AfterReturningExample {@AfterReturning(pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",returning="retVal")public void doAccessCheck(Object retVal) {// ...}}

返回属性中使用的名称必须与通知方法中的参数名称对应。当一个方法执行返回时,返回值作为相应的实参值传递给通知方法。return子句还将匹配限制为只匹配那些返回指定类型值的方法执行(在本例中是Object,它匹配任何返回值)。
请注意,它是不可能返回一个完全不同的参考时,使用后返回建议。
After Throwing Advice
当匹配的方法通过抛出异常退出时通知执行运行。您可以使用@ afterthrow注释来声明它,如下面的示例所示:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;@Aspect
public class AfterThrowingExample {@AfterThrowing("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")public void doRecoveryActions() {// ...}}

通常,您希望仅在抛出给定类型的异常时才运行通知,而且您还经常需要访问通知主体中抛出的异常。
可以使用抛出属性来限制匹配(如果需要,可以使用Throwable作为异常类型),并将抛出的异常绑定到一个通知参数。下面的例子展示了如何做到这一点:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;@Aspect
public class AfterThrowingExample {@AfterThrowing(pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",throwing="ex")public void doRecoveryActions(DataAccessException ex) {// ...}}

抛出属性中使用的名称必须与通知方法中的参数名称对应。当方法执行通过抛出异常而退出时,异常将作为相应的参数值传递给通知方法。抛出子句还限制只匹配抛出指定类型异常(本例中为DataAccessException)的方法执行。
After (Finally) Advice
当匹配的方法执行退出时,通知运行。它是使用@After注释声明的。After advice必须准备好处理正常和异常返回条件。它通常用于释放资源和类似的目的。下面的例子展示了如何使用after finally advice.

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;@Aspect
public class AfterFinallyExample {@After("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")public void doReleaseLock() {// ...}}

Around Advice
最后一种建议是关于环绕通知的。Around advice围绕匹配的方法执行。它有机会在方法运行之前和之后执行工作,并确定方法何时、如何运行,甚至是否真正运行。如果您需要以线程安全的方式(例如启动和停止计时器)在方法执行之前和之后共享状态,则经常使用Around通知。总是使用最弱的形式的建议来满足你的要求(也就是说,不要使用around advice,如果before advice可以做的话)。

Around通知是使用@Around注释声明的。通知方法的第一个参数的类型必须是ProceedingJoinPoint。在通知的主体中,对ProceedingJoinPoint调用proceed()会导致底层方法运行。前进方法也可以传入Object []。 数组中的值用作方法执行时的参数。
当用Object []调用时,procedes的行为与AspectJ编译器所编译的aroundadvice的行为略有不同。对于使用传统AspectJ语言编写的环绕通知,传递给proc的参数数量必须与传递给环绕通知的参数数量(而不是基础连接点采用的参数数量)相匹配,并且传递给 给定的参数位置会取代该值绑定到的实体的连接点处的原始值(不要担心,如果这现在没有意义)。Spring采用的方法更简单,并且更好地匹配其基于代理的、仅执行的语义。如果您编译了为Spring编写的@AspectJ方面,并使用AspectJ编译器和weaver使用proceed with参数,那么您只需要知道这种区别。有一种方法可以在Spring AOP和AspectJ之间100%兼容,并且在下面有关建议参数的部分中对此进行了讨论。

下面的示例演示如何使用around建议:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;@Aspect
public class AroundExample {@Around("com.xyz.myapp.CommonPointcuts.businessService()")public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {// start stopwatchObject retVal = pjp.proceed();// stop stopwatchreturn retVal;}}

around通知返回的值是方法的调用者看到的返回值。例如,一个简单的缓存方面可以从缓存中返回一个值,如果缓存中有一个值,则调用proceed()。注意,在around通知的主体中,proceed可能被调用一次、多次,或者根本不被调用。所有这些都是合法的。

Advice Parameters
Spring提供了完全类型的通知,这意味着您可以在通知签名中声明所需的参数(正如我们在前面的返回和抛出示例中看到的那样),而不是一直使用Object[]数组。在本节的后面,我们将看到如何使参数和其他上下文值对advice主体可用。首先,我们看一下如何编写通用的通知,以找出该通知目前正在通知的方法。

访问当前连接点
任何通知方法都可以声明一个org.aspectj.lang类型的参数作为它的第一个参数。JoinPoint(注意around通知需要声明一个类型为ProceedingJoinPoint的第一个参数,它是JoinPoint的一个子类。JoinPoint接口提供了许多有用的方法。
getArgs():返回方法参数。
getThis():返回代理对象。
getTarget():返回目标对象。
getSignature():返回被通知的方法的描述。
toString():输出被通知方法的有用描述。

向通知传递参数
我们已经了解了如何绑定返回值或异常值(在返回和抛出通知之后使用)。要使通知主体可用参数值,可以使用args的绑定形式。如果在args表达式中使用参数名代替类型名,则在调用通知时将相应参数的值作为参数值传递。一个例子应该会使这一点更清楚。假设您希望通知以Account对象作为第一个参数的DAO操作的执行,并且需要访问通知主体中的Account。你可以这样写:

@Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
public void validateAccount(Account account) {// ...
}

切入点表达式的args(account,…)部分有两个目的。首先,它将匹配限制为只有那些方法执行时,该方法至少接受一个参数,并且传递给该参数的实参是Account的实例。其次,它通过Account参数使通知可以使用实际的Account对象。
写这个的另一种方法是声明一个切入点,当它匹配一个连接点时提供Account对象值,然后从通知中引用命名的切入点。

@Pointcut("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
private void accountDataAccessOperation(Account account) {}@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {// ...
}

有关更多细节,请参阅AspectJ编程指南。
代理对象(this)、目标对象(target)和注释(@within、@target、@annotation和@args)都可以以类似的方式绑定。下面两个示例展示如何匹配带有@Auditable注释的方法的执行,并提取审计代码
两个示例中的第一个展示了@Auditable注释的定义

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {AuditCode value();
}

第二个示例展示了匹配@Auditable方法执行的通知

@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
public void audit(Auditable auditable) {AuditCode code = auditable.value();// ...
}

通知参数和泛型
Spring AOP可以处理类声明和方法参数中使用的泛型。假设您有如下类型的泛型:

public interface Sample<T> {void sampleGenericMethod(T param);void sampleGenericCollectionMethod(Collection<T> param);
}

通过将advice参数键入要拦截方法的参数类型,可以将方法类型的拦截限制为某些参数类型

@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
public void beforeSampleMethod(MyType param) {// Advice implementation
}

这种方法不适用于泛型集合。因此,您不能像下面这样定义切入点

@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
public void beforeSampleMethod(Collection<MyType> param) {// Advice implementation
}

要做到这一点,我们必须检查集合的每个元素,这是不合理的,因为我们也无法决定如何处理空值。要达到类似的效果,您必须键入参数到collection>并手动检查元素的类型。
确定参数的名字
通知调用中的参数绑定依赖于将切入点表达式中使用的名称与通知和切入点方法签名中声明的参数名称相匹配。Java反射不能使用参数名,因此Spring AOP使用以下策略来确定参数名:
如果用户显式指定了参数名,则使用指定的参数名。通知和切入点注释都有一个可选的argNames属性,您可以使用它来指定带注释的方法的参数名称。这些参数名在运行时可用。下面的示例展示了如何使用argNames属性:

@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",argNames="bean,auditable")
public void audit(Object bean, Auditable auditable) {AuditCode code = auditable.value();// ... use code and bean
}

如果第一个参数是JoinPoint的,则为ProceedingJoinPoint或JoinPoint。如果是StaticPart类型,则可以从argNames属性的值中忽略参数的名称。例如,如果修改前面的通知以接收连接点对象,则argNames属性不需要包含它:

@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",argNames="bean,auditable")
public void audit(JoinPoint jp, Object bean, Auditable auditable) {AuditCode code = auditable.value();// ... use code, bean, and jp
}

对连接点的第一个参数、过程连接点和连接点的特殊处理。StaticPart类型对于不收集任何其他连接点上下文的通知实例特别方便。在这种情况下,可以省略argNames属性。例如,下面的通知不需要声明argNames属性:

@Before("com.xyz.lib.Pointcuts.anyPublicMethod()")
public void audit(JoinPoint jp) {// ... use jp
}

使用’argNames’属性有点笨拙,所以如果没有指定’argNames’属性,Spring AOP会查看类的调试信息,并尝试从本地变量表中确定参数名。只要用调试信息编译类(’-g:vars’最少),他的信息就会出现。
使用这个标志进行编译的结果是:(1)您的代码稍微容易理解(反向工程),(2)类文件大小稍微大了一点(通常不重要),(3)删除未使用的局部变量的优化没有被编译器应用。换句话说,您在使用这个标志进行构建时应该不会遇到任何困难。
如果AspectJ编译器(ajc)已经编译了一个@AspectJ方面,即使没有调试信息,也不需要添加argNames属性,因为编译器会保留所需的信息。
如果编译代码时没有必要的调试信息,那么Spring AOP将尝试推断绑定变量与参数的配对(例如,如果在切入点表达式中只有一个变量被绑定,并且advice方法只接受一个参数,那么这种配对是显而易见的)。
如果给定可用信息,变量的绑定是不明确的,则会抛出一个含糊不清的bindingexception。

如果上述所有策略都失败,则抛出一个IllegalArgumentException。

Proceeding with Arguments
我们前面提到过,我们将描述如何使用在Spring AOP和AspectJ中一致工作的参数编写proceed调用。
解决方案是确保通知签名按顺序绑定每个方法参数。下面的示例展示了如何做到这一点:

@Around("execution(List<Account> find*(..)) && " +"com.xyz.myapp.CommonPointcuts.inDataAccessLayer() && " +"args(accountHolderNamePattern)")
public Object preProcessQueryPattern(ProceedingJoinPoint pjp,String accountHolderNamePattern) throws Throwable {String newPattern = preProcess(accountHolderNamePattern);return pjp.proceed(new Object[] {newPattern});
}

在许多情况下,无论如何都要进行绑定(如前面的示例所示)。

Advice Ordering
如果多个通知都希望在同一个连接点上运行,会发生什么情况?Spring AOP遵循与AspectJ相同的优先规则来决定通知的执行顺序。优先级最高的建议在“进入时”首先运行(因此,给定两个before建议,优先级最高的建议首先运行)。从连接点“在退出的路上”,优先级最高的通知最后运行(因此,给定两个after通知,优先级最高的通知将排在第二)。
在不同方面定义的两个通知都需要在同一个连接点上运行时,除非另行指定,否则执行顺序是未定义的。您可以通过指定优先级来控制执行顺序。这是通过实现org.springframework.core以正常的Spring方式完成的。方面类中的有序接口,或者使用@Order注释对其进行注释。对于两个方面,从order . getorder()(或注释值)返回较低值的方面具有较高的优先级。

在Spring Framework 5.2.7中,在同一个@Aspect类中定义的需要在同一个连接点运行的通知方法的优先级是基于它们的通知类型,按照以下顺序从最高到最低的优先级分配的:@Around, @Before, @After, @ afterreturn, @AfterThrowing。但是请注意,由于Spring s AspectJAfterAdvice中的实现风格,在同一个方面中的任何@ afterreturn或@ afterthrow通知方法之后都会有效地调用@After advice方法。
当两块相同类型的建议(例如,两个@After建议方法)定义在相同的@Aspect类都需要运行在同一连接点,定是未定义的(因为没有办法获取源代码javac-compiled类的声明顺序通过反射)。考虑将此类通知方法分解为每个@Aspect类中的每个连接点的一个通知方法,或者将通知片段重构为单独的@Aspect类,您可以通过Ordered或@Order在方面级别对其进行排序。

5.4.5. Introductions

引入(在AspectJ中称为类型间声明)使方面能够声明已通知的对象实现给定接口,并代表这些对象提供该接口的实现。

您可以使用@DeclareParents注释进行介绍。此注释用于声明匹配类型有一个新的父类(因此得名)。
例如,给定一个名为UsageTracked的接口和该接口的一个名为DefaultUsageTracked的实现,下面的方面声明服务接口的所有实现者也都实现了UsageTracked接口(例如,通过JMX公开统计信息)。

@Aspect
public class UsageTracking {@DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class)public static UsageTracked mixin;@Before("com.xyz.myapp.CommonPointcuts.businessService() && this(usageTracked)")public void recordUsage(UsageTracked usageTracked) {usageTracked.incrementUseCount();}}

要实现的接口由带注释的字段的类型决定。@DeclareParents注释的value属性是一个AspectJ类型模式。任何匹配类型的bean都实现了UsageTracked接口。注意,在前面示例的before通知中,服务bean可以直接用作UsageTracked接口的实现。如果以编程方式访问bean,您将编写以下代码:

UsageTracked usageTracked = (UsageTracked) context.getBean("myService");

5.4.6. Aspect Instantiation Models

默认情况下,应用程序上下文中每个方面都有一个实例。AspectJ将其称为单例实例化模型。可以用不同的生命周期定义方面。Spring支持AspectJ的perthis和pertarget实例化模型;当前不支持percflow、percflowbelow和pertypewithin。

可以通过在@Aspect注释中指定perthis子句来声明perthis方面。考虑以下示例:

@Aspect("perthis(com.xyz.myapp.CommonPointcuts.businessService())")
public class MyAspect {private int someState;@Before("com.xyz.myapp.CommonPointcuts.businessService()")public void recordServiceUsage() {// ...}}

在前面的示例中,perthis子句的作用是为执行业务服务的每个唯一服务对象(在切入点表达式匹配的连接点上与之绑定的每个唯一对象)创建一个方面实例。方面实例是在服务对象上第一次调用方法时创建的。
当服务对象超出范围时,方面就超出了范围。在创建方面实例之前,其中的通知都不会运行。一旦创建了方面实例,在其中声明的通知就会在匹配的连接点上运行,但仅当服务对象与此方面相关联时才会运行。有关per子句的更多信息,请参阅AspectJ编程指南。
pertarget实例化模型的工作方式与perthis完全相同,但是它在匹配的连接点上为每个唯一的目标对象创建一个方面实例。

5.4.7. An AOP Example

现在您已经了解了所有组成部分是如何工作的,我们可以将它们组合在一起做一些有用的事情。业务服务的执行有时会由于并发问题(例如,死锁失败)而失败。如果重试该操作,很可能在下一次尝试时成功。
对于适合在这种情况下重试的业务服务(幂等操作不需要返回给用户以解决冲突),我们希望透明地重试操作,以避免客户机看到一个悲观lockingfailureexception异常。这是一种明显跨越服务层中的多个服务的需求,因此非常适合通过方面实现。

因为我们希望重试该操作,所以需要使用around通知,以便多次调用proceed。下面的清单显示了基本的方面实现:

@Aspect
public class ConcurrentOperationExecutor implements Ordered {private static final int DEFAULT_MAX_RETRIES = 2;private int maxRetries = DEFAULT_MAX_RETRIES;private int order = 1;public void setMaxRetries(int maxRetries) {this.maxRetries = maxRetries;}public int getOrder() {return this.order;}public void setOrder(int order) {this.order = order;}@Around("com.xyz.myapp.CommonPointcuts.businessService()")public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {int numAttempts = 0;PessimisticLockingFailureException lockFailureException;do {numAttempts++;try {return pjp.proceed();}catch(PessimisticLockingFailureException ex) {lockFailureException = ex;}} while(numAttempts <= this.maxRetries);throw lockFailureException;}}

请注意,方面实现了有序接口,以便我们可以将方面的优先级设置为高于事务通知的优先级(每次重试时都需要一个新的事务)。maxRetries和order属性都由Spring配置。主要操作发生在围绕通知的doConcurrentOperation中。请注意,目前我们将重试逻辑应用于每个businessService()。我们尝试继续下去,如果因为一个悲观的lockingfailureexception异常失败了,我们会再次尝试,除非我们已经耗尽了所有的重试尝试。
下面是相应的Spring配置

<aop:aspectj-autoproxy/><bean id="concurrentOperationExecutor" class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor"><property name="maxRetries" value="3"/><property name="order" value="100"/>
</bean>

为了精炼方面,使其只重试幂等操作,我们可以定义以下幂等注释

@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {// marker annotation
}

然后我们可以使用注释来注释服务操作的实现。对只重试幂等操作的方面的更改包括细化切入点表达式,以便只有@幂等操作匹配,如下所示:

@Around("com.xyz.myapp.CommonPointcuts.businessService() && " +"@annotation(com.xyz.myapp.service.Idempotent)")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {// ...
}

Spring AOP官网学习相关推荐

  1. Spring官网学习(一)概述

    文章目录 1.Spring官网简介 2.Spring总览 2.1.什么是Spring 2.2.Spring的发展历程 3.Spring的设计理念 3.1.Spring的优点 4.IOC和AOP浅析 4 ...

  2. Spring Boot的学习之路(02):和你一起阅读Spring Boot官网

    官网是我们学习的第一手资料,我们不能忽视它.却往往因为是英文版的,我们选择了逃避它,打开了又关闭. 我们平常开发学习中,很少去官网上看.也许学完以后,我们连官网长什么样子,都不是很清楚.所以,我们在开 ...

  3. Spring Batch_官网DEMO实现

    2019独角兽企业重金招聘Python工程师标准>>> Spring Batch_官网DEMO实现 http://spring.io/guides/gs/batch-processi ...

  4. PCL官网学习OpenNI Grabber 调用奥比中光Astra s 遇到问题openni2_grabber.cpp @ 325 : No devices connected.

    PCL官网学习OpenNI Grabber 调用奥比中光Astra s 遇到问题openni2_grabber.cpp @ 325 : No devices connected. 问题描述 termi ...

  5. postgresql 官网学习文档

    pg数据官网学习文档,PostgreSQL: Documentation 中文版:文档目录/Document Index: 世界上功能最强大的开源数据库...

  6. Angular官网学习笔记

    Angular官网学习笔记 一.Angular概述 **什么是Angular:**一个基于TypeScript构建的开发平台包括: 一个基于组件的框架,用于构建可伸缩的Web应用 一组完美集成的库,涵 ...

  7. knockout+html绑定,Knockout.Js官网学习(style绑定、attr绑定)

    Style绑定 style绑定是添加或删除一个或多个DOM元素上的style值.比如当数字变成负数时高亮显示,或者根据数字显示对应宽度的Bar.(注:如果你不是应用style值而是应用CSS clas ...

  8. Spring Security 官网文档学习

    文章目录 通过`maven`向普通的`WEB`项目中引入`spring security` 配置 `spring security` `configure(HttpSecurity)` 方法 自定义U ...

  9. 如何学习一个新的计算机概念(协议等),如snmp? 上官网学习【官网集合】

    snmp学习,不要仅仅只在百度上翻阅.要养成习惯,去snmp的官网,读英文官方文档.这里会有一手的教程.源代码.命令行. Qt官网:https://www.qt.io .https://doc.qt. ...

最新文章

  1. matlab 2014a 升级,MATLAB R2014a从入门到精通(升级版) pdf扫描版[42MB]
  2. linux虚拟主机泛解析,Apache虚拟主机的配置和泛域名解析实现代码
  3. mysql anyvalue函数_Mysql 的ANY_VALUE()函数和 ONLY_FULL_GROUP_BY 模式
  4. python重复输出五句话_如何用python3输出重复的数据?
  5. 内镜碎石术装置行业调研报告 - 市场现状分析与发展前景预测
  6. 优化网站设计(九):减少DNS查找的次数
  7. Careercup - Facebook面试题 - 4907555595747328
  8. 系统启动 之 Linux系统启动概述(1)
  9. 工作流实现自定义表单
  10. 微信高级群发接口 {errcode:40008,errmsg:invalid message type hint: [aRIDBA0726age9]}
  11. Ubuntu 修改鼠标中键功能
  12. NetSuite BOM材料产出率舍入
  13. Linux中JAVA服务器CPU占用过高(分析解决方法)
  14. wamp出现拒绝访问
  15. 找字符串中最长单词C语言,C语言 在已知字符串中找最长单词
  16. java发送图片邮件_使用javamail发送包含图片的html格式邮件详解
  17. 如何用Python爬取网易云歌曲?秘诀在这~
  18. 各大公司面试题(社招)
  19. HCS12XEP100 ATD模块定时中断采样
  20. 【阅读论文】第七章--多图重建的黄斑肿大检测--博-自动化眼底图像分析技术可筛查糖尿病患者的视网膜疾病

热门文章

  1. ssh @ ssh: Could not resolve hostname : Name or service not known
  2. js 屏幕滚动到固定位置
  3. 如何语言讲述雪铁龙c6,到底是一款怎样的车 浅谈雪铁龙C6体验感受
  4. 结合实战暴利营销13种技巧方式总有一个万里挑一适合你!!!
  5. 2022 最新TypeScript入门学习笔记
  6. 【300+精选大厂面试题持续分享】大数据运维尖刀面试题专栏(八)
  7. 王道考研408 数据结构 第三章 栈、队列与数组
  8. java将中文转换成拼音_java实现将汉语转换为拼音功能
  9. 使用 docker 运行 drupal
  10. mac每次执行mvn -v命令要先执行source ~/.bash_profile才生效