Spring AOP 使用
示例代码地址:https://gitee.com/ZKW-Kevin/spring-aop.git
参考文章:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop
核心概念和术语
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();
}
}

浙公网安备 33010602011771号