Spring AOP
前言
AOP中有些概念比较容易混淆,借着分享的机会梳理一波。文章包括AOP的概念,AspectJ与Spring AOP的区别,以及Spring AOP的一些语法、场景,同时结合示例说明关于不同通知的执行顺序情况。
AOP概念
AOP
(Aspect-oriented Programming)面向切面编程,是一种编程模式,作为面向对象编程的一种补充,即提供了一种将横切关注点从业务中分离出来的方式。广泛用于例如事务管理、安全检查、缓存等等,可以把这些能力叫做横切关注点,与核心业务逻辑无关的、被重复使用的功能。
AOP的思想就是将横切关注点从核心业务逻辑中抽离出来,形成可复用的模块,也称为切面(Aspect),切面定义了在何处(切入点)以及如何(通知)将横切关注点应用到目标代码中。
常见的 AOP 框架包括 AspectJ、Spring AOP 等。框架的关键在于自动创建AOP代理。
AOP 代理则可分为静态代理和动态代理两大类:
静态代理是指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强;
动态代理则在运行时借助于 JDK 动态代理、CGLIB 等在内存中生成 AOP 动态代理类,因此也被称为运行时增强。
AspectJ AOP
明确一点,Aspect并不是Spring的一部分,它是独立的面向切面的解决方案,是一个框架,同时支持静态代理和动态代理。
AspectJ AOP的一些概念
1、切点(Pointcut):切点用于定义/判断在目标代码中哪些位置可以应用切面的功能。
2、通知(Advice):通知定义了在切点处执行的具体操作,如在方法执行前、执行后、抛出异常时等执行特定的逻辑。
3、切面(Aspect):切面是一个模块化的单元,用于封装横切关注点和通知。
4、织入(Weaving):织入是将切面的功能应用到目标代码中的过程。AspectJ 提供了编译器织入(Compile-Time Weaving)、类加载器织入(Load-Time Weaving)和运行时织入(Runtime Weaving)等织入方式。
切面通过将切点和通知结合在一起,实现了横切关注点的管理和控制。
示例演示
1、引入pom
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.13</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
<build>
<plugins>
<!-- 编译期织入,用以编译.aj文件到.class,在编译期的时候进行织入,这样编译出来的 .class 文件已经织入了我们的代码 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.7</version>
<configuration>
<complianceLevel>1.8</complianceLevel>
<source>1.8</source>
<target>1.8</target>
<showWeaveInfo>true</showWeaveInfo>
<verbose>true</verbose>
<Xlint>ignore</Xlint>
<encoding>UTF-8</encoding>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
2、创建业务类
@Getter
@Setter
public class Account {
public int balance = 20;
public boolean pay(int amount) {
if (balance < amount) {
return false;
}
balance -= amount;
return true;
}
}
3、AspectJ实现
iead 需要导入下AspectJ插件
import com.mainto.spring.aop.Account;
// 使用aspect声明
public aspect AccountAspect {
pointcut callPay(int amount, Account account):
call(boolean com.mainto.spring.aop.Account.pay(int)) && args(amount) && target(account);
boolean around(int amount, Account account): callPay(amount, account) {
if (account.balance < amount) {
System.out.println("[AccountAspect]拒绝付款!");
return false;
} else {
System.out.println("[AccountAspect]支付成功 " + amount);
}
return proceed(amount, account);
}
}
文件命名为 .aj
4、测试
public class AspectTest {
public static void main(String[] args) {
testCompileTime();
}
public static void testCompileTime() {
Account account = new Account();
System.out.println("==================");
account.pay(10);
account.pay(50);
System.out.println("==================");
}
}
输出结果:
==================
[AccountAspect]支付成功 10
[AccountAspect]拒绝付款!
==================
5、看下编译后的class
public static void main(String[] args) {
testCompileTime();
}
public static void testCompileTime() {
Account account = new Account();
System.out.println("==================");
byte var1 = 10;
pay_aroundBody1$advice(account, var1, AccountAspect.aspectOf(), var1, account, (AroundClosure)null);
byte var3 = 50;
pay_aroundBody3$advice(account, var3, AccountAspect.aspectOf(), var3, account, (AroundClosure)null);
System.out.println("==================");
}
可以看出来,示例中通过maven插件,使得AspectJ在编译阶段便进行了织入。
AspectJ引入供学习了解,更轻量的实现可以参考Spring AOP
Spring AOP
Spring AOP是基于代理的轻量级AOP框架,运行时的动态代理实现,Spring AOP并不是完全的Spring的生态,是基于AspectJ和IoC容器的整合。Spring AOP实现了AspectJ的Annotation,使用了@Aspect来定义切面,使用
@Pointcut来定义切入点,使用Advice的注解来定义增强处理。虽然使用了AspectJ的Annotation,但是并没有使用它的编译器和织入器。
Spring AOP一些概念
1、切点(Pointcut):切点用于定义/判断在目标代码中哪些位置可以应用切面的功能。
2、通知(Advice):通知定义了在切点处执行的具体操作,如在方法执行前、执行后、抛出异常时等执行特定的逻辑。
3、切面(Aspect):切面是一个模块化的单元,用于封装横切关注点和通知。在 Spring AOP 中,切面是一个普通的 Java 类。
4、连接点(Join Point):连接点是在应用程序执行过程中与切点匹配的点。在 Spring AOP 中,方法调用是最常见的连接点。
5、代理(Proxy):Spring AOP 使用代理模式来实现切面的织入。它可以通过 JDK 动态代理或 CGLIB 字节码增强技术创建代理对象。
示例演示
1、引入pom
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.19</version>
</dependency>
2、定义aop
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class AspectConfiguration {
@Pointcut("execution(public * *(..))") // 匹配 Join Point
private void anyPublicMethod() {} // 方法名即 Pointcut 名
@Around("anyPublicMethod()") // Join Point 拦截动作
public Object aroundAnyPublicMethod(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("@Around any public method.");
return pjp.proceed();
}
}
3、定义测试类
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
// 激活 Aspect 注解自动代理
@EnableAspectJAutoProxy
public class AspectJAnnotatedPointcutDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(AspectJAnnotatedPointcutDemo.class,
AspectConfiguration.class);
// 刷新
context.refresh();
AspectJAnnotatedPointcutDemo aspectJAnnotationDemo = context.getBean(AspectJAnnotatedPointcutDemo.class);
aspectJAnnotationDemo.execute();
// 关闭
context.close();
}
private void execute() {
System.out.println("AspectJAnnotatedPointcutDemo execute()...");
}
}
输出结果:
@Around any public method.
AspectJAnnotatedPointcutDemo execute()…
Spring AOP 与 AspectJ AOP的区别
1、AspectJ是AOP的完整实现,Spring AOP是部分实现。
2、Spring AOP比AspectJ使用起来更加的简单;AspectJ更强大,可以修改目标代码的字节码。
3、Spring AOP整合了AspectJ和Spring IoC容器。
4、Spring AOP仅支持基于代理模式的AOP,AspectJ可以支持3种织入方式(编译、类加载、运行)。
5、Spring AOP仅支持方法级别的PointCut,AspectJ可以在方法级别之外匹配更细粒度的连接点,包括方法执行、构造器执行、方法调用、构造器调用、异常处理、字段访问、静态初始化和对象初始化等。
Spring AOP详解
AOP通知(Advice)类型
上面已经介绍了一些观念,再看下AOP的通知,Spring中通知(Advice)包括5中:
前置通知(Before)
后置通知(After)
返回通知(AfterReturning)
异常通知(AfterThrowing)
环绕通知(Around)
它们的顺序是怎么样的?
如果有多个切面,它们的顺序又是怎么样的?
Spring AOP的使用方式
1、基于注解的方式:通过在切面类和目标类中使用注解来标识切面和切点。(推荐)
2、基于XML配置的方式:通过在 XML 配置文件中定义切面、切点和通知的关系来实现切面的织入。
3、Api编程方式:通过编写代码来手动创建切面、定义切点和通知,并将切面应用到目标对象中。
Advice演示
使用注解的方式
1、定义切面1
@Aspect
@Order(10)
public class AspectConfiguration {
@Pointcut("execution(public * *())") // 匹配 Join Point,无参数
private void anyPublicMethod() {} // 方法名即 Pointcut 名
@Around("anyPublicMethod()")
public Object aroundAnyPublicMethod(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("@Around any public method. " + pjp.getSignature());
return pjp.proceed();
}
@Before("anyPublicMethod()")
public void beforeAnyPublicMethod() throws Throwable {
System.out.println("@Before any public method.");
}
@After("anyPublicMethod()")
public void finalizeAnyPublicMethod() {
System.out.println("@After any public method.");
}
@AfterReturning("anyPublicMethod()")
public void afterAnyPublicMethod() {
System.out.println("@AfterReturning any public method.");
}
}
2、定义切面2
@Aspect
@Order(20)
public class AspectConfiguration2 {
@Pointcut("execution(public * *())") // 匹配 Join Point,无参数
private void anyPublicMethod() {} // 方法名即 Pointcut 名
@Around("anyPublicMethod()")
public Object aroundAnyPublicMethod(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("@Around any public method.(2) " + pjp.getSignature());
return pjp.proceed();
}
@Before("anyPublicMethod()")
public void beforeAnyPublicMethod() throws Throwable {
System.out.println("@Before any public method.(2)");
}
@After("anyPublicMethod()")
public void finalizeAnyPublicMethod() {
System.out.println("@After any public method.(2)");
}
@AfterReturning("anyPublicMethod()")
public void afterAnyPublicMethod() {
System.out.println("@AfterReturning any public method.(2)");
}
}
3、测试
@Configuration
// 激活 Aspect 注解自动代理
@EnableAspectJAutoProxy
public class AspectJAnnotatedPointcutDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(AspectJAnnotatedPointcutDemo.class,
AspectConfiguration.class,
AspectConfiguration2.class
);
// 刷新
context.refresh();
AspectJAnnotatedPointcutDemo aspectJAnnotationDemo = context.getBean(AspectJAnnotatedPointcutDemo.class);
aspectJAnnotationDemo.execute();
// 关闭
context.close();
}
public void execute() {
System.out.println("AspectJAnnotatedPointcutDemo execute()...");
}
}
输出结果:
@Around any public method. void com.mainto.spring.aop.spring.AspectJAnnotatedPointcutDemo.execute()
@Before any public method.
@Around any public method.(2) void com.mainto.spring.aop.spring.AspectJAnnotatedPointcutDemo.execute()
@Before any public method.(2)AspectJAnnotatedPointcutDemo execute()…
@AfterReturning any public method.(2)
@After any public method.(2)
@AfterReturning any public method.
@After any public method.
需注意:Spring 4 和 Spring 5关于after和afterReturning的顺序会有所不同,Spring 4 after先执行,Spring 5 after后执行。本文演示使用的Spring 5版本。
小结:
1、单个切面的通知:
around -> before -> target method -> afterReturing/afterThrowing -> after
2、多个切面,按优先级高低执行,如@Order值越小优先级越高,优先执行的后结束,即先进后出。
PointCut常用方式
1、execution表达式:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
详见官方文档:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-pointcuts-examples
示例:
1)、execution(public void com.example.MyService.doSomething()):匹配 com.example.MyService 类中名为 doSomething、无参数且返回类型为 void 的公共方法。
2)、execution(* com.example.MyService.*()):匹配 com.example.MyService 类中的所有无参数方法。
2、自定义注解:@annotation(方法)、@within(类)
Spring AOP 的常见应用场景
1、日志记录:通过 AOP 可以方便地在方法执行前后记录方法的输入参数、返回值等信息,实现全局的日志记录功能。
2、事务管理:AOP 可以在方法执行前后自动开启和提交事务,简化事务管理的代码。
3、缓存管理:通过 AOP 可以在方法执行前先查询缓存,减少对数据库等资源的访问。
4、安全性检查:AOP 可以在方法执行前进行安全性/权限检查,确保用户有足够的权限执行某个操作。
5、性能监控:AOP 可以对方法的执行时间进行统计,帮助开发者分析系统的性能瓶颈。
扩展
Spring aop相关api:
总结
Spring AOP 是一个强大而又常用的面向切面编程框架,常用在日常开发中。篇幅有限关于Api的一些介绍后续再扩展。