spring-AOP-面向切面编程
1,AOP 概念
面向切面的编程(AOP)通过提供另一种思考程序结构的方式来补充面向对象的编程(OOP)。OOP中模块化的关键单位是类,而AOP中模块化的单位是切面。切面使跨越多种类型和对象的关注点(如事务管理)模块化。(这样的关注点在AOP文献中通常被称为 "交叉(crosscutting)" 关注点)。
1,术语
-
Aspect(切面): 一个跨越多个类的关注点的模块化。事务管理是企业级Java应用中横切关注点的一个很好的例子。在Spring AOP中,切面是通过使用常规类(基于 schema 的方法)或使用 @Aspect 注解的常规类(@AspectJ 风格)实现的。
-
Join point: 程序执行过程中的一个点,例如一个方法的执行或一个异常的处理。在Spring AOP中,一个连接点总是代表一个方法的执行。
-
Advice: 一个切面在一个特定的连接点采取的行动。不同类型的advice包括 "around"、"before" 和 "after" 的advice(Advice 类型将在后面讨论)。许多AOP框架,包括Spring,都将advice建模为一个拦截器,并在连接点(Join point)周围维护一个拦截器链。
-
Pointcut: 一个匹配连接点的谓词(predicate)。advice与一个切点表达式相关联,并在切点匹配的任何连接点上运行(例如,执行一个具有特定名称的方法)。由切点表达式匹配的连接点概念是AOP的核心,Spring默认使用AspectJ的切点表达式语言。
-
Introduction: 代表一个类型声明额外的方法或字段。Spring AOP允许你为任何 advice 的对象引入新的接口(以及相应的实现)。例如,你可以使用引入来使一个bean实现 IsModified 接口,以简化缓存。(介绍在AspectJ社区中被称为类型间声明)。
-
Target object: 被一个或多个切面所 advice 的对象。也被称为 "advised object"。由于Spring AOP是通过使用运行时代理来实现的,这个对象总是一个被代理的对象。
-
AOP proxy: 一个由AOP框架创建的对象,以实现切面契约(advice 方法执行等)。在Spring框架中,AOP代理是一个JDK动态代理或CGLIB代理。
-
Weaving(织入): 将aspect与其他应用程序类型或对象连接起来,以创建一个 advice 对象。这可以在编译时(例如,使用AspectJ编译器)、加载时或运行时完成。Spring AOP和其他纯Java AOP框架一样,在运行时进行织入。
2,AOP 代理
Spring AOP默认使用标准的JDK动态代理进行AOP代理。这使得任何接口(或一组接口)都可以被代理。
Spring AOP也可以使用CGLIB代理。这对于代理类而不是接口来说是必要的。默认情况下,如果一个业务对象没有实现一个接口,就会使用CGLIB。由于对接口而不是类进行编程是很好的做法,业务类通常实现一个或多个业务接口。在那些(希望是罕见的)需要向没有在接口上声明的方法提供advice的情况下,或者在需要将代理对象作为具体类型传递给方法的情况下,可以 强制使用 CGLIB。
3,Spring AOP 和 AspectJ 的区别
- SpringAOP 是 spring 支持的面向切面 AOP 编程。
- AspectJ 是一个面向切面的框架,它扩展了 Java 语言。AspectJ 定义了 AOP 语法,它有一个专门的编译器用来生成遵守 Java 字节编码规范的 Class 文件。
4,使用
1,启用 @AspectJ
- 注解
@Configuration @EnableAspectJAutoProxy public class AppConfig { }
- XML 配置文件
<aop:aspectj-autoproxy/>
2,声明一个 Aspect
- 注解
package com.xyz; import org.aspectj.lang.annotation.Aspect; @Aspect @Component public class NotVeryUsefulAspect { }
3,声明一个切点 PointCut
@Pointcut("execution(* transfer(..))") // the pointcut expression private void anyOldTransfer() {} // the pointcut signature
Spring AOP支持以下AspectJ的切点指定器(PCD),用于切点表达式中
-
execution: 用于匹配方法执行的连接点。这是在使用Spring AOP时要使用的主要切点指定器。
-
within: 将匹配限制在某些类型内的连接点(使用Spring AOP时,执行在匹配类型内声明的方法)。
-
this: 将匹配限制在连接点(使用Spring AOP时方法的执行),其中bean引用(Spring AOP代理)是给定类型的实例。
-
target: 将匹配限制在连接点(使用Spring AOP时方法的执行),其中目标对象(被代理的应用程序对象)是给定类型的实例。
-
args: 将匹配限制在连接点(使用Spring AOP时方法的执行),其中参数是给定类型的实例。
-
@target: 限制匹配到连接点(使用Spring AOP时方法的执行),其中执行对象的类有一个给定类型的注解。
-
@args: 将匹配限制在连接点(使用Spring AOP时方法的执行),其中实际传递的参数的运行时类型有给定类型的注解。
-
@within: 将匹配限制在具有给定注解的类型中的连接点(使用Spring AOP时,执行在具有给定注解的类型中声明的方法)。
-
@annotation: 将匹配限制在连接点的主体(Spring AOP中正在运行的方法)具有给定注解的连接点上。
其他的 pointcut 类型
完整的AspectJ点式语言支持Spring不支持的其他点式指定符:call、get、set、preinitialization、staticinitialization、initialization、handler、adviceexecution、withincode、cflow、cflowbelow、if、@this 和 @withincode。在由Spring AOP解释的pointcut表达式中使用这些 pointcut 指定器会导致> 抛出 IllegalArgumentException。Spring AOP支持的切点指定器集合可能会在未来的版本中扩展,以支持更多的AspectJ切点指定器。
组合切点 PointCut 表达式
你可以通过使用 &&、|| 和 ! 来组合 pointcut 表达式。你也可以通过名称来引用pointcut表达式。下面的例子显示了三个pointcut表达式。
package com.xyz; @Aspect public class Pointcuts { @Pointcut("execution(public * *(..))") public void publicMethod() {} (1) @Pointcut("within(com.xyz.trading..*)") public void inTrading() {} (2) @Pointcut("publicMethod() && inTrading()") public void tradingOperation() {} (3) }
一些常见的 PointCut 表达式
- 任何 public 方法的 execution
execution(public * *(..))
- 任何名称以 set 开头的方法的 execution。
execution(* set*(..))
- AccountService 接口所定义的任何方法的 execution:
execution(* com.xyz.service.AccountService.*(..))
- 在 service 包中定义的任何方法的 execution:
execution(* com.xyz.service.*.*(..))
- 在 service 包或其子包中定义的任何方法的 execution。
execution(* com.xyz.service..*.*(..))
- service 包内的任何连接点(仅在Spring AOP中的method execution)。
within(com.xyz.service.*)
- 在 service 包或其一个子包中的任何连接点(仅在Spring AOP中的method execution)。
within(com.xyz.service..*)
- 任何代理实现了 AccountService 接口的连接点(仅在Spring AOP中的method execution)。
this(com.xyz.service.AccountService)
- 目标对象实现了 AccountService 接口的任何连接点仅在Spring AOP中的method execution)。
target(com.xyz.service.AccountService)
- 任何连接点(仅在Spring AOP中的method execution)都需要一个参数,并且在运行时传递的参数是 Serializable 的。
args(java.io.Serializable)
- 任何连接点(仅在Spring AOP中的method execution),其中目标对象有 @Transactional 注解。
@target(org.springframework.transaction.annotation.Transactional)
- 任何连接点(仅在Spring AOP中的method execution),其中目标对象的声明类型有一个 @Transactional 注解。
@within(org.springframework.transaction.annotation.Transactional)
- 任何连接点(仅在Spring AOP中的method execution),其中执行的方法有一个 @Transactional 注解。
@annotation(org.springframework.transaction.annotation.Transactional)
*任何连接点(仅在Spring AOP中的method execution),它需要一个参数,并且所传递的参数的运行时类型具有 @Classified 注解。
@args(com.xyz.security.Classified)
- 在一个名为 tradeService 的Spring Bean上的任何连接点(仅在Spring AOP中的method execution)。
bean(tradeService)
- 在Spring Bean上的任何连接点(仅在Spring AOP中的method execution),其名称与通配符表达式 *Service 相匹配。
bean(*Service)
4,声明一个 Advice
- 内联的切点表达式
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect public class BeforeExample { @Before("execution(* com.xyz.dao.*.*(..))") public void doAccessCheck() { // ... } }
- 命名的切点表达式
@Aspect @Component public class AspectTest { @Pointcut("execution(* com.spring1.service.aop.ServiceImpl.*(..))") public void pointCut(){} //这里 pointCut() 可以是其他类中的,要写全限定名 @Before("pointCut()") public void before(JoinPoint point){ System.out.println("前置通知:" + point); } }
增强的几种类型
-
@Before
方法执行前 -
@AfterReturning
当一个匹配的方法执行正常返回
@Aspect public class AfterReturningExample { //retVal 可以获取方法返回值 @AfterReturning( pointcut="execution(* com.xyz.dao.*.*(..))", returning="retVal") public void doAccessCheck(Object retVal) { // ... } }
- @AfterThrowing
当一个匹配的方法执行通过抛出异常退出时,After throwing advice 运行
@Aspect public class AfterThrowingExample { // throwing 希望advice只在给定类型的异常被抛出时运行,而且你也经常需要在advice body中访问被抛出的异常 @AfterThrowing( pointcut="execution(* com.xyz.dao.*.*(..))", throwing="ex") public void doRecoveryActions(DataAccessException ex) { // ... } }
- @After
当一个匹配的方法执行退出时,After (finally) advice 会运行
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.After; @Aspect public class AfterFinallyExample { @After("execution(* com.xyz.dao.*.*(..))") public void doReleaseLock() { // ... } }
请注意,AspectJ中的 @After advice 被定义为 "after finally advice",类似于try-catch语句中的finally块。它将对任何结果、正常返回或从连接点(用户声明的目标方法)抛出的异常进行调用,这与 @AfterReturning 不同,后者只适用于成功的正常返回。
- @Around
"围绕" 一个匹配的方法的执行而运行。它有机会在方法运行之前和之后进行工作,并决定何时、如何、甚至是否真正运行该方法。如果你需要以线程安全的方式分享方法执行前后的状态,例如启动和停止一个定时器,那么 Around advice 经常被使用。
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.ProceedingJoinPoint; @Aspect public class AroundExample { @Around("execution(* com.xyz..service.*.*(..))") public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable { // start stopwatch Object retVal = pjp.proceed(); // stop stopwatch return retVal; } }
始终使用符合你要求的最不强大的 advice 形式。
例如,如果 before advice 足以满足你的需要,就不要使用 around advice。
Advice 参数:
- 访问当前的 JoinPoint
任何 advice method 都可以声明一个 org.aspectj.lang.JoinPoint 类型的参数作为其第一个参数。请注意,around advice 方法需要声明一个 ProceedingJoinPoint 类型的第一个参数,它是 JoinPoint 的一个子类。
JoinPoint 接口提供了许多有用的方法。
-
getArgs(): 返回方法的参数。
-
getThis(): 返回代理对象。
-
getTarget(): 返回目标对象。
-
getSignature(): 返回正在被 advice 的方法的描述。
-
toString(): 打印对所 advice 的方法的有用描述。
向 Advice 传递参数:
@Before("execution(* com.xyz.dao.*.*(..)) && args(account,..)") public void validateAccount(Account account) { // ... }
pointcut 表达式的 args(account,..) 部分有两个作用。首先,它将匹配限制在方法的执行上,即方法至少需要一个参数,并且传递给该参数的参数是一个 Account 的实例。其次,它使实际的 Account 对象通过 account 参数对 advice 可用。
本文作者:Hi.PrimaryC
本文链接:https://www.cnblogs.com/cnff/p/17562820.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步