AOP 概述
AOP(面向切面编程):
一、概念解释
AOP 即 Aspect Oriented Programming,意为面向切面编程。它是通过预编译方式和运行期间动态代理实现程序功能统一维护的一种技术。AOP 是面向对象编程(OOP)的延续,在 Java 开发中占据重要地位。它能够将业务逻辑和非业务逻辑进行隔离,比如将日志记录、事务管理、权限验证等与核心业务逻辑分离,从而降低各部分之间的耦合度,提高程序的可重用性和开发效率。
二、核心原理
AOP 的核心原理是使用动态代理的方式在执行方法前后或者出现异常的时候加入相关的逻辑。具体来说:
-
在目标方法执行前,可以进行一些前置处理,如权限判断。在执行方法前判断当前用户是否具有执行该方法的权限,如果没有权限则阻止方法的执行。
-
在目标方法执行后,可以进行后置处理,如日志记录。记录方法的执行时间、参数、返回值等信息,方便进行系统的监控和调试。
-
当目标方法执行出现异常时,可以进行异常处理,如事务回滚。如果在事务环境中,当方法出现异常时,自动回滚事务,保证数据的一致性。
三、使用案例
-
事务处理:
-
开启事务:在业务方法开始执行前,开启数据库事务。
-
关闭事务:在业务方法正常执行完毕后,提交事务,关闭数据库连接。
-
出现异常后回滚事务:如果业务方法执行过程中出现异常,回滚事务,撤销对数据库的更改。
-
-
权限判断:
-
在执行方法前,判断用户是否具有执行该方法的权限。可以通过检查用户的角色、权限列表等信息来确定用户是否有权限执行特定的方法。如果没有权限,则可以抛出异常或者返回错误信息,阻止方法的执行。
-
-
日志:
-
在执行前进行日志处理,记录方法的调用信息,如方法名、参数等。在方法执行后,记录方法的返回值和执行时间等信息。这样可以方便地跟踪系统的运行状态,进行故障排查和性能优化。
-
AOP 的基本概念
一、连接点(Joinpoint)
连接点是指在程序执行过程中可以被增强的方法。在一个类中,可能有多个方法可以被视为连接点,因为它们都有机会被添加额外的功能或行为。例如,在一个业务类中,多个业务方法都可以被认为是连接点,因为可以在这些方法执行前后或出现异常时进行一些额外的处理。
二、切入点(Pointcut)
切入点是实际被增强的连接点。虽然一个类中可能有很多潜在的连接点,但在实际应用中,只有特定的一些方法会被选择进行增强。比如在一个业务系统中,可能只对某些特定的业务方法如 add(添加操作)和 update(更新操作)进行增强,那么这些方法就被称为切入点。切入点是通过表达式来定义的,用于指定哪些连接点应该被增强。
三、通知(Advice)
通知是指在特定的连接点上要执行的增强功能。通知可以分为不同的类型:
-
方法执行前通知:在目标方法执行之前执行的通知。可以用于进行参数验证、记录日志等操作。
-
方法执行后通知:在目标方法执行之后执行的通知。可以用于记录方法的执行结果、进行资源清理等操作。
-
环绕通知:环绕目标方法执行的通知。可以在目标方法执行前后进行一些复杂的逻辑处理,并且可以控制目标方法是否执行以及如何执行。
四、目标(Target)
目标是指被代理的对象,也就是连接点和切入点所在的类。在 AOP 中,目标对象的方法会被增强,通过代理对象来调用目标对象的方法,并在适当的时候执行通知中的增强逻辑。
五、代理(Proxy)
当向目标对象应用通知时,会创建一个代理对象。代理对象封装了目标对象,并在调用目标对象的方法时,根据通知的类型执行相应的增强逻辑。代理对象可以透明地替代目标对象,使得调用者在不知道的情况下调用了增强后的方法。
springAOP 实现
一、Spring 对 AOP 的支持
Spring 是一个广泛使用的 Java 开发框架,它对面向切面编程(AOP)提供了强大的支持。通过 Spring AOP,可以轻松地将横切关注点(如日志记录、事务管理、安全检查等)与业务逻辑分离,提高代码的可维护性和可扩展性。
二、引入 AspectJ 的实现
AspectJ 是一个基于 Java 语言的强大 AOP 框架,它提供了丰富的 AOP 功能,并且实现方式更为简捷,使用更为方便。Spring 引入了 AspectJ 的 AOP 实现,使得开发者可以在 Spring 框架中使用 AspectJ 的注解和表达式来定义切面、切入点和通知。
三、下载 AOP 相关 jar
为了在项目中使用 Spring AOP,需要添加相关的依赖。在 Maven 项目中,可以通过以下依赖来引入 Spring AOP 和 AspectJ 的支持:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.2.RELEASE</version> </dependency>
这个依赖包含了 Spring AOP 和 AspectJ 的集成部分,使得可以在 Spring 应用中使用 AspectJ 的注解和表达式来定义切面。
四、使用 Spring AOP 和 AspectJ
-
定义切面:使用
@Aspect
注解来定义一个切面类。切面类中包含了通知方法,这些方法会在特定的连接点上执行。 -
定义切入点:使用 AspectJ 的表达式来定义切入点,指定哪些方法应该被增强。例如,可以使用
@Pointcut
注解来定义切入点表达式。 -
定义通知:在切面类中定义通知方法,这些方法会在切入点匹配的方法执行前后或出现异常时执行。通知方法可以使用
@Before
、@After
、@Around
等注解来指定通知的类型。 -
配置 Spring AOP:在 Spring 的配置文件中,启用 Spring AOP 并将切面类注册为 bean。可以使用
<aop:aspectj-autoproxy/>
元素来自动代理切面。
以下是一个简单的示例:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class LoggingAspect { @Before("execution(* com.example.service.*.*(..))") public void logBeforeMethodExecution() { System.out.println("Before method execution"); } }
在这个示例中,定义了一个名为LoggingAspect
的切面类,使用@Before
注解定义了一个方法执行前通知。切入点表达式execution(* com.example.service.*.*(..))
指定了在com.example.service
包下的所有方法执行前都会执行这个通知方法。
AspectJ 中常用的通知有五种类型
一、AspectJ 五种通知类型介绍
-
前置通知(@Before):在目标方法执行之前执行的通知。可以用于进行参数验证、记录日志等操作。在上述代码中,通过
@Before("execution(* com.ff.spring.dao.UserDao.*(..))")
定义了一个前置通知,当执行com.ff.spring.dao.UserDao
类中的任何方法时,会先输出 “saveLog”。 -
后置通知(@After):方法执行之后,无论是否出现异常都会执行的通知。可以用于进行资源清理等操作。
-
返回通知(@AfterReturnning):只有在方法成功执行之后才会执行的通知,如果出现异常则不执行。
-
异常通知(@AfterThrowing):在抛出异常之后执行的通知。在上述代码中,通过
@AfterThrowing(value = "execution(*com.ff.spring.dao.UserDao.*(..))", throwing = "e")
定义了一个异常通知,当com.ff.spring.dao.UserDao
类中的方法抛出异常时,会输出 “afterthrow”。 -
环绕通知(@Around):环绕目标方法执行的通知,可以在目标方法执行前后进行一些复杂的逻辑处理,并且可以控制目标方法是否执行以及如何执行。在上述代码中,定义了一个环绕通知方法
aroundAdvice
,在这个方法中可以通过joinPoint.proceed()
来调用目标方法,并在方法执行前后进行相应的处理,如前置通知、返回通知和异常通知等逻辑都可以在这个方法中实现。
二、基于注解方式的实现步骤
-
启动 AspectJ 支持:在 Spring 的配置文件中添加
<aop:aspectj-autoproxy />
元素来启用 AspectJ 的自动代理功能。这使得 Spring 能够自动识别切面类并为目标对象创建代理。 -
定义通知:
-
使用
@Component
注解将切面类标记为 Spring 的组件,以便被 Spring 容器管理。 -
使用
@Aspect
注解标记切面类,表明这个类是一个 AspectJ 的切面。 -
在切面类中,使用不同的通知注解(如
@Before
、@AfterThrowing
、@Around
等)来定义各种通知方法,并通过切入点表达式指定哪些方法应该被增强。
-
//定义通知:
@Component
@Aspect
public class AOPDemo {
@Before("execution(* com.ff.spring.dao.UserDao.*(..))")
public void saveLog(){
System.*out*.println("saveLog");
}
@AfterThrowing(value = "execution(*com.ff.spring.dao.UserDao.*(..))",throwing = "e")
public void afterthrow(Throwable e){
System.*out*.println("afterthrow");
}
public void aroundAdvice(ProceedingJoinPoint joinPoint){
try {
//前置通知
Object[] objects =
joinPoint.getArgs();
joinPoint.proceed();//调用我们自己的目标方法
//返回通知
} catch (Throwable throwable) {
throwable.printStackTrace();
//异常通知
}
//后置通知
}
}