之前写过一篇jdk代理的笔记:Java代理的基本用法_小楼夜听雨的博客-优快云博客
今天补充cglib代理相关的一些笔记。
问:动态代理与静态代理最大的区别是什么?
答:代理类的创建时间不同。
静态代理在编码时期,需要硬编码实现对应的静态代理类,随后进入编译阶段生成对应的字节码,不够灵活。
动态代理是在运行的过程中,生成字节码文件,加载进内存,更加的灵活。
(感谢评论区网友指正:动态代理并不是将生成的字节码文件加载进内存,而是直接在内存中生成字节码文件,并且默认是不会保存到磁盘上的)
那么jdk代理与cglib代理有什么区别呢?我们可以带着这个疑问,来写一个简单的cglib demo。
导入依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>3.3.0</version>
</dependency>
测试类
public interface Animal {
String getName();
}
实现类
public class Dog implements Animal{
@Override
public String getName() {
System.out.println("dog。。。。。。");
return "dog";
}
}
代理对象创建工具
public class CglibProxy {
public static Object getProxy(Class<?> clazz) {
//利用cglib自带的调试工具,将生成的代理类字节码文件输出到指定文件夹
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E:\\personcode\\proxy-learn\\cglibproxy");
Enhancer enhancer = new Enhancer();
//设置类加载器
enhancer.setClassLoader(clazz.getClassLoader());
//设置被代理类
enhancer.setSuperclass(clazz);
//设置方法拦截器
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("方法调用前");
Object result = methodProxy.invokeSuper(obj, args);
System.out.println("方法调用后");
return result;
}
});
return enhancer.create();
}
}
测试
public class MainTest {
public static void main(String[] args) {
Animal animal = (Animal) CglibProxy.getProxy(Dog.class);
animal.getName();
}
}
输出
方法调用前
dog。。。。。。
方法调用后
分析
从代码中可以看出,Enhancer类是总线类,代理对象的创建过程都是围绕Enhancer进行的。
Enhancer是对外的窗口,提供调用其核心功能(主要是代理对象的创建和使用)的api,有兴趣的可以看下源码。我这里重点看看生成的代理对象。
生成的文件有三个,第一个就是代理对象
其命名也是有规则的
Dog$$EnhancerByCGLIB$$259018ca
Dog是原对象的类名,EnhancerByCGLIB固定,后面是随机字符串。
代理对象直接继承原对象,回想一下jdk代理对象是如何和原对象产生联系的(提示,实现接口)。
j
cglib选择通过继承的方式来增强原对象,而在java中,final修饰的类不能被继承,cglib也因此不能代理被final修饰的类。
jdk代理继承了Proxy类,在Java不能多继承的前提下,只能实现接口。从这一点上来看,cglib就业更广,毕竟很多时候,强行实现一个接口有点累赘。
但其实不管是cglib还是jdk代理,都是为了增强被代理类。“增强”,意味着原代理类的功能还是要用的。
还记得在jdk代理中,我们是通过反射来调用原对象的方法的。cglib代理中,则完全不一样。
FastClass是对原生class类的一个包装。
这里涉及一个index的概念,cglib把原对象的method全部分装在一个数组里,代理对象可以通过方法名、方法的参数来定位到原对象的某个方法,从而直接调用该方法。
在jdk代理中,代理对象则通过反射来完成对原对象的方法调用,亦是一大区别。
性能对比
jdk动态代理的代理对象创建速度比较快,cglib比较慢,因为在创建代理对象的过程中需要操作字节码(ASM)。
但是cglib不强制要求对象实现接口,比较通用点,使用起来也较为便捷。
Spring中默认使用jdk代理,在遇到没有实现接口的时候,会使用cglib代理。
我在网上看到过一个争议,Spring中默认使用的究竟是jdk代理还是cglib代理?
最后看到一个网友做了测试,Spring确实默认用jdk代理,没办法的时候才选择cglib,但是SpringBoot不太一样,默认是开启了cglib代理。
地址:https://zhuanlan.zhihu.com/p/89057894
cglib api
cglib有很多使用的功能,具体的api可以参考下面的博客:
CGLIB(Code Generation Library)详解_小楼一夜听春雨-优快云博客
如有错误,欢迎批评指正!