Spring AOP 简单使用

示例代码地址:gitee
参考文章:Spring官网

核心概念和术语

  • Aspect:跨越了多个类的关注点模块化。可以用 @Aspect 或者 schema 配置来实现
  • Join point:在程序运行期间的一个点,例如方法执行或者异常处理。Spring AOP 中,一个 Join point通常代表一个方法执行
  • Adviceaspect 在一个指定的 join point 采取的行动。包含多个类型:before, after, around。包含 Spring 在内的多个 AOP 框架将 advice 作为一个拦截器来对待,并在 join point 周围维护了一个拦截器链
  • Pointcut:一个匹配 join point 的谓词。advicepointcut 表达式相关联,且与被 pointcut 所匹配的所有 join point 处运行。
  • Introduction:代表类型声明额外的方法或字段。Spring AOP 让你给任意的 advice 对象引入新的接口(和相应的实现)
  • Target object:被一个或多个 aspectadvice 的对象。也被认为是 advised object。由于 Spring AOP 使用动态代理实现,该对象就是代理对象
  • AOP proxyAOP 框架为了实现 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 AOPSpring bean 仅仅支持方法执行连接点,因此你可以认为切入点就像去匹配 Spring bean 中的方法执行。

切入点声明有两个部分

  1. 一个名称和任意参数的签名:方法签名
  2. 一个切入点表达式(准确确定我们对哪个方法执行有兴趣):使用 @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 也支持一个额外的切入点指示符(PCDbean。该 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();
    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值