Spring AOP(Aspect-Oriented Programming)是一种通过“横切逻辑”(如日志、安全、事务等)增强业务逻辑的技术。Spring 默认使用 JDK 动态代理 或 CGLIB 来生成代理对象,并通过这些代理对象来织入增强逻辑。
JDK 动态代理的前提是目标类必须实现接口,否则使用 CGLIB。
演示代码:
service层:
@Service
public class HelloServiceImpl implements HelloService {
@Override
public void sayHello(String name) {
System.out.println("Hello, " + name);
}
}
controller层:
@RestController
public class TestController {
@Autowired
private HelloService helloService;
@GetMapping("/hello")
public String hello(@RequestParam String name) {
helloService.sayHello(name);
return "OK";
}
}
切面类:
@Aspect
@Component
public class LogAspect {
// 切点表达式:匹配 service 包下所有类的所有方法
// 环绕通知:方法调用前后都能增强
@Around("execution(* com.saul.service..*(..))")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取当前方法的签名信息,用于日志记录
String method = joinPoint.getSignature().toShortString();
// 记录方法开始执行的时间,用于计算执行耗时
long start = System.currentTimeMillis();
System.out.println("【AOP日志】开始调用方法:" + method);
Object result = joinPoint.proceed(); // 执行原方法
// 记录方法执行结束的时间,用于计算执行耗时
long end = System.currentTimeMillis();
System.out.println("【AOP日志】方法执行完毕:" + method + ",耗时:" + (end - start) + "ms");
return result;
}
}
在这段代码中,HelloServiceImpl implements HelloService
,符合 JDK 动态代理的条件,所以 Spring AOP 会使用 java.lang.reflect.Proxy 来生成代理对象。
我们以以下切面代码为例,深入理解 Spring AOP 的完整执行流程:
@Aspect
@Component
public class LogAspect {
@Around("execution(* com.saul.service..*(..))")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
String method = joinPoint.getSignature().toShortString();
long start = System.currentTimeMillis();
System.out.println("【AOP日志】开始调用方法:" + method);
Object result = joinPoint.proceed(); // 执行目标方法
long end = System.currentTimeMillis();
System.out.println("【AOP日志】方法执行完毕:" + method + ",耗时:" + (end - start) + "ms");
return result;
}
}
Spring 在启动过程中,会扫描到标注了 @Aspect
的切面类(如 LogAspect
),并完成以下工作:
注册 AOP 自动代理创建器(如 AnnotationAwareAspectJAutoProxyCreator
)作为 BeanPostProcessor
;
将切面类中带有增强注解(如 @Around
)的方法封装为 Advisor
(增强器);
将这些增强器最终转化为 MethodInterceptor
,并保存起来。
此时,Spring 会对所有 Bean 进行判断,一旦发现某个 Bean(如 HelloServiceImpl
)的方法匹配切点表达式( @Around("execution(* com.saul.service..*(..))")),就会为它生成代理对象(使用 JDK 动态代理或 CGLIB)。
@Autowired
private HelloService helloService; // 注入的是代理对象,不是原始对象
用户访问 /hello?name=xxx
:
@GetMapping("/hello")
public String hello(@RequestParam String name) {
helloService.sayHello(name); // 实际调用的是代理对象的方法
return "OK";
}
此时,helloService
是一个 JDK 动态代理对象,调用方法会进入其内部的 InvocationHandler
。
Spring 生成的代理对象内部结构可简化理解为:
HelloService proxy = (HelloService) Proxy.newProxyInstance(
HelloService.class.getClassLoader(), //类加载器
new Class[]{HelloService.class}, //代理对象需要实现的接口,和被代理对象保持一致
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// ① 查找与方法匹配的增强器(Advisor)
// ② 将增强器转化为拦截器链(List)
// ③ 触发执行:ReflectiveMethodInvocation.proceed()
return interceptorChain.proceed(); // 启动链式调用
}
});
当执行 interceptorChain.proceed()
时,会依次执行链中的所有增强方法(即切面逻辑)。每个拦截器一般结构如下:
public Object invoke(MethodInvocation invocation) throws Throwable {
// 前置逻辑
Object result = invocation.proceed(); // 调用下一个拦截器或目标方法
// 后置逻辑
return result;
}
如果方法匹配 @Around("execution(* com.saul.service..*(..))")
,就会进入 logExecutionTime()
方法。
在 logExecutionTime
中再次调用 joinPoint.proceed()
,这就是执行目标方法(即原始 sayHello()
)的入口。
Object result = joinPoint.proceed(); // 执行目标方法
这一行才真正进入 HelloServiceImpl.sayHello()
,完成业务逻辑。
执行完毕后,切面方法会记录日志、统计耗时等,然后将结果返回,完成整个调用流程。
阶段 | 执行环节 | 说明 |
---|---|---|
1️⃣ 请求发起 | 用户访问 /hello |
浏览器发送请求到控制器 |
2️⃣ 控制器调用 | TestController.hello() → helloService.sayHello() |
实际调用的是 JDK 动态代理对象的方法 |
3️⃣ 方法拦截 | JDK 动态代理对象执行 InvocationHandler.invoke() |
进入代理逻辑,准备增强处理 |
4️⃣ 构建增强链 | 查找匹配的切面表达式,如 @Around("execution(...)") |
匹配到切面方法,封装成 MethodInterceptor |
5️⃣ 执行切面 | logExecutionTime(joinPoint) 方法执行 |
记录开始时间、打印前置日志 |
6️⃣ 调用原方法 | joinPoint.proceed() |
实际调用目标方法,如 HelloServiceImpl.sayHello() |
7️⃣ 后置增强 | logExecutionTime() 后续逻辑 |
打印耗时日志、收尾工作 |
8️⃣ 返回结果 | 返回 sayHello() 结果 → 控制器返回响应 |
响应回客户端,流程结束 |
java.lang.reflect.Proxy
java.lang.reflect.InvocationHandler
当你调用某个代理对象的方法时,其实是调用了:
InvocationHandler.invoke(proxy, method, args)
Spring 中的 AOP 代理类实现了 InvocationHandler
接口。这个实现内部会:
找到与当前方法匹配的增强(Advice)
将这些增强按顺序封装成 Interceptor Chain(拦截器链)
使用 ReflectiveMethodInvocation
对象执行这些增强,并最终调用目标方法
切面:
@Around("execution(* com.saul.service..*(..))")
public Object logExecutionTime(ProceedingJoinPoint joinPoint)
Spring 会解析这个表达式,匹配 HelloServiceImpl.sayHello()
,并将其织入 logExecutionTime()
中,组成增强链,最终通过动态代理执行。
你可以在 TestController
中打印出 helloService
的真实类型:
System.out.println(helloService.getClass());
你会看到类似这样的输出:
class com.sun.proxy.$Proxy23
这表示使用的是 JDK 动态代理(如果是 CGLIB 会看到 HelloServiceImpl$$EnhancerBySpringCGLIB
)
请求 /hello
↓
TestController.hello(name)
↓
helloService.sayHello(name)
↓
JDK 代理对象 intercept
↓
匹配切面表达式?
↓
Yes → 执行切面逻辑(如 @Around)
↓
调用目标方法 sayHello
↓
执行完成 → 后置增强 → 返回结果
前端发送请求参数:
执行结果: