为什么JDK动态代理只能代理接口,不能直接代理类?CGlib为什么可以代理类?

17 篇文章 0 订阅

一、JDK动态代理与CGlib动态代理

众所周知,Spring会基于代理的类去动态的选择使用JDK创建代理对象还是CGLIB(当然也可以配置全都使用CGlib),这取决于被代理的对象是类还是接口。

  • java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
  • CGlib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理(字节码增强)。

二、为什么JDK动态代理只能代理接口,不能直接代理类?

(一)简单的实现JDK动态代理

1.先定义一个接口(JDK动态代理只能代理接口)

public interface UserService {
	/**
	 * test
	 *
	 * @return
	 */
	String query();
}

2.接口的实现类

public class UserServiceImpl implements UserService{
    
    @Override
    public String query() {
        System.out.println("query");
        return null;
    }
}

3.再定义一个InvocationHandler接口的实现类:
UserServiceInvocationHandler。在自定义的InvocationHandler中,定义了一个属性:target,定义这个属性的目的是为了在InvocationHandler中持有对目标对象的引用,target属性的初始化是在构造器中进行初始化的。

public class UserServiceInvocationHandler implements InvocationHandler {

	/**
	 * 持有目标对象
	 */
	private Object target;

	public UserServiceInvocationHandler(Object target) {
		this.target = target;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("invocation handler");
		// 通过反射调用目标对象的方法
		return method.invoke(target, args);
	}
}

4.通过Proxy.newInstance()创建代理对象

public class MainApplication {

	public static void main(String[] args) {
		// 指明一个类加载器,要操作class文件,怎么少得了类加载器呢
		ClassLoader classLoader = MainApplication.class.getClassLoader();
		// 为代理对象指定要是实现哪些接口,这里我们要为UserServiceImpl这个目标对象创建动态代理,所以需要为代理对象指定实现UserService接口
		Class[] classes = new Class[]{UserService.class};
		// 初始化一个InvocationHandler,并初始化InvocationHandler中的目标对象
		InvocationHandler invocationHandler = new UserServiceInvocationHandler(new UserServiceImpl());
		// 创建动态代理
		UserService userService = (UserService) Proxy.newProxyInstance(classLoader, classes, invocationHandler);
		// 执行代理对象的方法,通过观察控制台的结果,判断我们是否对目标对象(UserServiceImpl)的方法进行了增强
		userService.query();
	}
}

执行结果
在这里插入图片描述
5.代理类的class文件

阅读到这里,我们基本知道了JDK动态代理的写法,功能虽然实现了,但是对于喜欢研究源码的同学,可能会思考:为什么调用Proxy.newProxyInstance()方法后,就产生了代理对象,对目标对象的方法进行了增强?原理是什么呢?

  • 通常学习某项技术的原理,都是看源文件.java或者借助反编译工具反编译.class文件后去分析。可是对于JDK的动态代理,它产生的代理对象是在运行时创建的,通过常规操作,我们没办法得到这个代理对象对应的.class文件。如果能得到代理对象多对应的class文件,那动态代理的原理,我们就好分析了。
  • 所以接下来介绍两种获取到代理对象的.class文件的方法。

手动写到磁盘
在调用Proxy.newProxyInstance()方法时,最终会调用到ProxyGenerator.generateProxyClass()方法,该方法的作用就是生成代理对象的class文件,返回值是一个byte[]数组。所以我们可以将生成的byte[]数组通过输出流,将内容写出到磁盘。

	public static void main(String[] args) throws IOException {
		String proxyName = "com.sun.proxy.$Proxy0";
		Class[] interfaces = new Class[]{UserService.class};
		int accessFlags = Modifier.PUBLIC;
		byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
				proxyName, interfaces, accessFlags);
		// 将字节数组写出到磁盘
		File file = new File("C:\\Users\\admin\\Desktop\\$Proxy0.class");
		OutputStream outputStream = new FileOutputStream(file);
		outputStream.write(proxyClassFile);
	}

自动写到磁盘
第二种方法是通过百度查来的,在代码中添加一行代码:System.getProperties().put(“sun.misc.ProxyGenerator.saveGeneratedFiles”, “true”),就能实现将程序运行过程中产生的动态代理对象的class文件写入到磁盘。

	public static void main(String[] args) {
		// 让代理对象的class文件写入到磁盘
		System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
		// 指明一个类加载器,要操作class文件,怎么少得了类加载器呢
		ClassLoader classLoader = MainApplication.class.getClassLoader();
		// 为代理对象指定要是实现哪些接口,这里我们要为UserServiceImpl这个目标对象创建动态代理,所以需要为代理对象指定实现UserService接口
		Class[] classes = new Class[]{UserService.class};
		// 初始化一个InvocationHandler,并初始化InvocationHandler中的目标对象
		InvocationHandler invocationHandler = new UserServiceInvocationHandler(new UserServiceImpl());
		// 创建动态代理
		UserService userService = (UserService) Proxy.newProxyInstance(classLoader, classes, invocationHandler);
		// 执行代理对象的方法,通过观察控制台的结果,判断我们是否对目标对象(UserServiceImpl)的方法进行了增强
		userService.query();
	}

在这里插入图片描述
第一种的路径可以切换为你的桌面路径或者其它的,在桌面找到生成后的代理class文件,直接拖进idea,打开后如下(两种生成的都是一样的,打开哪个都可以)

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.sun.proxy;

import cn.bugstack.middleware.db.router.UserService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public class $Proxy0 extends Proxy implements UserService {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String query() throws  {
        try {
            return (String)super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("cn.bugstack.middleware.db.router.UserService").getMethod("query");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
  • 通过源码我们发现,$Proxy0类继承了Proxy类,同时实现了UserService接口。到这里,我们的问题一就能解释了,为什么JDK的动态代理只能基于接口实现,不能基于继承来实现?因为Java中不支持多继承,而JDK的动态代理在创建代理对象时,默认让代理对象继承了Proxy类,所以JDK只能通过接口去实现动态代理。

  • $ Proxy0 实现了UserService接口,所以重写了接口中的两个方法($ Proxy0 同时还重写了Object类中的几个方法)。所以当我们调用query()方法时,先是调用到$Proxy0.query()方法,在这个方法中,直接调用了super.h.invoke()方法,父类是Proxy,父类中的h就是我们定义的InvocationHandler,所以这儿会调用到UserServiceInvocationHandler.invoke()方法。因此当我们通过代理对象去执行目标对象的方法时,会先经过InvocationHandler的invoke()方法,然后在通过反射method.invoke()去调用目标对象的方法,因此每次都会先打印invocation handler这句话。

三、CGlib为什么可以代理类?

cglib实现动态代理的逻辑是使用子类继承代理类,就没有单继承的限制了。

四、总结

1.JDK动态代理是实现了被代理对象的接口,Cglib是继承了被代理对象。
2.JDK和Cglib都是在运行期生成字节码,JDK是直接写Class字节码,Cglib使用ASM框架写Class字节码,Cglib代理实现更复杂,生成代理类比JDK效率低。
3.JDK调用代理方法,是通过反射机制调用,Cglib是通过FastClass机制直接调用方法,Cglib执行效率更高。

五、参考:

https://blog.csdn.net/weixin_42227763/article/details/117965268
https://www.cnblogs.com/monkey0307/p/8328821.html

  • 11
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值