[技术分享]-springboot & AOP

本文介绍了Spring框架中AOP(面向切面编程)的核心概念和技术细节,包括如何使用Spring创建AOP代理、定义切点、通知类型等。通过具体示例展示了如何在项目中实施AOP,帮助开发者更好地理解和应用这一强大的编程模式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Spring对AOP的支持

Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring创建代理的规则为:

1、默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了

2、当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB

=> AOP编程其实是很简单的事情,纵观AOP编程,程序员只需要参与三个部分:

1、定义普通业务组件

2、定义切入点,一个切入点可能横切多个业务组件

3、定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作

所以进行AOP编程的关键就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,即:代理对象的方法=增强处理+被代理对象的方法。

使用AOP  首先在工程的pom文件中加入对AOP的dependency

   <!-- String AOP -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

一个切面类,首先需由 import org.aspectj.lang.annotation.Aspect 注解标记。在工程中定义的任意带有一个@Aspect切面(拥有@Aspect注解)的bean都将被Spring自动识别并用于配置Spring AOP,由SpringIOC容器自动装配而成。

=> 接着 声明一个切入点:

@Pointcut("execution(public * com.xxx.yyyy.search.controller..*.*(..)) && @annotation(com.xxx.yyyy.search.model.anno.GetObject)")
    public void webRequest() {
    }

=>切入点可以使用如下切入点指示符:

execution - 匹配方法执行的连接点,这也许是你会用到的Spring的最主要的切入点指示符。
within - 限定匹配特定类型的连接点(在使用Spring AOP的时候,在匹配的类型中定义的方法的执行)。
this - 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中bean reference(Spring AOP 代理)是指定类型的实例。
target - 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中目标对象(被代理的应用对象)是指定类型的实例。
args - 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中参数是指定类型的实例。
@target - 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中正执行对象的类持有指定类型的注解。
@args - 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中实际传入参数的运行时类型持有指定类型的注解。
@within - 限定匹配特定的连接点,其中连接点所在类型已指定注解(在使用Spring AOP的时候,所执行的方法所在类型已指定注解)。
@annotation - 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中连接点的主题持有指定的注解

=>切入点表达式:一般将会用到*通配符作为所有或者部分命名模式。 参数模式稍微有点复杂:()匹配了一个不接受任何参数的方法, 而(..)匹配了一个接受任意数量参数的方法(零或者更多)。 模式(*)匹配了一个接受一个任何类型的参数的方法。 模式(*,String)匹配了一个接受两个参数的方法,第一个可以是任意类型, 第二个则必须是String类型。

使用AspectJ表达式


任意公共方法的执行:execution(public * *(..))
任何一个名字以“set”开始的方法的执行:execution(* set*(..))
AccountService接口定义的任意方法的执行:execution(* com.xyz.service.AccountService.*(..))
在service包中定义的任意方法的执行:execution(* com.xyz.service.*.*(..))
在service包或其子包中定义的任意方法的执行:execution(* com.xyz.service..*.*(..))
在service包中的任意连接点(在Spring AOP中只是方法执行):within(com.xyz.service.*)
在service包或其子包中的任意连接点(在Spring AOP中只是方法执行):within(com.xyz.service..*)
实现了AccountService接口的代理对象的任意连接点 (在Spring AOP中只是方法执行):this(com.xyz.service.AccountService)
实现AccountService接口的目标对象的任意连接点 (在Spring AOP中只是方法执行):target(com.xyz.service.AccountService)
任何一个只接受一个参数,并且运行时所传入的参数是Serializable 接口的连接点(在Spring AOP中只是方法执行)args(java.io.Serializable)
目标对象中有一个 @Transactional 注解的任意连接点 @target(org.springframework.transaction.annotation.Transactional)
@within(org.springframework.transaction.annotation.Transactional)
任何一个执行的方法有一个 @Transactional 注解的连接点 (在Spring AOP中只是方法执行)
@annotation(org.springframework.transaction.annotation.Transactional)
'@annotation'在绑定表单中更加常用:- 请参见后面的通知一节中了解如何使得注解对象在通知体内可用。
任何一个只接受一个参数,并且运行时所传入的参数类型具有@Classified 注解的连接点(在Spring AOP中只是方法执行):@args(com.xyz.security.Classified)
任何一个在名为'tradeService'的Spring bean之上的连接点 (在Spring AOP中只是方法执行):bean(tradeService)

任何一个在名字匹配通配符表达式'*Service'的Spring bean之上的连接点 (在Spring AOP中只是方法执行):bean(*Service)

=>声明通知

通知是跟一个切入点表达式关联起来的,并且在切入点匹配的方法执行之前或者之后或者前后运行。 切入点表达式可能是指向已命名的切入点的简单引用或者是一个已经声明过的切入点表达式。

1.前置通知  一个切面里面是通过@Before注解声明一个前置通知的

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() {
    // ...
  }
}

2.后置通知  一个切面里面是通过@AfterReturning注解声明一个后置通知的

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

  @AfterReturning(
    pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
    returning="retVal")
  public void doAccessCheck(Object retVal) {
    // ...
  }
}

3.异常通知  一个切面里面是通过@AfterThrowing注解声明一个异常通知的

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

  @AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
  public void doRecoveryActions() {
    // ...
  }
}

使用throwing属性不仅可以限制匹配的异常类型(如果你不想限制,请使用 Throwable作为异常类型),还可以将抛出的异常绑定到通知的一个参数上。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

  @AfterThrowing(
    pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
    throwing="ex")
  public void doRecoveryActions(DataAccessException ex) {
    // ...
  }
}

3.最终通知  一个切面里面是通过@After注解声明一个最终通知的

无论一个方法何时结束的,最终通知都会运行。使用@After注解来声明。最终通知必须准备处理正常返回和异常返回两种情况。通常用它来释放资源。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;

@Aspect
public class AfterFinallyExample {

  @After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
  public void doReleaseLock() {
    // ...
  }
}

3.环绕通知  一个切面里面是通过@Around注解声明一个环绕通知的

环绕通知在一个方法执行之前和之后执行。它使得通知有机会 在一个方法执行之前和执行之后运行。而且它可以决定这个方法在什么时候执行,如何执行,甚至是否执行。 环绕通知经常在某线程安全的环境下,你需要在一个方法执行之前和之后共享某种状态的时候使用。 请尽量使用最简单的满足你需求的通知。(比如如果简单的前置通知也可以适用的情况下不要使用环绕通知)。

环绕通知使用@Around注解来声明。通知的第一个参数必须是 ProceedingJoinPoint类型。在通知体内,调用 ProceedingJoinPointproceed()方法会导致 后台的连接点方法执行。proceed 方法也可能会被调用并且传入一个 Object[]对象-该数组中的值将被作为方法执行时的参数。

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.SystemArchitecture.businessService()")
  public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
    // start stopwatch
    Object retVal = pjp.proceed();
    // stop stopwatch
    return retVal;
  }
}

=> 访问当前的连接点

任何通知方法可以将第一个参数定义为org.aspectj.lang.JoinPoint类型 (环绕通知需要定义第一个参数为ProceedingJoinPoint类型, 它是 JoinPoint 的一个子类)。JoinPoint 接口提供了一系列有用的方法,比如 getArgs()(返回方法参数)、 getThis()(返回代理对象)、getTarget()(返回目标)、 getSignature()(返回正在被通知的方法相关信息)和 toString() (打印出正在被通知的方法的有用信息)。


[未完待续]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值