CGLIB深度解析
CGLIB动态代理技术深度解析
目录
引言
在Java应用开发中,动态代理是一种强大的技术,广泛应用于框架开发、AOP实现、性能监控等场景。CGLIB(Code Generation Library)作为一种主流的动态代理技术,因其强大的功能和高性能而备受关注。与JDK动态代理只能代理接口不同,CGLIB可以代理普通类,这使其在Spring等框架中扮演着不可替代的角色。本文将深入探讨CGLIB的工作原理、使用方法及其在Spring框架中的应用,帮助读者全面理解这一重要技术。
基本概念
什么是CGLIB
CGLIB(Code Generation Library)是一个强大的、高性能的代码生成库,主要用于在运行时动态生成和修改Java字节码。它是一种基于ASM(Java字节码操作和分析框架)的字节码生成工具,可以在运行时扩展Java类并实现接口。
// CGLIB依赖引入
// Maven
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
// Gradle
implementation 'cglib:cglib:3.3.0'
解析:
- CGLIB不是Java标准库的一部分,需要单独引入依赖
- 在Spring框架中,通常不需要显式引入,因为Spring Core已经包含了CGLIB
- 最新版本的Spring使用的是重新打包的CGLIB(
spring-core
模块中的org.springframework.cglib
)
动态代理技术对比
Java中主要有两种动态代理技术:JDK动态代理和CGLIB动态代理。
特性 | JDK动态代理 | CGLIB动态代理 |
---|---|---|
实现方式 | 基于接口 | 基于类继承 |
代理对象要求 | 必须实现接口 | 可以是普通类(非final) |
性能 | JDK8后性能已大幅提升 | 一般较好(特别是方法调用次数多时) |
是否JDK内置 | 是(java.lang.reflect.Proxy ) | 否(需要额外依赖) |
生成代理的时机 | 运行时 | 运行时 |
核心API | InvocationHandler | MethodInterceptor |
// JDK动态代理示例
interface UserService {
void createUser(String name);
}
// 代理处理器
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method: " + method.getName());
Object result = method.invoke(realService, args);
System.out.println("After method: " + method.getName());
return result;
}
};
// 创建代理
UserService proxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class<?>[] { UserService.class },
handler
);
解析:
- JDK动态代理只能代理接口,代理类实现了指定的接口
- CGLIB可以代理普通类,代理类是目标类的子类
- 在Spring中,当Bean实现了接口时优先使用JDK动态代理,否则使用CGLIB
CGLIB工作原理
字节码生成
CGLIB通过继承的方式实现代理,它会动态生成一个目标类的子类,并在子类中重写父类的方法,从而实现代理功能。
┌─────────────┐
│ TargetClass │
└─────────┬───┘
│
│ extends
▼
┌───────────────────────┐
│ TargetClass$$EnhancerByCGLIB │
│ - MethodInterceptor field │
└───────────────────────┘
解析:
- CGLIB使用ASM框架在运行时动态生成字节码
- 生成的代理类是目标类的子类,命名通常为
原类名$$EnhancerByCGLIB$$随机字符串
- 代理类重写了父类的所有非final方法
方法拦截机制
CGLIB通过MethodInterceptor
接口实现方法拦截,当调用代理对象的方法时,会触发拦截器的intercept
方法。
public interface MethodInterceptor extends Callback {
Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable;
}
参数说明:
obj
: 代理对象method
: 被拦截的方法args
: 方法参数proxy
: 方法代理,可用于调用原始方法
在拦截器中,可以:
- 在原始方法调用前执行逻辑
- 决定是否调用原始方法
- 在原始方法调用后执行逻辑
- 修改返回值
核心API
CGLIB的核心API主要包括:
-
Enhancer:主要类,用于创建代理对象
Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(TargetClass.class); enhancer.setCallback(methodInterceptor); TargetClass proxy = (TargetClass) enhancer.create();
-
MethodInterceptor:方法拦截器接口
MethodInterceptor interceptor = new MethodInterceptor() { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { // 前置处理 Object result = proxy.invokeSuper(obj, args); // 调用原始方法 // 后置处理 return result; } };
-
CallbackFilter:回调过滤器,决定对不同方法使用不同的回调
CallbackFilter filter = new CallbackFilter() { @Override public int accept(Method method) { if (method.getName().equals("targetMethod")) { return 0; // 使用callbacks[0]处理 } return 1; // 使用callbacks[1]处理 } }; enhancer.setCallbackFilter(filter);
-
MethodProxy:方法代理,提供了两种调用方式
invokeSuper(obj, args)
:调用原始方法(父类方法)invoke(obj, args)
:调用代理方法,容易导致递归调用
解析:
Enhancer
是创建代理的入口点MethodInterceptor
定义了方法拦截的逻辑- 使用
invokeSuper
而非invoke
可避免递归调用 - 可以为不同方法设置不同的拦截器
CGLIB在Spring中的应用
@Configuration类代理
Spring使用CGLIB代理@Configuration
类,以确保@Bean
方法的单例行为。
@Configuration
public class AppConfig {
@Bean
public ServiceA serviceA() {
return new ServiceA();
}
@Bean
public ServiceB serviceB() {
// 这里调用serviceA(),但实际上会返回容器中已存在的bean
return new ServiceB(serviceA());
}
}
解析:
- Spring会为
AppConfig
创建CGLIB代理 - 当
serviceB()
内部调用serviceA()
时,代理会拦截调用 - 拦截器会检查容器中是否已存在
serviceA
bean - 如果存在,直接返回容器中的bean,而不执行
serviceA()
方法 - 这确保了即使在方法间相互调用时,也能保持bean的单例特性
事务管理
Spring的声明式事务管理(@Transactional
)在类没有实现接口时,使用CGLIB创建代理:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional
public void createUser(User user) {
userRepository.save(user);
sendWelcomeEmail(user); // 可能抛出异常
}
private void sendWelcomeEmail(User user) {
// 发送邮件逻辑
}
}
解析:
- Spring会为
UserService
创建CGLIB代理 - 代理拦截
createUser
方法调用 - 在方法执行前开启事务
- 如果方法执行成功,提交事务
- 如果方法抛出异常,回滚事务
方法注入
当一个单例bean需要依赖一个原型bean时,Spring可以使用CGLIB实现方法注入(lookup method injection):
@Component
public abstract class SingletonBean {
@Lookup
public abstract PrototypeBean getPrototypeBean();
public void doSomething() {
PrototypeBean bean = getPrototypeBean();
// 使用新创建的原型bean
bean.process();
}
}
解析:
- Spring使用CGLIB生成
SingletonBean
的子类 - 实现抽象方法
getPrototypeBean()
- 实现逻辑是从Spring容器中获取一个新的
PrototypeBean
实例 - 每次调用
getPrototypeBean()
都会返回一个新的实例
CGLIB使用实例
基本代理示例
下面是一个使用CGLIB创建代理的基本示例:
// 目标类
public class TargetClass {
public String sayHello(String name) {
return "Hello, " + name;
}
public final String finalMethod() {
return "This is a final method";
}
}
// 创建代理
public class CglibProxyExample {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(TargetClass.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before method: " + method.getName());
Object result = proxy.invokeSuper(obj, args);
System.out.println("After method: " + method.getName());
return result;
}
});
TargetClass proxy = (TargetClass) enhancer.create();
System.out.println(proxy.sayHello("World")); // 被拦截
System.out.println(proxy.finalMethod()); // 不被拦截,因为是final方法
}
}
解析:
- 创建
Enhancer
对象,设置目标类和回调 sayHello
方法被拦截,执行前后会打印日志finalMethod
是final方法,不会被重写,因此不会被拦截
回调过滤器
使用CallbackFilter
可以为不同的方法设置不同的拦截器:
public class CallbackFilterExample {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(TargetClass.class);
// 创建多个回调
Callback[] callbacks = new Callback[] {
// 第一个回调:记录日志
new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Logging before: " + method.getName());
Object result = proxy.invokeSuper(obj, args);
System.out.println("Logging after: " + method.getName());
return result;
}
},
// 第二个回调:性能监控
new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
long start = System.currentTimeMillis();
Object result = proxy.invokeSuper(obj, args);
long end = System.currentTimeMillis();
System.out.println("Method " + method.getName() + " took " + (end - start) + "ms");
return result;
}
},
// 第三个回调:不做任何处理
NoOp.INSTANCE
};
// 设置回调过滤器
enhancer.setCallbackFilter(new CallbackFilter() {
@Override
public int accept(Method method) {
if (method.getName().equals("sayHello")) {
return 0; // 使用日志回调
} else if (method.getName().equals("calculate")) {
return 1; // 使用性能监控回调
}
return 2; // 其他方法使用NoOp回调
}
});
enhancer.setCallbacks(callbacks);
TargetClass proxy = (TargetClass) enhancer.create();
// 测试不同方法
proxy.sayHello("World"); // 使用日志回调
proxy.calculate(10, 20); // 使用性能监控回调
proxy.someOtherMethod(); // 使用NoOp回调
}
}
解析:
- 创建多个不同功能的回调
- 使用
CallbackFilter
根据方法名选择不同的回调 NoOp.INSTANCE
是一个特殊的回调,不做任何处理,直接调用原始方法
多重回调
CGLIB支持组合多个回调,实现更复杂的代理逻辑:
public class MultipleCallbackExample {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(TargetClass.class);
// 创建方法拦截器
MethodInterceptor loggingInterceptor = new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Logging before method");
Object result = proxy.invokeSuper(obj, args);
System.out.println("Logging after method");
return result;
}
};
// 创建延迟加载器
LazyLoader lazyLoader = new LazyLoader() {
@Override
public Object loadObject() throws Exception {
System.out.println("Lazy loading object");
return new TargetClass();
}
};
// 设置回调
enhancer.setCallback(new Dispatcher() {
@Override
public Object loadObject() throws Exception {
// 根据条件返回不同的回调
if (shouldLog()) {
return loggingInterceptor;
} else {
return lazyLoader;
}
}
});
TargetClass proxy = (TargetClass) enhancer.create();
proxy.sayHello("World");
}
private static boolean shouldLog() {
// 根据条件决定是否记录日志
return true;
}
}
解析:
Dispatcher
接口用于动态决定使用哪个回调LazyLoader
接口用于延迟创建目标对象- 可以根据运行时条件选择不同的回调策略
CGLIB的优缺点与限制
优势分析
CGLIB作为动态代理技术有以下优点:
- 无接口代理:可以代理没有实现任何接口的普通类
- 高性能:在方法调用次数多的场景下,性能通常优于JDK动态代理
- 灵活性:提供了丰富的API,支持多种代理方式和回调类型
- 广泛应用:被Spring、Hibernate等主流框架采用
局限性
CGLIB也存在一些限制:
-
不能代理final类:因为CGLIB是通过继承实现的,而final类不能被继承
public final class FinalClass { public void method() { // 这个类不能被CGLIB代理 } }
-
不能代理final方法:final方法不能被重写,因此无法被代理
public class SomeClass { public final void finalMethod() { // 这个方法不能被CGLIB代理 } }
-
构造函数调用:代理类会调用被代理类的构造函数,可能导致一些意外行为
public class TargetClass { public TargetClass() { System.out.println("Constructor called"); // 这里的代码会在创建代理时执行 } }
-
可能的性能问题:生成字节码和加载类的过程可能会带来一定的性能开销
性能考量
在选择使用CGLIB时,需要考虑以下性能因素:
- 首次生成代理的开销:CGLIB首次生成代理类时需要动态生成字节码,可能较慢
- 方法调用性能:CGLIB代理方法调用通常比JDK动态代理快
- 内存占用:CGLIB会生成新的类,可能增加方法区内存占用
- 类加载器问题:在复杂的类加载器环境中可能出现问题
// 性能测试示例
public class ProxyPerformanceTest {
public static void main(String[] args) {
// 创建JDK代理
UserService jdkProxy = createJdkProxy();
// 创建CGLIB代理
UserServiceImpl cglibProxy = createCglibProxy();
// 测试JDK代理性能
long start = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
jdkProxy.findById(i);
}
long end = System.currentTimeMillis();
System.out.println("JDK Proxy: " + (end - start) + "ms");
// 测试CGLIB代理性能
start = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
cglibProxy.findById(i);
}
end = System.currentTimeMillis();
System.out.println("CGLIB Proxy: " + (end - start) + "ms");
}
}
解析:
- 在JDK 8之前,CGLIB通常比JDK动态代理性能好
- 在JDK 8及以后,JDK动态代理性能得到了显著提升
- 实际性能取决于具体场景和JDK版本
总结
问:CGLIB与JDK动态代理的区别是什么?
关键知识点
CGLIB与JDK动态代理的主要区别可以从三个方面概括:
-
实现原理不同:
- CGLIB通过继承实现代理,在运行时生成目标类的子类
- JDK动态代理通过接口实现,生成实现同样接口的新类
- CGLIB使用ASM库操作字节码;JDK动态代理使用反射机制
-
代理条件不同:
- CGLIB可以代理普通类,不要求目标类实现接口,但不能代理final类和方法
- JDK动态代理只能代理接口,要求目标类必须实现至少一个接口
- CGLIB会调用目标类构造函数;JDK动态代理不会调用目标类构造函数
-
Spring中的应用:
JDK动态代理- Spring AOP默认首选JDK动态代理
- 当Bean实现了接口时,Spring会使用JDK动态代理
- 代理对象实现与目标对象相同的接口
@Service
public class UserServiceImpl implements UserService {
// Spring会使用JDK动态代理
}
CGLIB动态代理
- 当Bean没有实现任何接口时,Spring会使用CGLIB
- 可以通过配置强制使用CGLIB:@EnableAspectJAutoProxy(proxyTargetClass = true)
- 代理对象是目标对象的子类
@Service
public class UserService {
// 没有实现接口,Spring会使用CGLIB
}
应用
- @Configuration类代理:Spring使用CGLIB代理@Configuration类,确保@Bean方法返回单例
- 事务管理:当@Transactional标注的类没有实现接口时,使用CGLIB创建代理
- 方法注入:使用CGLIB实现@Lookup方法,解决单例依赖原型的问题
- AOP实现:当目标对象没有实现接口时,Spring AOP使用CGLIB创建代理
- Spring默认选择策略:有接口优先用JDK动态代理,无接口时用CGLIB
eg:
深入解析
-
工作原理:
- CGLIB使用ASM库在运行时生成字节码
- 通过继承目标类创建子类
- 重写非final方法,在方法中加入拦截逻辑
- 使用
MethodInterceptor
接口定义拦截行为
-
追问:
- 为什么Spring默认使用JDK动态代理:接口代理更符合面向接口编程原则
- Spring如何选择代理方式:如果目标类实现了接口,优先使用JDK动态代理;否则使用CGLIB
- CGLIB的局限性:不能代理final类和方法,可能有构造函数调用问题
- 如何强制Spring使用CGLIB:设置
spring.aop.proxy-target-class=true
或@EnableAspectJAutoProxy(proxyTargetClass = true)
-
建议:
- 优先使用基于接口的设计,便于使用JDK动态代理
- 避免在被代理类中使用final修饰符
- 注意代理类构造函数的副作用
- 在性能关键场景下进行测试,选择合适的代理方式