核心概念和术语
Aspect
:跨越了多个类的关注点模块化。可以用@Aspect
或者schema
配置来实现Join point
:在程序运行期间的一个点,例如方法执行或者异常处理。Spring AOP
中,一个Join point
通常代表一个方法执行Advice
:aspect
在一个指定的join point
采取的行动。包含多个类型:before, after, around
。包含Spring
在内的多个AOP
框架将advice
作为一个拦截器来对待,并在join point
周围维护了一个拦截器链Pointcut
:一个匹配join point
的谓词。advice
于pointcut
表达式相关联,且与被pointcut
所匹配的所有join point
处运行。Introduction
:代表类型声明额外的方法或字段。Spring AOP
让你给任意的advice
对象引入新的接口(和相应的实现)Target object
:被一个或多个aspect
所advice
的对象。也被认为是advised object
。由于Spring AOP
使用动态代理实现,该对象就是代理对象AOP proxy
:AOP
框架为了实现aspect
的相关协定(advice
等)创建的一个对象。一个AOP proxy
通常是一个JDK 动态代理
或者是CGLIB 代理
Weaving
:连接aspect
和不同应用程序类型或者对象去创建一个Target object
。
使用步骤
1、启用 @AspectJ 支持
@AspectJ
指的是一种将切面,描述为带有注解的常规 Java
类的风格
// 注解方式
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
// xml 方式
<aop:aspectj-autoproxy/>
2、声明一个切面(aspect
)
启用 @AspectJ
支持后,你应用上下文里的任何一个 bean
都是一个 @AspectJ
切面(拥有 @Aspect
注解),默认被 Spring
监测并用于配置 Spring AOP
。
切面(带有 @Aspect
的类)可以跟其他类一样,拥有方法和属性。也可以包含,切入点,建议,引入(类型间)声明。
// 注解方式
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class NotVeryUsefulAspect {
}
// xml 方式
<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
<!-- configure properties of the aspect here -->
</bean>
3、声明一个切入点(pointcut
)
切入点,确定连接点 join points
,能够让我们可以控制 advice
什么时候运行。Spring AOP
对 Spring bean
仅仅支持方法执行连接点,因此你可以认为切入点就像去匹配 Spring bean
中的方法执行。
切入点声明有两个部分:
- 一个名称和任意参数的签名:方法签名
- 一个切入点表达式(准确确定我们对哪个方法执行有兴趣):使用
@Pointcut
来标明
被当做切入点使用的方法返回值,必须为 void
@Pointcut("execution(* transfer(..))") // the pointcut 表达式
private void anyOldTransfer() {} // the pointcut 签名
切入点指示符相关支持
Spring AOP
支持 AspectJ
切入点指示符(PCD)在切入点表达式中的相关使用
execution
:用于匹配连接点的方法执行。这个是主要的切入点表达式within
:用于匹配指定类型内的方法执行this
:限制匹配连接点,bean
的引用(Spring AOP 代理
)是给定类型实例args
:限制匹配连接点,参数是给定的类型的实例@target
:限制匹配连接点,执行对象的class
拥有指定类型的注解@args
:限制匹配连接点,传递的实际参数运行时类型拥有指定类型的注解@within
:限制匹配拥有指定注解类型的连接点@annotation
:限制匹配连接点,连接点的目标拥有指定注解
Spring AOP
也支持一个额外的切入点指示符(PCD) bean
。该 PCD,让你限制匹配一个指定名称的 Sring bean
// idOrNameOfBean 可使用 *,!,||,&&
bean(idOrNameOfBean)
组合切入点表达式
// 可使用 &&, ||, !
@Pointcut("execution(public * *(..))") // 匹配所有 public 执行的方法
private void anyPublicOperation() {}
@Pointcut("within(com.xyz.myapp.trading..*)") // 匹配当前指定目录下,执行的方法
private void inTrading() {}
@Pointcut("anyPublicOperation() && inTrading()") // 匹配两个方法中的公共方法
private void tradingOperation() {}
可见性不影响切入点匹配
常见切入点定义展示
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() {}
}
案例
Spring AOP
通常使用 execution
切入点指示符
// execution 遵循的编写规则
execution(modifiers-pattern? // 修饰符 ? 匹配前面的子表达式零次或一次,表示可有可无
ret-type-pattern // return type pattern
declaring-type-pattern? // 声明类型
name-pattern(param-pattern)
throws-pattern?)
*
是 ret-type-pattern
中使用最频繁的,也可以将 *
作为 name-pattern
的全部或部分
()
匹配无参方法,(..)
匹配任意数量(0 - 无穷)参数,(*)
匹配一个任意类型的参数,(*,String)
// 任何 public 方法的执行
execution(public * *(..))
// 以 set 开头的方法名
execution(* set*(..))
// AccountServcie 下的所有方法
execution(* com.xyz.service.AccountService.*(..))
// service 包下任意的连接点
within(com.xyz.service.*)
// service 包或者其子包中任意连接点
within(com.xyz.service..*)
// 实现了 AccountService 接口的代理
this(com.xyz.service.AccountService)
// 实现了 AccountService 目标对象
target(com.xyz.service.AccountService)
// 采用单个参数,并且在运行时传递的参数是可序列化的任意连接点
args(java.io.Serializable)
// 目标对象有一个 @Transactional 注解的任意连接点
@target(org.springframework.transaction.annotation.Transactional)
// 目标对象声明的类型有一个 @Transactional 注解的任意连接点
@within(org.springframework.transaction.annotation.Transactional)
// 执行方法拥有一个 @Transactional 注解
@annotation(org.springframework.transaction.annotation.Transactional)
// 采用单个参数,并且在运行时传递的参数是 @Classified
@args(com.xyz.security.Classified)
// Spring bean named tradeService
bean(tradeService)
bean(*Service)
4、声明 Advice
Advice
跟切入点表达式相关联,在匹配的切入点方法执行之前、之后或周围运行。
该切入点表达式可以是对切入点的简单引用,也可以是就地声明的切入点表达式。
Before Advice
@Aspect
public class BeforeExample {
@Pointcut("execution(* com.xyz.myapp..service.*.*(..))")
public void businessService() {}
@Before("businessService()")
public void doBusiness() {
// ...
}
@Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
@Before("execution(* com.xyz.myapp.dao.*.*(..))")
public void doAccessCheckII() {
// ...
}
}
After Returning Advice
@Aspect
public class AfterReturningExample {
@AfterReturning("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
@AfterReturning(
pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
returning="retVal")
public void doAccessCheckII(Object retVal) {
// ...
}
}
After Throwing Advice
@Aspect
public class AfterThrowingExample {
@AfterThrowing("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
public void doRecoveryActions() {
// ...
}
@AfterThrowing(
pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
throwing="ex")
public void doRecoveryActionsII(DataAccessException ex) {
// ...
}
}
After (Finally) Advice
@Aspect
public class AfterFinallyExample {
// 在方法执行结束后运行,常用于释放资源,或者类似目的
@After("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
public void doReleaseLock() {
// ...
}
}
Around Advice
Around advice
在围绕匹配方法的执行而执行。before after
的集合体。经常被使用于:需要分享一个方法执行前和执行后的状态。例如:starting and stopping a timer
@Aspect
public class AroundExample {
// 需要将 Object 作为返回参数
// ProceedingJoinPoint 作为第一个参数
// 调用 ProceedingJoinPoint.proceed() 确保底层方法运行
@Around("com.xyz.myapp.CommonPointcuts.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
// 不带参数调用,将会导致,调用者的原始参数被底层方法调用
Object retVal = pjp.proceed(); // 可以调用,也可以不调用
// stop stopwatch
return retVal;
}
}
Introductions
introductions
(在 AsprctJ
被称为类型间声明),使切面能够声明实现了给定接口的建议对象,并代表对象提供接口的实现。
可以通过使用 @DeclareParents
注解使用 introduction
。该注解被用于声明匹配的类型有一个新的父类。
例如,给与一个接口 UsageTracked
,一个实现类 DefaultUsageTracked
。下面的切面声明,所有实现了服务接口的实现类同时也实现了 UsageTracked
接口
@Aspect
public class UsageTracking {
// 任意匹配类型的 bean 都实现 UsageTracked 接口
@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();
}
}