返回顶部
首页 > 资讯 > 后端开发 > Python >理解JDK动态代理为什么必须要基于接口
  • 526
分享到

理解JDK动态代理为什么必须要基于接口

JDK动态代理JDK代理JDK动态代理接口 2022-11-13 18:11:17 526人浏览 独家记忆

Python 官方文档:入门教程 => 点击学习

摘要

目录1. 前言2. 一个简单的例子2.1. 定义接口2.2. 接口实现类2.3. 自定义 Handler2.4. 测试2.5. 输出结果3. 源码分析3.1. newProxyIns

1. 前言

JDK 动态代理的应用还是非常广泛的,例如在 springmybatis 以及 Feign 等很多框架中动态代理都被大量的使用,可以说学好 JDK 动态代理,对于我们阅读这些框架的底层源码还是很有帮助的

2. 一个简单的例子

在分析原因之前,我们先完整的看一下实现 JDK 动态代理需要几个步骤,首先需要定义一个接口

2.1. 定义接口

public interface Worker {
    void work();
}

2.2. 接口实现类

public class Programmer implements Worker {
    @Override
    public void work() {
        System.out.println("coding...");
    }
}

2.3. 自定义 Handler

自定义一个 Handler,实现 InvocationHandler 接口,通过重写内部的 invoke() 方法实现逻辑增强

public class WorkHandler implements InvocationHandler {
    private final Object target;
    public WorkHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("work")) {
            System.out.println("before work...");
            Object result = method.invoke(target, args);
            System.out.println("after work...");
            return result;
        }
        return method.invoke(target, args);
    }
}

2.4. 测试

在 main() 方法中进行测试,使用 Proxy 类的静态方法 newProxyInstance() 生成一个代理对象并调用方法

public class MainTest {
    public static void main(String[] args) {
        Programmer programmer = new Programmer();
        Worker worker = (Worker) Proxy.newProxyInstance(
                programmer.getClass().getClassLoader(),
                programmer.getClass().getInterfaces(),
                new WorkHandler(programmer));
        worker.work();
    }
}

2.5. 输出结果

before work...
coding...
after work...

3. 源码分析

既然是一个代理的过程,那么肯定存在原生对象和代理对象之分,下面我们查看源码中是如何动态的创建代理对象的过程。

上面例子中,创建代理对象调用的是 Proxy 类的静态方法 newProxyInstance(),查看一下源码

3.1. newProxyInstance() 方法

public class Proxy implements java.io.Serializable {
	protected InvocationHandler h;
	// 有参构造器,参数是 InvocationHandler 
	protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }
	@CallerSensitive
	public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
		throws IllegalArgumentException {
	
		// 如果h为空直接抛出空指针异常,之后所有的单纯的判断null并抛异常,都是此方法
		Objects.requireNonNull(h);
		// 拷贝类实现的所有接口
    	final Class<?>[] intfs = interfaces.clone();
		// 获取当前系统安全接口
    	final SecurityManager sm = System.getSecurityManager();
    	if (sm != null) {
			// Reflection.getCallerClass 返回调用该方法的方法的调用类;loader:接口的类加载器
			// 进行包访问权限、类加载器权限等检查
			checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
		}
 
		// 查找或生成指定的代理类
		Class<?> cl = getProxyClass0(loader, intfs);
 
		// 用指定的调用处理程序调用它的构造函数
		try {
			if (sm != null) {
				checkNewProxyPermission(Reflection.getCallerClass(), cl);
        	}
	   		// 获取代理类的构造函数对象
	    	// constructorParams是类常量,作为代理类构造函数的参数类型,常量定义如下:
	    	// private static final Class<?>[] constructorParams = { InvocationHandler.class };
			final Constructor<?> cons = cl.getConstructor(constructorParams);
			final InvocationHandler ih = h;
			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......
	}
}
  • 在 checkProxyAccess() 方法中,进行参数验证
  • 在 getProxyClass0() 方法中,生成一个代理类 Class 或寻找已生成过的代理类的缓存
  • 通过 getConstructor() 方法获取生成的代理类的构造方法
  • 通过 newInstance() 方法,生成最终的代理对象

上面这个过程中,获取构造方法和生成代理对象都是利用的 Java 中的反射机制,而需要重点看的是生成代理类的方法 getProxyClass0()

3.1.1. getProxyClass0() 方法

private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
                                           
	// 接口数不得超过 65535 个,这么大,足够使用的了
	if (interfaces.length > 65535) {
		throw new IllegalArgumentException("interface limit exceeded");
	}
	// 如果缓存中有代理类了直接返回,否则将由代理类工厂ProxyClassFactory创建代理类
	return proxyClassCache.get(loader, interfaces);
}

如果缓存中已经存在了就直接从缓存中取,这里的 proxyClassCache 是一个 WeakCache 类型,如果缓存中目标 classLoader 和接口数组对应的类已经存在,那么返回缓存的副本。如果没有就使用 ProxyClassFactory 去生成 Class 对象

3.1.1.1. get() 方法

// key:类加载器;parameter:接口数组
public V get(K key, P parameter) {
	// 检查指定类型的对象引用不为空null。当参数为null时,抛出空指针异常
	Objects.requireNonNull(parameter);
	// 清除已经被 GC 回收的弱引用
	expungeStaleEntries();
	// 将ClassLoader包装成CacheKey, 作为一级缓存的key
	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;
        }
	}
 
	// 根据代理类实现的接口数组来生成二级缓存key
	Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
	// 通过subKey获取二级缓存值
	Supplier<V> supplier = valuesMap.get(subKey);
	Factory factory = null;
	// 这个循环提供了轮询机制, 如果条件为假就继续重试直到条件为真为止
	while (true) {
		if (supplier != null) {
			// 在这里supplier可能是一个Factory也可能会是一个CacheValue
			// 在这里不作判断, 而是在Supplier实现类的get方法里面进行验证
            V value = supplier.get();
            if (value != null) {
            	return value;
            }
        }
		if (factory == null) {
            factory = new Factory(key, parameter, subKey, valuesMap);
        }
		if (supplier == null) {
			// 到这里表明subKey没有对应的值, 就将factory作为subKey的值放入
        	supplier = valuesMap.putIfAbsent(subKey, factory);
           	if (supplier == null) {
                supplier = factory;
           	}
			// 否则, 可能期间有其他线程修改了值, 那么就不再继续给subKey赋值, 而是取出来直接用
            } else {
			// 期间可能其他线程修改了值, 那么就将原先的值替换
            if (valuesMap.replace(subKey, supplier, factory)) {
                supplier = factory;
			} else {
                supplier = valuesMap.get(subKey);
            }
		}
	}
}

很明显,重点关注下面代码

Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));

3.1.1.1.1. apply() 方法

private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>> {
    
	// 代理类的前缀名都以 $Proxy 开始
	private static final String proxyClassNamePrefix = "$Proxy";
 
	// 使用唯一的编号给作为代理类名的一部分,如 $Proxy0,$Proxy1 等
	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) {
                
        	// 验证指定的类加载器(loader)加载接口所得到的Class对象(interfaceClass)是否与intf对象相同
			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");
			}
                
            // 验证该Class对象是不是接口
            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;
 
        // 验证所有非公共的接口在同一个包内;公共的就无需处理
        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");
                }
        	}
		}
 		// 1.根据规则生成文件名
		if (proxyPkg == null) {
			
        	proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
		}
        long num = nextUniqueNumber.getAndIncrement();
	    // 代理类的完全限定类名,如 com.sun.proxy.$Proxy0.calss
        String proxyName = proxyPkg + proxyClassNamePrefix + num;
 
        // 2.生成代理的字节码数组
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
        // 3.生成 Class 
        try {
        	return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
        } catch (ClassFORMatError e) {
            throw new IllegalArgumentException(e.toString());
        }
	}
}

在 apply() 方法中,主要做了下面 3 件事

  • 根据规则生成文件名
  • 利用 ProxyGenerator.generateProxyClass() 方法生成代理的字节码数组
  • 调用方法 defineClass0() 生成 Class

3.1.2. getConstructor() 和 newInstance() 方法

返回代理类的 Class 后的流程,获取构造方法和生成代理对象都是利用的 Java 中的反射机制

4. 代理对象长啥样

4.1. 代理对象长啥样

创建代理对象流程的源码分析完了,我们可以先通过 debug 来看看上面生成的这个代理对象究竟是个什么

和源码中看到的规则一样,是一个 Class 为 $Proxy0 的对象。再看一下代理对象的 Class 的详细信息

类的全限定类名是 com.sun.proxy.$Proxy0,在上面我们提到过,这个类是在运行过程中动态生成的

4.2. $Proxy0 反编译

看一下反编译后 $Proxy0.java 文件的内容,下面的代码中,我只保留了核心部分,省略了无关紧要的 equals()、toString()、hashCode() 方法的定义

public final class $Proxy0 extends Proxy implements Worker{
    public $Proxy0(InvocationHandler invocationhandler){
        super(invocationhandler);
    }
    public final void work(){
        try{
            super.h.invoke(this, m3, null);
            return;
        }catch(Error _ex) { }
        catch(Throwable throwable){
            throw new UndeclaredThrowableException(throwable);
        }
    }
    private static Method m3;
    static {
        try{           
            m3 = Class.forName("com.hydra.test.Worker").getMethod("work", new Class[0]);   
            //省略其他Method
        }//省略catch
    }
}

这个临时生成的代理类 $Proxy0 中主要做了下面的几件事

  • 在这个类的静态代码块中,通过 反射机制 初始化了多个静态方法 Method 变量,除了接口中的方法还有 equals()、toString()、hashCode() 这三个方法
  • 代理类 $Proxy0 继承了父类 Proxy,在其实例化的过程中会调用父类的构造器,而父类 Proxy 中的构造器中传入的 InvocationHandler 对象实际上是我们自定义的 WorkHandler 的实例。此时就可以调用 WorkHandler 的 invoke() 方法了
  • 同时,代理类 $Proxy0 也实现了自定义的接口 Worker,并重写了 work() 方法,在 work()方法内又调用了 InvocationHandler 的 invoke() 方法,也就是实际上调用了 WorkHandler 的 invoke() 方法

到这里,整体的流程就分析完了,我们可以用一张图来简要总结上面的过程

5. JDK 动态代理为什么要有接口

其实如果不看上面的分析,我们也应该知道,要扩展一个类有常见的两种方式,继承父类或实现接口。这两种方式都允许我们对方法的逻辑进行增强,但现在不是由我们自己来重写方法,而是要想办法让 JVM 去调用 InvocationHandler 中的 invoke() 方法,也就是说代理类需要和两个东西关联在一起

  • 被代理类
  • InvocationHandler

而 JDK 动态代理处理这个问题的方式是选择继承父类 Proxy,并把 InvocationHandler 保存在父类的对象中

public class Proxy implements java.io.Serializable {
    protected InvocationHandler h;
    
    protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }
    
    // ......
}

通过父类 Proxy 的构造方法,保存了创建代理对象过程中传进来的 InvocationHandler 的实例,使用 protected 修饰保证了它可以在子类中被访问和使用。

但是同时,因为 Java 是单继承的,因此在代理类 $Proxy0 继承了 Proxy 后,其只能通过实现目标接口的方式来实现方法的扩展,达到我们增强目标方法逻辑的目的

以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。

--结束END--

本文标题: 理解JDK动态代理为什么必须要基于接口

本文链接: https://lsjlt.com/news/169266.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作