当前位置 : 主页 > 编程语言 > java >

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

来源:互联网 收集:自由互联 发布时间:2023-01-30
目录 1. 前言 2. 一个简单的例子 2.1. 定义接口 2.2. 接口实现类 2.3. 自定义 Handler 2.4. 测试 2.5. 输出结果 3. 源码分析 3.1. newProxyInstance() 方法 4. 代理对象长啥样 4.1. 代理对象长啥样 4.2. $P
目录
  • 1. 前言
  • 2. 一个简单的例子
    • 2.1. 定义接口
    • 2.2. 接口实现类
    • 2.3. 自定义 Handler
    • 2.4. 测试
    • 2.5. 输出结果
  • 3. 源码分析
    • 3.1. newProxyInstance() 方法
  • 4. 代理对象长啥样
    • 4.1. 代理对象长啥样
    • 4.2. $Proxy0 反编译
  • 5. JDK 动态代理为什么要有接口

    1. 前言

    JDK 动态代理的应用还是非常广泛的,例如在 Spring、MyBatis 以及 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) {
    			/*如果都是public接口,那么生成的代理类就在com.sun.proxy包下如果报		java.io.FileNotFoundException: com\sun\proxy\$Proxy0.class 
    			(系统找不到指定的路径。)的错误,就先在你项目中创建com.sun.proxy路径*/
            	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 后,其只能通过实现目标接口的方式来实现方法的扩展,达到我们增强目标方法逻辑的目的

    以上为个人经验,希望能给大家一个参考,也希望大家多多支持自由互联。

    上一篇:关于ArrayList的动态扩容机制解读
    下一篇:没有了
    网友评论