动态代理
动态代理
前言#
代理模式是一种设计模式,能够使得在不修改源目标的前提下,额外扩展源目标的功能。即通过访问源目标的代理类,再由代理类去访问源目标。这样一来,要扩展功能,就无需修改源目标的代码了。只需要在代理类上增加就可以了。
其实代理模式的核心思想就是这么简单,在java中,代理又分静态代理和动态代理2种,其中动态代理根据不同实现又区分基于接口的的动态代理和基于子类的动态代理。
其中静态代理由于比较简单,面试中也没啥问的,在代理模式一块,问的最多就是动态代理,而且动态代理也是spring aop的核心思想,spring其他很多功能也是通过动态代理来实现的,比如拦截器,事务控制等。
熟练掌握动态代理技术,能让你业务代码更加精简而优雅。如果你需要写一些中间件的话,那动态代理技术更是必不可少的技能包。
那此篇文章就带大家一窥动态代理的所有细节吧。
静态代理#
在说动态代理前,还是先说说静态代理。
所谓静态代理,就是通过声明一个明确的代理类来访问源对象。
我们有2个接口,Person和Animal。每个接口各有2个实现类,UML如下图:
每个实现类中代码都差不多一致,用Student来举例(其他类和这个几乎一模一样)
public class Student implements Person{
private String name;
public Student() {
}
public Student(String name) {
this.name = name;
}
@Override
public void wakeup() {
System.out.println(StrUtil.format("学生[{}]早晨醒来啦",name));
}
@Override
public void sleep() {
System.out.println(StrUtil.format("学生[{}]晚上睡觉啦",name));
}
}
假设我们现在要做一件事,就是在所有的实现类调用wakeup()
前增加一行输出早安~
,调用sleep()
前增加一行输出晚安~
。那我们只需要编写2个代理类PersonProxy
和AnimalProxy
:
PersonProxy:
public class PersonProxy implements Person {
private Person person;
public PersonProxy(Person person) {
this.person = person;
}
@Override
public void wakeup() {
System.out.println("早安~");
person.wakeup();
}
@Override
public void sleep() {
System.out.println("晚安~");
person.sleep();
}
}
AnimalProxy:
public class AnimalProxy implements Animal {
private Animal animal;
public AnimalProxy(Animal animal) {
this.animal = animal;
}
@Override
public void wakeup() {
System.out.println("早安~");
animal.wakeup();
}
@Override
public void sleep() {
System.out.println("晚安~");
animal.sleep();
}
}
最终执行代码为:
public static void main(String[] args) {
Person student = new Student("张三");
PersonProxy studentProxy = new PersonProxy(student);
studentProxy.wakeup();
studentProxy.sleep();
Animal dog = new Dog("旺旺");
AnimalProxy dogProxy = new AnimalProxy(dog);
dogProxy.wakeup();
dogProxy.sleep();
}
输出:
早安~
学生[张三]早晨醒来啦
晚安~
学生[张三]晚上睡觉啦
早安~
小狗[旺旺]早晨醒来啦
晚安~~
小狗[旺旺]晚上睡觉啦
结论:
静态代理的代码相信已经不用多说了,代码非常简单易懂。这里用了2个代理类,分别代理了Person
和Animal
接口。
这种模式虽然好理解,但是缺点也很明显:
- 会存在大量的冗余的代理类,这里演示了2个接口,如果有10个接口,就必须定义10个代理类。
- 不易维护,一旦接口更改,代理类和目标类都需要更改。
JDK动态代理#
动态代理,通俗点说就是:无需声明式的创建java代理类,而是在运行过程中生成"虚拟"的代理类,被ClassLoader加载。从而避免了静态代理那样需要声明大量的代理类。
JDK从1.3版本就开始支持动态代理类的创建。主要核心类只有2个:java.lang.reflect.Proxy
和java.lang.reflect.InvocationHandler
。
还是前面那个例子,用JDK动态代理类去实现的代码如下:
创建一个JdkProxy类,用于统一代理:
public class JdkProxy implements InvocationHandler {
private Object bean;
public JdkProxy(Object bean) {
this.bean = bean;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (methodName.equals("wakeup")){
System.out.println("早安~~~");
}else if(methodName.equals("sleep")){
System.out.println("晚安~~~");
}
return method.invoke(bean, args);
}
}
执行代码:
public static void main(String[] args) {
JdkProxy proxy = new JdkProxy(new Student("张三"));
Person student = (Person) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Person.class}, proxy);
student.wakeup();
student.sleep();
proxy = new JdkProxy(new Doctor("王教授"));
Person doctor = (Person) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Person.class}, proxy);
doctor.wakeup();
doctor.sleep();
proxy = new JdkProxy(new Dog("旺旺"));
Animal dog = (Animal) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Animal.class}, proxy);
dog.wakeup();
dog.sleep();
proxy = new JdkProxy(new Cat("咪咪"));
Animal cat = (Animal) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Animal.class}, proxy);
cat.wakeup();
cat.sleep();
}
讲解:
可以看到,相对于静态代理类来说,无论有多少接口,这里只需要一个代理类。核心代码也很简单。唯一需要注意的点有以下2点:
-
JDK动态代理是需要声明接口的,创建一个动态代理类必须得给这个”虚拟“的类一个接口。可以看到,这时候经动态代理类创造之后的每个bean已经不是原来那个对象了。
-
为什么这里
JdkProxy
还需要构造传入原有的bean呢?因为处理完附加的功能外,需要执行原有bean的方法,以完成代理
的职责。这里
JdkProxy
最核心的方法就是public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
其中proxy为代理过之后的对象(并不是原对象),method为被代理的方法,args为方法的参数。
如果你不传原有的bean,直接用
method.invoke(proxy, args)
的话,那么就会陷入一个死循环。
可以代理什么
JDK的动态代理是也平时大家使用的最多的一种代理方式。也叫做接口代理。前几天有一个小伙伴在群里问我,动态代理是否一次可以代理一个类,多个类可不可以。
JDK动态代理说白了只是根据接口”凭空“来生成类,至于具体的执行,都被代理到了InvocationHandler
的实现类里。上述例子我是需要继续执行原有bean的逻辑,才将原有的bean构造进来。只要你需要,你可以构造进任何对象到这个代理实现类。也就是说,你可以传入多个对象,或者说你什么类都不代理。只是为某一个接口”凭空“的生成多个代理实例,这多个代理实例最终都会进入InvocationHandler
的实现类来执行某一个段共同的代码。
所以,在以往的项目中的一个实际场景就是,我有多个以yaml定义的规则文件,通过对yaml文件的扫描,来为每个yaml规则文件生成一个动态代理类。而实现这个,我只需要事先定义一个接口,和定义InvocationHandler
的实现类就可以了,同时把yaml解析过的对象传入。最终这些动态代理类都会进入invoke
方法来执行某个共同的逻辑。
源码分析#
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
// 判断有没有创建代理的权限
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
// 这是动态代理中最重要的方法,代理类就是从中得到的
Class<?> cl = getProxyClass0(loader, intfs);
// 判断代理类是否可以访问
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
// 得到代理类的构造方法,代理类只有一个有参的构造方法,参数类型就是InvocationHandler,这个后面我们会说到的
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
// 判断代理类是否是被public修饰的,如果不是,设计代理类是可以通过反射访问到的
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
// 创建代理类对象
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// 通过缓存得到代理类,如果没有就创建
return proxyClassCache.get(loader, interfaces);
}
我们看一下proxyClassCache是什么类的实例
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
public WeakCache(BiFunction<K, P, ?> subKeyFactory,
BiFunction<K, P, V> valueFactory) {
this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
this.valueFactory = Objects.requireNonNull(valueFactory);
}
123456789
KeyFactory实现了函数性接口,其中apply放回了相应接口的虚引用
private static final class KeyFactory
implements BiFunction<ClassLoader, Class<?>[], Object>
{
@Override
public Object apply(ClassLoader classLoader, Class<?>[] interfaces) {
switch (interfaces.length) {
// 目标类实现了一个接口,就返回一个接口的虚引用,Key1继承了WeakReference,并重写了equals方法,以保证相同接口名是的对象是同一个
case 1: return new Key1(interfaces[0]);
// 目标类实现了两个接口,就返回两个接口的虚引用,Key2继承了WeakReference,并重写了equals方法,以保证两个相同接口名是的对象是同一个
case 2: return new Key2(interfaces[0], interfaces[1]);
// 如果目标类没有实现接口就返回一个Object对象
case 0: return key0;
// 返回一个虚引用数组,KeyX继承了WeakReference,并重写了equals方法,以保证多个相同接口名是的对象是同一个
default: return new KeyX(interfaces);
}
}
}
ProxyClassFactory也实现了函数性接口,返回代理类的Class对象
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
// 代理类类名的前缀
private static final String proxyClassNamePrefix = "$Proxy";
//代理类类名的后缀
private static final AtomicLong nextUniqueNumber = new AtomicLong();
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
for (Class<?> intf : interfaces) {
// 判断相同名字的接口是否是同一个Class对象
Class<?> interfaceClass = null;
try {
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
// 判断相应接口是否是一个真正的接口
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
// 验证接口是否重复
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}
String proxyPkg = null;
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
// 如果目标类实现的接口中,有接口不是被public修饰的,那么代理类会动态生成在那个不是被public修饰的接口所在包下,且被final修饰,如果有多个接口不是被public修饰的,而且这些接口不在同一个包下,那么会报错。因为如果不是在同一个包下,可能会出现访问不到的问题
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL;
String name = intf.getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
if (proxyPkg == null) {
// 如果目标类所实现的接口都是被public修饰的,那么会动态生成在com.sun.proxy下,而且被public final修饰
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
// 生成代理类的二进制文件
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
// 动态生成代理类,这就是最后一步了,由于是native方法,所以就无法向下追究了
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
}
我们最后再来看一下proxyClassCache.get(loader, interfaces)
public V get(K key, P parameter) {
Objects.requireNonNull(parameter);
expungeStaleEntries();
// 同一个类加载器是同一个cacheKey
Object cacheKey = CacheKey.valueOf(key, refQueue);
ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
if (valuesMap == null) {
ConcurrentMap<Object, Supplier<V>> oldValuesMap
= map.putIfAbsent(cacheKey,
valuesMap = new ConcurrentHashMap<>());
if (oldValuesMap != null) {
valuesMap = oldValuesMap;
}
}
// 调用前面我们说到的KeyFactory实例对象的apply方法,返回目标实现接口的虚引用
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
Supplier<V> supplier = valuesMap.get(subKey);
Factory factory = null;
while (true) {
if (supplier != null) {
// 到这一步,其实supplier就是factory对象
V value = supplier.get();
if (value != null) {
return value;
}
}
if (factory == null) {
// Factory实现了Supplier类
factory = new Factory(key, parameter, subKey, valuesMap);
}
if (supplier == null) {
supplier = valuesMap.putIfAbsent(subKey, factory);
if (supplier == null) {
supplier = factory;
}
} else {
if (valuesMap.replace(subKey, supplier, factory)) {
supplier = factory;
} else {
supplier = valuesMap.get(subKey);
}
}
}
}
我们在看一下factory对象的get()方法
public synchronized V get() {
Supplier<V> supplier = valuesMap.get(subKey);
if (supplier != this) {
V value = null;
try {
// 其实最总还是调用了我上面提到的ProxyClassFactory实例对象的apply()方法,返回代理类的Class对象
value = Objects.requireNonNull(valueFactory.apply(key, parameter));
} finally {
if (value == null) {
valuesMap.remove(subKey, this);
}
}
assert value != null;
CacheValue<V> cacheValue = new CacheValue<>(value);
reverseMap.put(cacheValue, Boolean.TRUE);
if (!valuesMap.replace(subKey, this, cacheValue)) {
throw new AssertionError("Should not reach here");
}
return value;
}
}
5、代理类的庐山真面目
上面我们已经了解了动态代理的大致原理,我们知道生成Class对象是由defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length)这个方法所生成的,而这个方法要传入一个由byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);这个方法所生成的代理类的二进制文件,我们只要把这个二进制文件输出到一个文件中,这个文件就是代理类的class文件,然后工具反解析就行了。
private static void createProxyClassFile() {
String name = "ProxySwitchSell";
byte[] data = ProxyGenerator.generateProxyClass(
name, NintendoSwitchSell.class.getInterfaces(), Modifier.PUBLIC | Modifier.FINAL);
FileOutputStream out = null;
try {
out = new FileOutputStream(name + ".class");
out.write(data);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != out) try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Cglib动态代理#
Spring在5.X之前默认的动态代理实现一直是jdk动态代理。但是从5.X开始,spring就开始默认使用Cglib来作为动态代理实现。并且springboot从2.X开始也转向了Cglib动态代理实现。
是什么导致了spring体系整体转投Cglib呢,jdk动态代理又有什么缺点呢?
那么我们现在就要来说下Cglib的动态代理。
Cglib是一个开源项目,它的底层是字节码处理框架ASM,Cglib提供了比jdk更为强大的动态代理。主要相比jdk动态代理的优势有:
- jdk动态代理只能基于接口,代理生成的对象只能赋值给接口变量,而Cglib就不存在这个问题,Cglib是通过生成子类来实现的,代理对象既可以赋值给实现类,又可以赋值给接口。
- Cglib速度比jdk动态代理更快,性能更好。
那何谓通过子类来实现呢?
还是前面那个例子,我们要实现相同的效果。代码如下
创建CglibProxy类,用于统一代理:
public class CglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
private Object bean;
public CglibProxy(Object bean) {
this.bean = bean;
}
public Object getProxy(){
//设置需要创建子类的类
enhancer.setSuperclass(bean.getClass());
enhancer.setCallback(this);
//通过字节码技术动态创建子类实例
return enhancer.create();
}
//实现MethodInterceptor接口方法
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
String methodName = method.getName();
if (methodName.equals("wakeup")){
System.out.println("早安~~~");
}else if(methodName.equals("sleep")){
System.out.println("晚安~~~");
}
//调用原bean的方法
return method.invoke(bean,args);
}
}
//第二种写法
public class SampleClass {
public void test(){
System.out.println("hello world");
}
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(SampleClass.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("before method run...");
Object result = proxy.invokeSuper(obj, args);
System.out.println("after method run...");
return result;
}
});
SampleClass sample = (SampleClass) enhancer.create();
sample.test();
}
}
执行代码:
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy(new Student("张三"));
Student student = (Student) proxy.getProxy();
student.wakeup();
student.sleep();
proxy = new CglibProxy(new Doctor("王教授"));
Doctor doctor = (Doctor) proxy.getProxy();
doctor.wakeup();
doctor.sleep();
proxy = new CglibProxy(new Dog("旺旺"));
Dog dog = (Dog) proxy.getProxy();
dog.wakeup();
dog.sleep();
proxy = new CglibProxy(new Cat("咪咪"));
Cat cat = (Cat) proxy.getProxy();
cat.wakeup();
cat.sleep();
}
讲解:
在这里用Cglib作为代理,其思路和jdk动态代理差不多。也需要把原始bean构造传入。原因上面有说,这里不多赘述。
关键的代码在这里
//设置需要创建子类的类
enhancer.setSuperclass(bean.getClass());
enhancer.setCallback(this);
//通过字节码技术动态创建子类实例
return enhancer.create();
可以看到,Cglib"凭空"的创造了一个原bean的子类,并把Callback指向了this,也就是当前对象,也就是这个proxy对象。从而会调用intercept
方法。而在intercept
方法里,进行了附加功能的执行,最后还是调用了原始bean的相应方法。
在debug这个生成的代理对象时,我们也能看到,Cglib是凭空生成了原始bean的子类:
javassist动态代理#
Javassist是一个开源的分析、编辑和创建Java字节码的类库,可以直接编辑和生成Java生成的字节码。相对于bcel, asm等这些工具,开发者不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。
在日常使用中,javassit通常被用来动态修改字节码。它也能用来实现动态代理的功能。
话不多说,还是一样的例子,我用javassist动态代理来实现一遍
创建JavassitProxy,用作统一代理:
public class JavassitProxy {
private Object bean;
public JavassitProxy(Object bean) {
this.bean = bean;
}
public Object getProxy() throws IllegalAccessException, InstantiationException {
ProxyFactory f = new ProxyFactory();
f.setSuperclass(bean.getClass());
f.setFilter(m -> ListUtil.toList("wakeup","sleep").contains(m.getName()));
Class c = f.createClass();
MethodHandler mi = (self, method, proceed, args) -> {
String methodName = method.getName();
if (methodName.equals("wakeup")){
System.out.println("早安~~~");
}else if(methodName.equals("sleep")){
System.out.println("晚安~~~");
}
return method.invoke(bean, args);
};
Object proxy = c.newInstance();
((Proxy)proxy).setHandler(mi);
return proxy;
}
}
执行代码:
public static void main(String[] args) throws Exception{
JavassitProxy proxy = new JavassitProxy(new Student("张三"));
Student student = (Student) proxy.getProxy();
student.wakeup();
student.sleep();
proxy = new JavassitProxy(new Doctor("王教授"));
Doctor doctor = (Doctor) proxy.getProxy();
doctor.wakeup();
doctor.sleep();
proxy = new JavassitProxy(new Dog("旺旺"));
Dog dog = (Dog) proxy.getProxy();
dog.wakeup();
dog.sleep();
proxy = new JavassitProxy(new Cat("咪咪"));
Cat cat = (Cat) proxy.getProxy();
cat.wakeup();
cat.sleep();
}
讲解:
熟悉的配方,熟悉的味道,大致思路也是类似的。同样把原始bean构造传入。可以看到,javassist也是用”凭空“生成子类的方式类来解决,代码的最后也是调用了原始bean的目标方法完成代理。
javaassit比较有特点的是,可以对所需要代理的方法用filter来设定,里面可以像Criteria构造器那样进行构造。其他的代码,如果你仔细看了之前的代码演示,应该能很轻易看懂了。
ByteBuddy动态代理#
ByteBuddy,字节码伙计,一听就很牛逼有不。
ByteBuddy也是一个大名鼎鼎的开源库,和Cglib一样,也是基于ASM实现。还有一个名气更大的库叫Mockito,相信不少人用过这玩意写过测试用例,其核心就是基于ByteBuddy来实现的,可以动态生成mock类,非常方便。另外ByteBuddy另外一个大的应用就是java agent,其主要作用就是在class被加载之前对其拦截,插入自己的代码。
ByteBuddy非常强大,是一个神器。可以应用在很多场景。但是这里,只介绍用ByteBuddy来做动态代理,关于其他使用方式,可能要专门写一篇来讲述,这里先给自己挖个坑。
来,还是熟悉的例子,熟悉的配方。用ByteBuddy我们再来实现一遍前面的例子
创建ByteBuddyProxy,做统一代理:
public class ByteBuddyProxy {
private Object bean;
public ByteBuddyProxy(Object bean) {
this.bean = bean;
}
public Object getProxy() throws Exception{
Object object = new ByteBuddy().subclass(bean.getClass())
.method(ElementMatchers.namedOneOf("wakeup","sleep"))
.intercept(InvocationHandlerAdapter.of(new AopInvocationHandler(bean)))
.make()
.load(ByteBuddyProxy.class.getClassLoader())
.getLoaded()
.newInstance();
return object;
}
public class AopInvocationHandler implements InvocationHandler {
private Object bean;
public AopInvocationHandler(Object bean) {
this.bean = bean;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (methodName.equals("wakeup")){
System.out.println("早安~~~");
}else if(methodName.equals("sleep")){
System.out.println("晚安~~~");
}
return method.invoke(bean, args);
}
}
}
执行代码:
public static void main(String[] args) throws Exception{
ByteBuddyProxy proxy = new ByteBuddyProxy(new Student("张三"));
Student student = (Student) proxy.getProxy();
student.wakeup();
student.sleep();
proxy = new ByteBuddyProxy(new Doctor("王教授"));
Doctor doctor = (Doctor) proxy.getProxy();
doctor.wakeup();
doctor.sleep();
proxy = new ByteBuddyProxy(new Dog("旺旺"));
Dog dog = (Dog) proxy.getProxy();
dog.wakeup();
dog.sleep();
proxy = new ByteBuddyProxy(new Cat("咪咪"));
Cat cat = (Cat) proxy.getProxy();
cat.wakeup();
cat.sleep();
}
讲解:
思路和之前还是一样,通过仔细观察代码,ByteBuddy也是采用了创造子类的方式来实现动态代理。
各种动态代理的性能如何#
前面介绍了4种动态代理对于同一例子的实现。对于代理的模式可以分为2种:
- JDK动态代理采用接口代理的模式,代理对象只能赋值给接口,允许多个接口
- Cglib,Javassist,ByteBuddy这些都是采用了子类代理的模式,代理对象既可以赋值给接口,又可以复制给具体实现类
Spring5.X,Springboot2.X只有都采用了Cglib作为动态代理的实现,那是不是cglib性能是最好的呢?
我这里做了一个简单而粗暴的实验,直接把上述4段执行代码进行单线程同步循环多遍,用耗时来确定他们4个的性能。应该能看出些端倪。
JDK PROXY循环10000遍所耗时:0.714970125秒
Cglib循环10000遍所耗时:0.434937833秒
Javassist循环10000遍所耗时:1.294194708秒
ByteBuddy循环10000遍所耗时:9.731999042秒
执行的结果如上
从执行结果来看,的确是cglib效果最好。至于为什么ByteBuddy执行那么慢,不一定是ByteBuddy性能差,也有可能是我测试代码写的有问题,没有找到正确的方式。所以这只能作为一个大致的参考。
看来Spring选择Cglib还是有道理的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· DeepSeek在M芯片Mac上本地化部署