哈尔滨住房建设发展集团网站百度怎么发免费广告
1 已有JDK Dynamic Proxy,为什么还要CGLIB?
JDK Dynamic Proxy 和 CGLib 是两种常用的代理技术,它们各自有不同的适用场景和局限性。在已经有了 JDK Dynamic Proxy 的情况下,还需要 CGLib 的原因:
1.1. 类 vs 接口
JDK Dynamic Proxy 只能代理实现了接口的类。
它通过 java.lang.reflect.Proxy 类生成代理对象,要求目标类必须实现一个或多个接口。
CGLib 可以代理没有实现接口的类。
它通过字节码操作(使用 ASM 库)动态生成目标类的子类来实现代理。
1.2. 功能限制
JDK Dynamic Proxy 无法代理类中的私有方法、静态方法或 final 方法。
它只能代理接口中声明的方法。
CGLib 可以代理类中的非私有方法(包括 protected 和 public 方法),并且支持对类本身进行增强。
1.3. 字节码增强需求
CGLib 提供了更强大的字节码操作能力,适用于需要深度修改类行为的场景。
例如,AOP 框架中可能需要拦截构造函数调用或修改类的内部逻辑,这在 JDK Dynamic Proxy 中是无法实现的。
2 怎样使用CGLIB实现动态代理?
CGLIB(Code Generation Library)是一个强大的、高性能的代码生成库,它广泛应用于AOP框架中,如Spring AOP。CGLIB通过生成一个被代理类的子类来实现代理,从而避免了Java代理的接口限制。以下是使用CGLIB实现动态代理的基本步骤:
2.1添加依赖
如果你使用的是Maven项目,需要在pom.xml中添加CGLIB的依赖:
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version></dependency>
2.2创建被代理类
创建一个普通的Java类,这个类的方法将被代理。这里使用前面讲过的示例代码: BusinessCalculator.
public class BusinessCalculator implements Calculator {@Overridepublic Long add(Integer a, Integer b) {long result = 0;for(int i=0; i<100000000; i++) {result += i + a + b;}return result;}@Overridepublic Long subtract(Integer a, Integer b) {long result = 0;for(int i=0; i<100000000; i++) {result += i + a - b;}return result;}@Overridepublic Long multiply(Integer a, Integer b) {long result = 0;for(int i=0; i<100000000; i++) {result += i + a * b;}return result;}@Overridepublic Long divide(Integer a, Integer b) {long result = 0;for(int i=0; i<100000000; i++) {result += i + a / b;}return result;}
}
2.3实现MethodInterceptor接口
创建一个拦截器类,实现net.sf.cglib.proxy.MethodInterceptor接口,并重写intercept方法
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;public class BusinessCalculatorInterceptor implements MethodInterceptor {@Overridepublic 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;}
}
2.4. 创建代理对象
使用CGLIB的Enhancer类创建目标类的代理对象。
示例代码如下:
import net.sf.cglib.proxy.Enhancer;public class CglibProxyDemo {public static void main(String[] args) {// 创建Enhancer对象Enhancer enhancer = new Enhancer();// 设置目标类enhancer.setSuperclass(BusinessCalculator.class);// 设置回调函数enhancer.setCallback(new BusinessCalculatorInterceptor());// 创建代理对象BusinessCalculator proxyInstance = (BusinessCalculator) enhancer.create();// 调用代理对象的方法System.out.println("Result of add: " + proxyInstance.add(1, 2));System.out.println("Result of subtract: " + proxyInstance.subtract(5, 3));System.out.println("Result of multiply: " + proxyInstance.multiply(6, 7));System.out.println("Result of divide: " + proxyInstance.divide(8, 9));}}
注意事项
目标类不能为final:CGLIB通过生成目标类的子类来实现动态代理,因此目标类不能声明为final。
性能开销:CGLIB在运行时生成字节码,可能会带来一定的性能开销。
通过以上步骤,您可以成功使用CGLIB实现动态代理。
2.5 运行效果
我的运行环境是JDK17, 运行上面代码时抛出异常:
Exception in thread "main" java.lang.ExceptionInInitializerErrorat org.derek.CglibProxyDemo.main(CglibProxyDemo.java:9)
Caused by: net.sf.cglib.core.CodeGenerationException: java.lang.reflect.InaccessibleObjectException-->Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @4f2410acat net.sf.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:464)at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:339)at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:96)at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:94)at net.sf.cglib.core.internal.LoadingCache$2.call(LoadingCache.java:54)at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)at net.sf.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:61)at net.sf.cglib.core.internal.LoadingCache.get(LoadingCache.java:34)at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:119)at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:294)at net.sf.cglib.core.KeyFactory$Generator.create(KeyFactory.java:221)at net.sf.cglib.core.KeyFactory.create(KeyFactory.java:174)at net.sf.cglib.core.KeyFactory.create(KeyFactory.java:153)at net.sf.cglib.proxy.Enhancer.<clinit>(Enhancer.java:73)... 1 more
这个错误是由于 Java 9 及以上版本引入的模块化系统(Java Platform Module System, JPMS)导致的。模块化系统对类的访问进行了严格的限制,而CGLIB尝试通过反射访问 java.lang
包中的类或方法,从而导致 java.lang.ClassFormatError。
解决方案:
-
IntelliJ IDEA:
-
打开运行配置(Run Configurations)。
-
在 VM options 中添加
--add-opens java.base/java.lang=ALL-UNNAMED
。
-
添加上面的VM 参数后,运行效果如下:
"C:\Program Files\Java\jdk-17\bin\java.exe" --add-opens java.base/java.lang=ALL-UNNAMED " org.derek.CglibProxyDemo
Before method: add
After method: add
Result of add: 5000000250000000
Before method: subtract
After method: subtract
Result of subtract: 5000000150000000
Before method: multiply
After method: multiply
Result of multiply: 5000004150000000
Before method: divide
After method: divide
Result of divide: 4999999950000000
3 CGLIB 实现动态代理的原理?
CGLIB 实现动态代理的原理主要基于字节码生成技术。以下是其核心原理:
3.1. 子类生成
CGLIB 通过在运行时动态生成目标类的子类来实现代理。它会继承目标类,并重写其中的所有非 final 方法。
继承机制:CGLIB 创建的目标类的子类会覆盖父类的方法,从而可以在方法调用前后插入自定义逻辑。
限制条件:目标类不能是 final 类型,且方法不能是 final 或 static,因为这些方法无法被子类覆盖。
3.2. 拦截器回调
CGLIB 使用回调机制(如 MethodInterceptor)来拦截目标方法的调用。当代理对象的方法被调用时,实际执行的是子类中重写的方法,而该方法会通过回调机制将控制权交给拦截器。
核心接口:MethodInterceptor 是 CGLIB 提供的核心接口,开发者需要实现该接口的 intercept 方法。
拦截逻辑:在 intercept 方法中,可以添加方法调用前后的逻辑,并通过MethodProxy.invokeSuper 调用原始方法。
3.3. 字节码生成
CGLIB 基于 ASM(一个 Java 字节码操作框架)生成字节码。它会在运行时动态创建一个新的类,该类继承了目标类并实现了所需的代理逻辑。
字节码操作:CGLIB 会解析目标类的字节码结构,生成一个新的子类字节码,并加载到 JVM 中。
性能优化:由于字节码生成和加载的过程较为复杂,CGLIB 的初始化开销较大,但一旦生成完成,运行时性能较高。
3.4. Enhancer 工具类
CGLIB 提供了 Enhancer 类作为核心工具,用于创建代理对象。
设置父类:通过 enhancer.setSuperclass(Class) 指定目标类。
设置回调:通过 enhancer.setCallback(Callback) 设置拦截器。
创建代理:调用 enhancer.create() 方法生成代理对象
3.5. 方法调用流程
以下是 CGLIB 动态代理的方法调用流程:
用户调用代理对象的方法。
代理对象(实际上是目标类的子类)拦截该方法调用。
调用 MethodInterceptor.intercept 方法,执行拦截逻辑。
在拦截器中通过 MethodProxy.invokeSuper 调用目标类的原始方法。
返回结果给用户。
3.6 示例代码说明
结合上下文中的 BusinessCalculator 类的add方法:
public class BusinessCalculator {public Long add(Integer a, Integer b) {long result = 0;for (int i = 0; i < 100000000; i++) {result += i + a + b;}return result;}
}
GLIB 会生成一个 BusinessCalculator 的子类,
BusinessCalculator$$EnhancerByCGLIB$$9b8a9c45
子类中包含下面的代码:
public final Long add(Integer var1, Integer var2) {MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;if (var10000 == null) {CGLIB$BIND_CALLBACKS(this);var10000 = this.CGLIB$CALLBACK_0;}return var10000 != null ? (Long)var10000.intercept(this, CGLIB$add$0$Method, new Object[]{var1, var2}, CGLIB$add$0$Proxy) : super.add(var1, var2);}
子类会覆盖 add 方法,并在方法内部调用拦截器的 intercept 方法。
拦截器中可以通过 MethodProxy.invokeSuper 调用原始的 add 方法。
怎样查看所有生成的源文件?
// 启用CGLIB调试模式,将生成的字节码文件保存到指定目录 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "net\\cglib");
感兴趣的童鞋,可以添加上面的代码,查看生成的类的源文件。结构如下:
总结
CGLIB 的核心原理是通过字节码生成技术动态创建目标类的子类,并利用回调机制拦截方法调用。相比 JDK 动态代理(基于接口),CGLIB 更适合对没有实现接口的类进行代理,但在性能和灵活性上各有优劣。