AOP:面向切面编程
AOP:首先需要明确一点,AOP不是spring、甚至不是java特有的概念。
面向切面编程(AOP,Aspect-Oriented Programming)是一种代码组织和复用范式,其核心思想是将传统的纵向处理,改为横向切割,从而实现通用业务和核心业务逻辑分离。可以达到代码的复用、解耦。

引入
1 2 3 4 5 6 7 8 9 10
| <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.19</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.2.24.RELEASE</version> </dependency>
|
配置
在Springconfig中如下配置
1 2 3 4 5 6
| @Configuration @ComponentScan @EnableAspectJAutoProxy public class SpringConfig {
}
|
好比我们接下来要设定一个创建新用户——权限检验——成功创建的逻辑
新建用户类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package Spring;
import org.springframework.stereotype.Component;
@Component public class Userdemo { public void add_user () { System.out.println("添加用户"); } public void get_user () { System.out.println("已获取用户:"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package Spring.aop;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component;
@Component @Aspect public class Authaspect { @Pointcut("bean(userdemo)") public void pointCut() { }
@Before("pointCut()") public void doLog() { System.out.println("正在进行权限校验"); }
}
|
AOP就是锚定指定类的受Spring管理下的对象,拦截他们的方法并在执行之前执行开发者想要执行的逻辑。
执行,在SpringDemo中执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package Spring;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SpringDemo { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); Userdemo nmsl = context.getBean(Userdemo.class); nmsl.add_user(); System.out.println("------------------"); nmsl.get_user(); } }
|
我来更正一下说法,我们基于AOP,对源对象方法的拦截插入等操作一般叫做“增强”,因为拦截插入使得整个程序有了统一的出口和入口,整体上性能有所提升。并且,在方法之前插入或者之后插入被称为“通知”。
几种通知类型:
执行前通知:Before
执行后通知:After
正常返回结果后通知: AfterReturning
抛出异常后通知:AfterThrowing
环绕通知:Around
使用通式就是这么写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| package Spring.aop;
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component;
@Component @Aspect public class Authaspect { @Pointcut("bean(userdemo)") public void pointCut() { }
@Before("pointCut()") public void doLog() { System.out.println("正在进行权限校验————————权限校验通过"); }
@After("pointCut()") public void doAfter() { System.out.println("doAfter"); }
@AfterReturning("pointCut()") public void doAfterReturning() { System.out.println("doAfterReturning"); }
@AfterThrowing("pointCut()") public void doAfterThrowing() { System.out.println("doAfterThrowing"); }
@Around("pointCut()") public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("doAround before"); Object obj = joinPoint.proceed(); System.out.println("doAround after"); return obj; }
}
|
有时候,为每个类单独锚定切入点是很困难的,所以我们会使用别的查找方法来批量切入。
一、execution:方法执行(最常用)
AOP 中的 execution方法是定义切入点(Pointcut)的核心方式,它通过编写表达式来精确匹配哪些方法需要被增强。你可以把它想象成一个功能强大的“瞄准镜”,负责在众多方法中锁定那些需要被注入通用功能(如日志、事务等)的目标。
语法结构
1 2 3
| execution( [修饰符] 返回类型 [包路径.类名.方法名](参数列表) [throws 异常] )
|
通配符说明
| 符号 |
含义 |
示例 |
* |
匹配任意单个元素 |
java.*.Service |
.. |
匹配多个元素(包/参数) |
com.example..* |
典型示例
1 2 3 4 5 6 7 8
| @Pointcut("execution(public * com.example.service.*.*(..))")
@Pointcut("execution(* com.example.service.UserService.get*(..))")
@Pointcut("execution(String *())")
|
二、within:类/包范围限定
使用场景
快速匹配整个类或包下的所有方法
代码示例
1 2 3 4 5 6 7 8
| @Pointcut("within(com.example.service.*)")
@Pointcut("within(com.example.service..*)")
@Pointcut("within(com.example.service.UserServiceImpl)")
|
三、this:代理对象类型匹配
适用场景
基于JDK动态代理时匹配接口类型
1 2
| @Pointcut("this(com.example.service.UserService)")
|
四、target:目标对象类型匹配
适用场景
基于CGLIB代理时匹配实现类
1 2
| @Pointcut("target(com.example.service.UserServiceImpl)")
|
五、args:参数类型匹配
特殊用法
1 2 3 4 5 6 7 8
| @Pointcut("args(Long, ..)")
@Before("args(userId, ..) && target(service)") public void log(Long userId, UserService service) { System.out.println("操作用户ID:" + userId); }
|
六、@annotation:方法注解匹配
实战应用
1 2 3 4 5 6 7 8 9 10 11 12
| @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface AuditLog {}
@Pointcut("@annotation(com.example.AuditLog)") public void auditPointcut() {}
@AuditLog public void sensitiveOperation() { }
|
七、@within:类注解匹配
应用场景
匹配带有指定注解的类中的所有方法
1 2 3 4 5 6 7
| @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface SecureApi {}
@Pointcut("@within(com.example.SecureApi)")
|
八、bean:Spring Bean名称匹配
Spring特有语法
1 2 3 4 5 6 7 8
| @Pointcut("bean(userService)")
@Pointcut("bean(*Service)")
@Pointcut("bean(userService) && !bean(adminService)")
|
组合表达式技巧
| 运算符 |
说明 |
示例 |
| && |
逻辑与 |
execution(* save*(..)) && within(com.example.service) |
| || |
逻辑或 |
`@annotation(AuditLog) |
| ! |
逻辑非 |
execution(* *(..)) && !bean(testService) |
复杂组合示例
1 2 3 4
| @Pointcut("execution(* com.example.service.*.*(..)) " + "&& !@annotation(DevOnly) " + "&& !within(com.example.service.test.*)")
|
性能优化建议
- 缩小匹配范围:优先使用
within缩小包范围
- 避免过度通配:
*Service比*.*效率高10倍以上
- 缓存切入点:使用
@Pointcut定义可复用的切入点
AOP中的一些术语
只是帮助理解
- 切面:一系列切点+通知的集合,一个关注点的模块化。(例如日志切面主要负责处理日志,认证切面主要负责处理认证相关)
- 连接点:程序执行过程中的一个点,例如方法的执行或异常的处理。在Spring AOP中,连接点始终表示方法执行。我们可以在连接点来进行拦截和增强。(例子:spring中,所有可以被增强的方法都是连接点)(秀女选妃,所有秀女都是连接点)(反向例子:我想在方法的一半来进行拦截,spring中是做不到的)
- 通知:简单来说,就是你要干嘛。(例如我要记录日志、权限校验等。)
- 切点:简单来说,就是我要在哪些连接点进行增强。不增强的点叫连接点,增强的点就是切点。(秀女选妃,选上了的就是切点)
- 引入:特殊增强:为类动态添加新接口。
- 目标对象:简单来说,就是我们切面进行了增强的原始对象。
- 代理:在spring中,主要使用JDK动态代理(基于接口) 和CGLIB代理(基于类继承)
- 织入:就是把我们的通知插入切点的过程。