问题
Spring AOP实现,动态代理
答案
1. 核心概念
Spring AOP的底层是通过动态代理实现的,在运行时为目标对象生成代理对象,在代理对象中织入增强逻辑。
动态代理的两种实现方式:
- JDK动态代理:基于接口,使用Java反射机制
- CGLIB代理:基于继承,使用字节码生成技术
2. JDK动态代理
(1)实现原理
JDK动态代理基于反射机制,通过java.lang.reflect.Proxy类动态生成代理对象。
核心API:
Proxy.newProxyInstance():创建代理对象InvocationHandler:定义代理逻辑
限制:只能代理实现了接口的类。
(2)手动实现示例
// 定义接口
public interface UserService {
void addUser(String name);
String getUser(Long id);
}
// 目标类实现接口
public class UserServiceImpl implements UserService {
@Override
public void addUser(String name) {
System.out.println("添加用户: " + name);
}
@Override
public String getUser(Long id) {
System.out.println("查询用户: " + id);
return "User-" + id;
}
}
// 实现InvocationHandler
public class LogInvocationHandler implements InvocationHandler {
private Object target; // 目标对象
public LogInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置增强
System.out.println("Before: " + method.getName());
// 调用目标方法
Object result = method.invoke(target, args);
// 后置增强
System.out.println("After: " + method.getName());
return result;
}
}
// 测试
public class JdkProxyTest {
public static void main(String[] args) {
// 创建目标对象
UserService target = new UserServiceImpl();
// 创建代理对象
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 类加载器
target.getClass().getInterfaces(), // 代理的接口
new LogInvocationHandler(target) // InvocationHandler
);
// 通过代理对象调用方法
proxy.addUser("张三");
String user = proxy.getUser(1L);
// 输出:
// Before: addUser
// 添加用户: 张三
// After: addUser
// Before: getUser
// 查询用户: 1
// After: getUser
}
}
(3)原理分析
// JDK动态代理生成的代理类(简化版)
public final class $Proxy0 extends Proxy implements UserService {
private static Method m3; // addUser方法
private static Method m4; // getUser方法
static {
try {
m3 = Class.forName("com.example.UserService")
.getMethod("addUser", new Class[]{String.class});
m4 = Class.forName("com.example.UserService")
.getMethod("getUser", new Class[]{Long.class});
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public $Proxy0(InvocationHandler h) {
super(h);
}
@Override
public void addUser(String name) {
try {
// 调用InvocationHandler.invoke()
this.h.invoke(this, m3, new Object[]{name});
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
@Override
public String getUser(Long id) {
try {
return (String) this.h.invoke(this, m4, new Object[]{id});
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
}
调用流程:
// 调用链
proxy.addUser("张三")
→ $Proxy0.addUser("张三")
→ InvocationHandler.invoke(proxy, addUserMethod, ["张三"])
→ method.invoke(target, ["张三"]) // 反射调用目标方法
→ UserServiceImpl.addUser("张三")
(4)查看生成的代理类
public class ProxyClassTest {
public static void main(String[] args) throws Exception {
// JDK 8及以前:设置系统属性,将代理类保存到文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// JDK 9+:使用jdk.proxy.ProxyGenerator.saveGeneratedFiles
System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new LogInvocationHandler(target)
);
proxy.addUser("张三");
// 生成的代理类位于:com/sun/proxy/$Proxy0.class
}
}
3. CGLIB代理
(1)实现原理
CGLIB(Code Generation Library)基于ASM字节码框架,通过继承目标类生成子类代理。
核心API:
Enhancer:代理类生成器MethodInterceptor:方法拦截器
优势:可以代理没有接口的类。
限制:
- 无法代理final类
- 无法代理final方法
- 无法代理private方法
(2)添加依赖
<!-- Spring Boot已包含CGLIB,无需单独引入 -->
<!-- 如果是纯Spring项目,需要添加 -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
(3)手动实现示例
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
// 目标类(无需实现接口)
public class UserService {
public void addUser(String name) {
System.out.println("添加用户: " + name);
}
public String getUser(Long id) {
System.out.println("查询用户: " + id);
return "User-" + id;
}
}
// 实现MethodInterceptor
public class LogMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 前置增强
System.out.println("Before: " + method.getName());
// 调用目标方法(两种方式)
// 方式1:通过父类方法调用(推荐,性能更好)
Object result = proxy.invokeSuper(obj, args);
// 方式2:通过反射调用(不推荐)
// Object result = method.invoke(target, args);
// 后置增强
System.out.println("After: " + method.getName());
return result;
}
}
// 测试
public class CglibProxyTest {
public static void main(String[] args) {
// 创建Enhancer对象
Enhancer enhancer = new Enhancer();
// 设置父类
enhancer.setSuperclass(UserService.class);
// 设置回调
enhancer.setCallback(new LogMethodInterceptor());
// 创建代理对象
UserService proxy = (UserService) enhancer.create();
// 通过代理对象调用方法
proxy.addUser("张三");
String user = proxy.getUser(1L);
// 输出:
// Before: addUser
// 添加用户: 张三
// After: addUser
// Before: getUser
// 查询用户: 1
// After: getUser
}
}
(4)原理分析
// CGLIB生成的代理类(简化版)
public class UserService$$EnhancerByCGLIB$$12345678 extends UserService {
private MethodInterceptor interceptor;
@Override
public void addUser(String name) {
// 调用MethodInterceptor.intercept()
interceptor.intercept(
this, // 代理对象
addUserMethod, // 目标方法
new Object[]{name}, // 方法参数
addUserMethodProxy // 方法代理(用于调用父类方法)
);
}
@Override
public String getUser(Long id) {
return (String) interceptor.intercept(
this,
getUserMethod,
new Object[]{id},
getUserMethodProxy
);
}
// 父类方法的快速访问(FastClass机制)
final void CGLIB$addUser$0(String name) {
super.addUser(name); // 直接调用父类方法
}
}
调用流程:
// 调用链
proxy.addUser("张三")
→ UserService$$EnhancerByCGLIB$$12345678.addUser("张三")
→ MethodInterceptor.intercept(proxy, addUserMethod, ["张三"], addUserMethodProxy)
→ MethodProxy.invokeSuper(proxy, ["张三"])
→ proxy.CGLIB$addUser$0("张三") // FastClass机制,避免反射
→ UserService.addUser("张三")
(5)FastClass机制
CGLIB使用FastClass机制避免反射调用,提高性能。
// 为目标类生成FastClass
public class UserService$$FastClassByCGLIB$$12345678 extends FastClass {
@Override
public Object invoke(int index, Object obj, Object[] args) {
UserService target = (UserService) obj;
switch (index) {
case 0: // addUser方法的索引
target.addUser((String) args[0]);
return null;
case 1: // getUser方法的索引
return target.getUser((Long) args[0]);
default:
throw new IllegalArgumentException("Unknown method index: " + index);
}
}
}
// 直接通过索引调用,避免反射
fastClass.invoke(0, target, new Object[]{"张三"}); // 调用addUser
4. Spring AOP代理选择
(1)代理选择策略
@Configuration
@EnableAspectJAutoProxy // 启用AOP
public class AopConfig {
}
默认策略:
- 目标对象实现了接口 → JDK动态代理
- 目标对象未实现接口 → CGLIB代理
(2)强制使用CGLIB
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true) // 强制CGLIB
public class AopConfig {
}
或在配置文件中:
spring:
aop:
proxy-target-class: true # 强制使用CGLIB
(3)源码分析
// Spring AOP代理创建核心类:DefaultAopProxyFactory
public class DefaultAopProxyFactory implements AopProxyFactory {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// 判断使用JDK代理还是CGLIB代理
if (config.isOptimize() ||
config.isProxyTargetClass() ||
hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class");
}
// 如果目标类是接口或已经是JDK代理,使用JDK代理
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
// 否则使用CGLIB代理
return new ObjenesisCglibAopProxy(config);
} else {
// 有接口,使用JDK代理
return new JdkDynamicAopProxy(config);
}
}
}
5. JDK动态代理 vs CGLIB代理
| 维度 | JDK动态代理 | CGLIB代理 |
|---|---|---|
| 实现方式 | 基于接口(反射) | 基于继承(字节码) |
| 要求 | 必须实现接口 | 无需接口 |
| 代理对象 | 实现接口的代理类 | 目标类的子类 |
| 性能(JDK 8+) | 调用性能接近CGLIB | 创建代理快,调用略慢 |
| 限制 | 只能代理接口方法 | 无法代理final类/方法 |
| Spring默认 | 有接口时使用 | 无接口时使用 |
| 依赖 | JDK自带 | 需要第三方库 |
性能对比
public class PerformanceTest {
public static void main(String[] args) {
int count = 10_000_000;
// 1. 创建代理性能测试
long jdkCreateStart = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
UserService jdkProxy = createJdkProxy();
}
long jdkCreateEnd = System.currentTimeMillis();
System.out.println("JDK创建代理: " + (jdkCreateEnd - jdkCreateStart) + "ms");
long cglibCreateStart = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
UserService cglibProxy = createCglibProxy();
}
long cglibCreateEnd = System.currentTimeMillis();
System.out.println("CGLIB创建代理: " + (cglibCreateEnd - cglibCreateStart) + "ms");
// 2. 方法调用性能测试
UserService jdkProxy = createJdkProxy();
long jdkInvokeStart = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
jdkProxy.getUser(1L);
}
long jdkInvokeEnd = System.currentTimeMillis();
System.out.println("JDK方法调用: " + (jdkInvokeEnd - jdkInvokeStart) + "ms");
UserService cglibProxy = createCglibProxy();
long cglibInvokeStart = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
cglibProxy.getUser(1L);
}
long cglibInvokeEnd = System.currentTimeMillis();
System.out.println("CGLIB方法调用: " + (cglibInvokeEnd - cglibInvokeStart) + "ms");
}
}
// 典型结果(JDK 8+):
// JDK创建代理: 50ms
// CGLIB创建代理: 200ms(慢4倍)
// JDK方法调用: 150ms
// CGLIB方法调用: 160ms(性能接近)
结论:
- JDK 6/7:CGLIB调用性能明显优于JDK代理
- JDK 8+:JDK代理调用性能大幅优化,与CGLIB接近
- 创建性能:JDK代理创建更快
- Spring推荐:优先使用JDK代理(有接口)
6. 实际应用场景
场景1:基于接口设计(推荐)
// 定义接口
public interface UserService {
void addUser(User user);
User getUser(Long id);
}
// 实现类
@Service
public class UserServiceImpl implements UserService {
@Override
@Transactional
public void addUser(User user) {
userDao.save(user);
}
@Override
public User getUser(Long id) {
return userDao.findById(id);
}
}
// Spring使用JDK动态代理
// 代理类:UserService$$Proxy0 implements UserService
场景2:无接口类(使用CGLIB)
@Service
public class OrderService { // 未实现接口
@Transactional
public void createOrder(Order order) {
orderDao.save(order);
}
}
// Spring使用CGLIB代理
// 代理类:OrderService$$EnhancerBySpringCGLIB$$xxx extends OrderService
场景3:检查代理类型
@Service
public class UserService implements UserServiceInterface {
@Autowired
private ApplicationContext context;
public void checkProxyType() {
UserService bean = context.getBean(UserService.class);
// 检查是否是JDK代理
boolean isJdkProxy = Proxy.isProxyClass(bean.getClass());
System.out.println("Is JDK Proxy: " + isJdkProxy);
// 检查是否是CGLIB代理
boolean isCglibProxy = bean.getClass().getName().contains("$$");
System.out.println("Is CGLIB Proxy: " + isCglibProxy);
// 输出代理类名
System.out.println("Proxy Class: " + bean.getClass().getName());
// JDK: com.sun.proxy.$Proxy123
// CGLIB: com.example.UserService$$EnhancerBySpringCGLIB$$12345678
}
}
7. 代理失效问题
(1)JDK代理失效
// 接口
public interface UserService {
void method();
}
// 实现类添加了接口没有的方法
@Service
public class UserServiceImpl implements UserService {
@Override
public void method() {
System.out.println("Interface method");
}
// 这个方法不在接口中
@Transactional
public void extraMethod() {
System.out.println("Extra method");
}
}
// 使用
@Autowired
private UserService userService; // 注入接口类型
public void test() {
userService.method(); // 正常
// userService.extraMethod(); // 编译错误,接口没有此方法
// 如果强制转换
((UserServiceImpl) userService).extraMethod(); // ClassCastException
}
解决方案:注入实现类类型
@Autowired
private UserServiceImpl userService; // 注入实现类类型
public void test() {
userService.extraMethod(); // 正常(Spring会使用CGLIB代理)
}
(2)CGLIB代理限制
@Service
public final class FinalService { // final类,无法被CGLIB代理
@Transactional
public void method() {
// AOP失效
}
}
@Service
public class UserService {
@Transactional
public final void finalMethod() { // final方法,无法被重写
// AOP失效
}
}
8. 面试答题要点
标准回答结构:
-
核心概念:Spring AOP基于动态代理实现,有JDK动态代理和CGLIB代理两种方式
- JDK动态代理:
- 基于接口和反射
- 使用Proxy和InvocationHandler
- 只能代理实现了接口的类
- CGLIB代理:
- 基于继承和字节码生成
- 使用Enhancer和MethodInterceptor
- 可以代理没有接口的类
- 无法代理final类/方法
- 代理选择:
- 有接口 → JDK代理
- 无接口 → CGLIB代理
- 可强制使用CGLIB
- 性能对比:
- JDK 8+性能接近
- JDK代理创建更快
- CGLIB有FastClass优化
加分点:
- 了解代理类生成机制
- 知道FastClass优化原理
- 能说明Spring的代理选择策略
- 了解两种代理的限制和适用场景
- 能手写简单的动态代理示例
9. 总结
核心要点:
| 维度 | JDK动态代理 | CGLIB代理 |
|---|---|---|
| 原理 | 反射 + 接口 | 字节码 + 继承 |
| 前提 | 必须有接口 | 无需接口 |
| 限制 | 只代理接口方法 | 无法代理final |
| 性能 | 创建快,调用快(JDK 8+) | 创建慢,调用快 |
| 使用 | Spring默认(有接口) | Spring默认(无接口) |
记忆口诀:
- 动态代理两方式,JDK和CGLIB
- JDK基于接口,反射来实现
- CGLIB基于继承,字节码生成
- 有接口用JDK,无接口用CGLIB
- final类和方法,CGLIB代理不了
- FastClass优化好,避免反射慢
使用建议:
- 优先设计接口:使用JDK代理
- 无接口场景:使用CGLIB代理
- 避免使用final:保证代理生效
- 注入接口类型:避免类型转换问题
理解Spring AOP的动态代理实现原理,是掌握AOP技术和排查代理问题的关键,也是Spring面试的高频考点。