前言
大家好,这里是程序员阿亮!今天来给大家讲一下AOP!
不知道大家是否有过这样的经历,当我们要在项目中加上权限校验、日志处理等公用操作,那么很有可能就需要修改大量的文件,给每一个类、方法都加上许多的重复性的代码,这样很繁杂且没有效率,更难以维护...
这时候,AOP(Aspect-Oriented Programming)就是你的救星。
如果说 IOC 是为了解耦 (把对象创建交出去),那么 AOP 就是为了降噪(把重复的非业务代码抽离出来)。
一、AOP是什么?
AOP(Aspect Oriented Programming) 是一种编程范式,用于将横切关注点(cross-cutting concerns)从业务逻辑中分离出来。横切关注点是指那些跨越多个模块的功能,如日志记录、事务管理、安全控制等。
二、核心术语

这些核心概念实际上不用特意去记,我用一些例子来讲解一下。
1. Aspect(切面)
切面是横切关注点的模块化,包含通知和切入点的组合。
java
// 使用@Aspect注解定义切面
@Aspect
@Component
public class LoggingAspect {
// 定义切入点
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}
// 定义通知
@Before("serviceMethods()")
public void logBefore(JoinPoint joinPoint) {
System.out.println("方法执行前: " + joinPoint.getSignature().getName());
}
}
2. Join Point(连接点)
程序执行过程中的某个特定点,如方法调用、异常抛出等。在Spring AOP中,连接点总是方法的执行。
java
// 连接点示例:UserService中的所有方法都是连接点
@Service
public class UserService {
// 这是一个连接点
public void createUser(User user) {
// 业务逻辑
}
// 这也是一个连接点
public User getUserById(Long id) {
return userRepository.findById(id);
}
}
3. Pointcut(切入点)
匹配连接点的谓词,用于指定在哪些连接点应用通知。
java
@Aspect
@Component
public class PointcutExamples {
// 匹配UserService类中的所有方法
@Pointcut("execution(* com.example.service.UserService.*(..))")
public void userServiceMethods() {}
// 匹配所有以"create"开头的方法
@Pointcut("execution(* com.example.service..create*(..))")
public void createMethods() {}
// 匹配所有返回User类型的方法
@Pointcut("execution(com.example.model.User com.example.service..*(..))")
public void userReturnMethods() {}
// 组合切入点
@Pointcut("userServiceMethods() && createMethods()")
public void userServiceCreateMethods() {}
}
4. Advice(通知)
在特定连接点执行的动作。有5种类型的通知:
4.1 @Before(前置通知)
在连接点之前执行。
java
@Aspect
@Component
public class BeforeAdviceExample {
@Before("execution(* com.example.service.UserService.createUser(..))")
public void beforeCreateUser(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
System.out.println("准备创建用户,参数: " + Arrays.toString(args));
// 可以进行参数验证
if (args.length > 0 && args[0] instanceof User) {
User user = (User) args[0];
if (user.getName() == null || user.getName().isEmpty()) {
throw new IllegalArgumentException("用户名不能为空");
}
}
}
}
4.2 @After(后置通知)
在连接点之后执行,无论是否异常。
java
@Aspect
@Component
public class AfterAdviceExample {
@After("execution(* com.example.service.UserService.*(..))")
public void afterAnyUserServiceMethod(JoinPoint joinPoint) {
System.out.println("方法 " + joinPoint.getSignature().getName() + " 执行完毕");
}
}
4.3 @AfterReturning(返回通知)
在连接点正常返回后执行。
java
@Aspect
@Component
public class AfterReturningAdviceExample {
@AfterReturning(
pointcut = "execution(* com.example.service.UserService.getUserById(..))",
returning = "result"
)
public void afterGetUserById(JoinPoint joinPoint, User result) {
System.out.println("获取用户成功: " + result.getName());
// 可以对返回结果进行处理
if (result != null) {
result.setLastAccessTime(new Date());
}
}
}
4.4 @AfterThrowing(异常通知)
在连接点抛出异常后执行。
java
@Aspect
@Component
public class AfterThrowingAdviceExample {
@AfterThrowing(
pointcut = "execution(* com.example.service.UserService.*(..))",
throwing = "ex"
)
public void afterUserServiceException(JoinPoint joinPoint, Exception ex) {
System.out.println("方法 " + joinPoint.getSignature().getName() +
" 抛出异常: " + ex.getMessage());
// 记录异常日志
logger.error("业务方法异常", ex);
}
}
4.5 @Around(环绕通知)
包围连接点,在方法执行前后都可执行自定义行为。
java
@Aspect
@Component
public class AroundAdviceExample {
@Around("execution(* com.example.service.UserService.*(..))")
public Object aroundUserServiceMethods(ProceedingJoinPoint pjp) throws Throwable {
long startTime = System.currentTimeMillis();
try {
System.out.println("方法开始: " + pjp.getSignature().getName());
// 执行目标方法
Object result = pjp.proceed();
System.out.println("方法结束: " + pjp.getSignature().getName());
return result;
} catch (Exception ex) {
System.out.println("方法异常: " + ex.getMessage());
throw ex;
} finally {
long endTime = System.currentTimeMillis();
System.out.println("方法执行时间: " + (endTime - startTime) + "ms");
}
}
}
5. Target Object(目标对象)
被一个或多个切面通知的对象,也称为被代理对象。
java
// 目标对象
@Service
public class UserService {
public void createUser(User user) {
System.out.println("创建用户: " + user.getName());
}
public User getUserById(Long id) {
System.out.println("获取用户ID: " + id);
return new User(id, "张三");
}
}
6. Weaving(织入)
将切面应用到目标对象并创建代理对象的过程。织入可以在编译时、类加载时或运行时进行。
java
// Spring配置类,启用AOP自动代理
@Configuration
@EnableAspectJAutoProxy // 启用AspectJ自动代理(织入)
public class AppConfig {
@Bean
public UserService userService() {
return new UserService();
}
@Bean
public LoggingAspect loggingAspect() {
return new LoggingAspect();
}
}
三、AOP如何实现?
Spring AOP 的魔法核心只有四个字:动态代理。
当你从 Spring 容器(IOC容器)中获取一个 Bean 时,你拿到的根本不是你写的那个类,而是一个被 Spring 偷梁换柱后的"代理对象"。
Spring 提供了两种实现动态代理的方式,这是面试的重灾区:
1. JDK 动态代理 (JDK Dynamic Proxy)
-
原理: 基于 Java反射包 java.lang.reflect.Proxy 实现。
-
要求: 目标类必须实现接口。
-
工作方式: 代理对象和目标对象实现了同一个接口。
2. CGLIB 动态代理 (Code Generation Library)
-
原理: 基于 ASM 字节码生成框架。
-
要求: 目标类不需要实现接口。
-
工作方式: 代理对象是目标对象的子类(继承)。它通过重写父类方法来增强功能。
-
注意:因为是继承,所以无法代理 final 修饰的类或方法。
Spring 如何选择?
-
如果目标对象实现了接口,Spring 默认使用 JDK 动态代理。
-
如果目标对象没有实现接口,Spring 强制使用 CGLIB。
-
(注:在 Spring Boot 2.x 后,默认配置趋向于强制使用 CGLIB,因为它的性能已经优化得很好,且更稳定)。
四、手写一个简易AOP
假设有一个 UserService:
java
public interface UserService {
void login();
}
public class UserServiceImpl implements UserService {
public void login() {
System.out.println("--- 正在执行登录业务逻辑 ---");
}
}
模拟 JDK 动态代理实现 AOP
java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyAopDemo {
public static void main(String[] args) {
// 1. 创建目标对象(房东)
UserService target = new UserServiceImpl();
// 2. 创建代理对象(中介)
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// AOP逻辑:前置通知
System.out.println("[Before] 开启事务/记录日志...");
// 执行真正的业务逻辑
Object result = method.invoke(target, args);
// AOP逻辑:后置通知
System.out.println("[After] 提交事务/清理资源...");
return result;
}
}
);
// 3. 调用代理对象的方法
// 注意:这里调用的是 proxy,而不是 target
proxy.login();
}
}
实际上就是生成代理对象然后去增强。
实际上Spring也差不多是这样实现的,结合IOC的话就是:
-
IOC 创建对象: Spring 扫描类,利用反射实例化 Bean。
-
后置处理器介入 (BeanPostProcessor):
Spring 有一个关键的接口叫 AnnotationAwareAspectJAutoProxyCreator(它是一个 BeanPostProcessor)。
在 Bean 初始化之后,这个处理器会检查:
-
"这个 Bean 匹配上了哪个切点(Pointcut)吗?"
-
"如果有匹配,我就不返回原始对象了,我要创建一个Proxy(代理对象)返回回去。"
-
-
放入容器: 最终放入 Map 单例池的是这个 Proxy 对象。
总结
AOP 并不神秘,它就是一个解耦利器。
-
解决什么问题? 消除重复代码(日志、事务、权限),让业务逻辑更纯粹。
-
核心原理? 动态代理。
-
有接口 -> JDK 代理。
-
没接口 -> CGLIB 代理(子类继承)。
-
-
关键组件? 切面(Aspect)、切点(Pointcut)、通知(Advice)。
一句话总结:
IOC 让对象从"手动 new"变成了"自动注入";
AOP 让代码从"纵向重复"变成了"横向切入"。

