首页 > 编程语言 >从源码角度分析JDK动态代理

从源码角度分析JDK动态代理

时间:2024-11-17 19:18:12浏览次数:3  
标签:JDK 代理 value 源码 supplier new null public

文章目录


前言

  本篇从源码的角度,对JDK动态代理的实现,工作原理做简要分析。


一、JDK动态代理

  JDK动态代理是运行时动态代理的一种实现,相比较于CGLIB ,目标对象必须实现接口,下面是一个简单案例:
  接口及实现类:

public interface UserService {
    void register();
}
public class UserServiceImpl implements UserService {
    @Override
    public void register() {
        System.out.println("注册的逻辑...");
    }
}

  测试类:

public class ProxyTest {
    public static void main(String[] args) {

        UserService userService = new UserServiceImpl();

        UserService proxy = (UserService) Proxy.newProxyInstance(
                userService.getClass().getClassLoader(),
                userService.getClass().getInterfaces(),
                new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("注册前...");
                Object invoke = method.invoke(userService, args);
                System.out.println("完成注册...");
                return invoke;
            }
        });
        proxy.register();
    }
}

  关键在于Proxy.newProxyInstance方法,该方法有三个参数:

  • ClassLoader loader:指定代理类的类加载器。
  • Class<?>[] interfaces:指定代理类需要实现的接口。
  • InvocationHandler h:定义代理对象的行为。

在这里插入图片描述  最关键的是第三个参数:InvocationHandler h官方注释给出的含义是,每次调用代理对象的方法时,都会转发到 InvocationHandlerinvoke 方法进行处理。
  案例中的代码运行后也确实是执行了invoke的逻辑:
在这里插入图片描述  那么动态代理是如何生成的?调用目标类的方法,为何会执行invoke的逻辑?下面从源码的角度进行分析

二、动态代理的生成

  生成动态代理的关键,在于getProxyClass0中的逻辑:
在这里插入图片描述  java.lang.reflect.Proxy#getProxyClass0的两个参数,分别是类加载器,和代理类实现的接口。首先会尝试从proxyClassCache缓存中获取代理类,如果获取不到,则会走get方法的逻辑进行创建:

    private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        //如果存在由实现给定接口的给定加载器定义的代理类,则返回缓存的副本;否则,它将通过 ProxyClassFactory 创建代理类
        return proxyClassCache.get(loader, interfaces);
    }

  java.lang.reflect.WeakCache#get的参数,同样是类加载器,和代理类实现的接口:

    public V get(K key, P parameter) {
        Objects.requireNonNull(parameter);
			
			 //清理缓存中已经过期或失效的条目。
        expungeStaleEntries();

			//将类加载器和refQueue包装成一个CacheKey对象,支持弱引用机制。
			//保证即使主键对象被垃圾回收,缓存条目也能正确清理。
        Object cacheKey = CacheKey.valueOf(key, refQueue);

        // lazily install the 2nd level valuesMap for the particular cacheKey
        //初始化二级缓存映射 
        // private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map = new ConcurrentHashMap<>();
        //map是WeakCache的属性,value是二级缓存,每一级缓存都是ConcurrentMap的形式
        ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
        //二级缓存为空
        if (valuesMap == null) {
        		//将一个新的 ConcurrentHashMap 放入一级缓存,使用CHM为了保证插入的并发安全,防止重复插入
            ConcurrentMap<Object, Supplier<V>> oldValuesMap
                = map.putIfAbsent(cacheKey,
                                  valuesMap = new ConcurrentHashMap<>());
            if (oldValuesMap != null) {
                valuesMap = oldValuesMap;
            }
        }

        // create subKey and retrieve the possible Supplier<V> stored by that
        // subKey from valuesMap
        //利用类加载器和和代理类实现的接口生成一个二级缓存key
        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
        //获取二级缓存的value
        Supplier<V> supplier = valuesMap.get(subKey);
        //初始化一个工厂
        Factory factory = null;

			
        while (true) {
        		//如果二级缓存中已经有了value,直接获取
            if (supplier != null) {
                // supplier might be a Factory or a CacheValue<V> instance
                V value = supplier.get();
                if (value != null) {
                    return value;
                }
            }
            // else no supplier in cache
            // or a supplier that returned null (could be a cleared CacheValue
            // or a Factory that wasn't successful in installing the CacheValue)

            // lazily construct a Factory
            if (factory == null) {
            		//懒加载一个 Factory
                factory = new Factory(key, parameter, subKey, valuesMap);
            }

					// 如果当前没有 supplier
            if (supplier == null) {
            		//将当前 Factory 插入二级缓存。
                supplier = valuesMap.putIfAbsent(subKey, factory);
                //supplier 为 null 证明二级缓存中没有相同的值
                if (supplier == null) {
                    // successfully installed Factory
                    //当前线程成功设置factory
                    supplier = factory;
                }
                // else retry with winning supplier
            } else {
                if (valuesMap.replace(subKey, supplier, factory)) {
                    // successfully replaced
                    // cleared CacheEntry / unsuccessful Factory
                    // with our Factory
                    supplier = factory;
                } else {
                    // retry with current supplier
                    supplier = valuesMap.get(subKey);
                }
            }
        }
    }

  在该方法中利用到了二级缓存,关键在于WeakCache的map属性:一级缓存的value是一个ConcurrentMap类型的集合。

    private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map = new ConcurrentHashMap<>();

  当某个线程第一次进入该方法时,根据key从一级缓存中获取是为空的,就会在下面的代码片段中创建一个一级缓存一级缓存中的二级缓存此时只是初始化,是没有具体的值的:
在这里插入图片描述  然后会根据类加载器,和代理类实现的接口构建一个二级缓存的key,尝试从二级缓存中获取值:
在这里插入图片描述  第一次也是获取不到的,所以会在创建Factory工厂后,通过valuesMap.putIfAbsent(subKey, factory);去设置二级缓存的键值:
在这里插入图片描述
  接着第二次循环再次走到下面的代码片时,supplier已经不为空了(此时的supplier就是factory,二级缓存的value)
在这里插入图片描述  不难看出上述的操作都是在为缓存赋值,使用CHM的原因也是为了防止多线程并发操作时发生重复。


  接下来V value = supplier.get();实际是调用了WeakCacheFactory内部类的get方法,方法上也加了锁避免并发冲突。

        @Override
        public synchronized V get() { // serialize access
            // re-check
            //从二级缓存中获取值
            Supplier<V> supplier = valuesMap.get(subKey);
            //还会判断supplier的类型是否被替换
            if (supplier != this) {
                // something changed while we were waiting:
                // might be that we were replaced by a CacheValue
                // or were removed because of failure ->
                // return null to signal WeakCache.get() to retry
                // the loop
                return null;
            }
            // else still us (supplier == this)

            // create new value
            V value = null;
            try {
                value = Objects.requireNonNull(valueFactory.apply(key, parameter));
            } finally {
                if (value == null) { // remove us on failure
                    valuesMap.remove(subKey, this);
                }
            }
            // the only path to reach here is with non-null value
            assert value != null;

            // wrap value with CacheValue (WeakReference)
            //用一个弱引用包装器(CacheValue)包装生成的值 value。
            CacheValue<V> cacheValue = new CacheValue<>(value);

            // try replacing us with CacheValue (this should always succeed)
            if (valuesMap.replace(subKey, this, cacheValue)) {
                // put also in reverseMap
                //将 cacheValue 添加到 reverseMap 中。
                reverseMap.put(cacheValue, Boolean.TRUE);
            } else {
                throw new AssertionError("Should not reach here");
            }

            // successfully replaced us with new CacheValue -> return the value
            // wrapped by it
            //成功生成的值(value)被缓存后,返回给调用者。
            return value;
        }
    }

  图上圈出的方法,是创建代理类的核心方法:
在这里插入图片描述  在java.lang.reflect.Proxy.ProxyClassFactory#apply方法中,首先会进行接口的各种校验,以及生成代理类的名称

        @Override
        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
					//验证是否存在重复接口
            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
            for (Class<?> intf : interfaces) {
                /*
                 * Verify that the class loader resolves the name of this
                 * interface to the same Class object.
                 */
                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");
                }
                /*
                 * Verify that the Class object actually represents an
                 * interface.
                 */
                //使用 isInterface() 确认当前类是一个接口。
                if (!interfaceClass.isInterface()) {
                    throw new IllegalArgumentException(
                        interfaceClass.getName() + " is not an interface");
                }
                /*
                 * Verify that this interface is not a duplicate.
                 */
                //如果接口已经存在于集合中,说明重复,抛出异常。
                if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                    throw new IllegalArgumentException(
                        "repeated interface: " + interfaceClass.getName());
                }
            }

            String proxyPkg = null;     // package to define proxy class in
            //默认代理类为 public 和 final
            int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

            /*
             * Record the package of a non-public proxy interface so that the
             * proxy class will be defined in the same package.  Verify that
             * all non-public proxy interfaces are in the same package.
             */
            for (Class<?> intf : interfaces) {
                int flags = intf.getModifiers();
                //如果接口不是 public,代理类必须在与接口相同的包中。
                if (!Modifier.isPublic(flags)) {
                		 //将代理类的访问权限限制为 final。不可修改
                    accessFlags = Modifier.FINAL;
                    //对于非 public 接口,提取接口的包名。
                    String name = intf.getName();
                    int n = name.lastIndexOf('.');
                    String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                    if (proxyPkg == null) {
                        proxyPkg = pkg;
                    //所有非 public 接口在同一个包中,否则抛出异常。
                    } else if (!pkg.equals(proxyPkg)) {
                        throw new IllegalArgumentException(
                            "non-public interfaces from different packages");
                    }
                }
            }
					//如果所有接口都是 public,代理类定义在默认的 com.sun.proxy 包中。
            if (proxyPkg == null) {
                // if no non-public proxy interfaces, use com.sun.proxy package
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
            }

            /*
             * Choose a name for the proxy class to generate.
             */
            //生成代理类的名称
            long num = nextUniqueNumber.getAndIncrement();
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            /*
             * Generate the specified proxy class.
             */
            //真正创建代理类的核心方法,偏底层的native方法
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                /*
                 * A ClassFormatError here means that (barring bugs in the
                 * proxy class generation code) there was some other
                 * invalid aspect of the arguments supplied to the proxy
                 * class creation (such as virtual machine limitations
                 * exceeded).
                 */
                throw new IllegalArgumentException(e.toString());
            }
        }
    }

  代理类的名称格式为:[包名].Proxy $ [递增编号],为什么代理类中有$符号也是在这里决定的:
在这里插入图片描述  而return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);是核心中的核心,代理类正是在该方法中生成的,点进去发现是本地方法
在这里插入图片描述  那么如何才能看到在运行时动态生成的代理类?可以添加JVM参数:

-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

在这里插入图片描述  运行项目,在当前的工作目录下即可看到运行时动态生成的代理类:
在这里插入图片描述

三、invoke的运行时调用

  在上一步既然看到了生成的代理类,那么在调用目标方法时,为什么会转发到invoke就很清晰了:

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

package com.sun.proxy;

import com.itbaima.proxy.UserService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

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

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

    //...中间都是一些hashcode和equals的逻辑

    public final void register() throws  {
        try {
        		//调用Proxy类InvocationHandler属性的invoke方法
            super.h.invoke(this, m3, (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"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m3 = Class.forName("com.itbaima.proxy.UserService").getMethod("register");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(((Throwable)var2).getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(((Throwable)var3).getMessage());
        }
    }
}

  调用代理对象的 register 方法时,register 方法的调用被路由到 $Proxy0 的 register 方法,然后又会调用Proxy类InvocationHandler属性的invoke方法。


总结

  1. 为什么生成的代理类,方法都要被final修饰?
      防止子类重写,绕过 InvocationHandler 的逻辑,破坏代理机制,确保代理行为的安全性。并且生成的代理类是高度封装的,设计原则是尽量减少外界对其行为的干扰。final 修饰符保证了代理类的封闭性和完整性,符合开闭原则(对扩展开放,对修改封闭)。
  2. JDK 动态代理中的 WeakCache为什么要使用二级缓存机制?
      一级缓存存储了一个 Supplier,用来提供对代理类的引用。二级缓存存储实际生成的代理类。二级缓存的存在可以保存生成的代理类,避免因一级缓存中的数据被回收导致代理类被重复生成。并且二级缓存通过弱引用管理代理类生命周期,避免内存泄漏。

标签:JDK,代理,value,源码,supplier,new,null,public
From: https://blog.csdn.net/2301_77599076/article/details/143822396

相关文章

  • 基于大语言模型的自治代理综述 《A Survey on Large Language Model based Autonomous
    图2基于LLM的自治代理架构设计的统一框架基于大语言模型的自治代理综述《ASurveyonLargeLanguageModelbasedAutonomousAgents》自治代理长期以来一直是学术界和工业界的研究热点。以前的研究往往侧重于在孤立的环境中训练知识有限的代理,这与人类的学习过程存......
  • SpringBoot在线投票数据分析平台研究与设计8kxf0(程序+源码+数据库+调试部署+开发环境)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容一、项目背景与意义随着互联网技术的普及,在线投票活动逐渐成为各类组织、企业和个人进行决策和意见收集的重要手段。然而,如何高效地收集、整理和分......
  • SpringBoot在线教育系统a1q7y(程序+源码+数据库+调试部署+开发环境)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容一、项目背景与意义随着互联网技术的快速发展,教育领域正经历着前所未有的变革。在线教育系统作为数字化教育的重要载体,以其跨越时空限制、灵活便捷......
  • 基于Springboot+Vue的停车管理系统 (含源码数据库)
    1.开发环境开发系统:Windows10/11架构模式:MVC/前后端分离JDK版本:JavaJDK1.8开发工具:IDEA数据库版本:mysql5.7或8.0数据库可视化工具:navicat服务器:SpringBoot自带apachetomcat主要技术:Java,Springboot,mybatis,mysql,vue2.视频演示地址3.功能这个系......
  • 基于Springboot+Vue的中国蛇类识别系统 (含源码数据库)
    1.开发环境开发系统:Windows10/11架构模式:MVC/前后端分离JDK版本:JavaJDK1.8开发工具:IDEA数据库版本:mysql5.7或8.0数据库可视化工具:navicat服务器:SpringBoot自带apachetomcat主要技术:Java,Springboot,mybatis,mysql,vue2.视频演示地址3.功能这个系......
  • Linux设置socks代理
    公司里绝大多数主机已经禁止外网访问,仅保留一台主机设置socks作为代理服务器。如下为对socks这一概念的学习整理什么是socks是OSI模型下会话层的协议,位于表示层与传输层之间,作用是:exchangesnetworkpacketsbetweenaclientandserverthroughaproxyserver出现......
  • ssm131保险业务管理系统设计与实现+jsp(论文+源码)_kaic
     毕业设计(论文)题目:保险业务管理系统设计与实现      摘 要现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本保险业务管理系统就是在这样的大环境下诞生,其可以帮助管理者在短时......
  • 解读 DelayQueue 源码:探究其精妙的设计架构与实现细节
    一、简介DelayQueue是JUC包(java.util.concurrent)为我们提供的延迟队列,用于实现延时任务比如订单下单15分钟未支付直接取消。它是BlockingQueue的一种,底层是一个基于PriorityQueue实现的一个无界队列,是线程安全的BlockingQueue的实现类DelayQueue 中存放的元素......
  • flask火车购票系统(毕设源码+论文)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容一、选题背景关于火车购票系统的研究,现有研究多侧重于传统购票方式的改进以及购票系统的基本功能实现。在国内外,随着信息技术的发展,购票系统已逐渐......
  • flask基于SpringBoot的私人物品管理平台(毕设源码+论文)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容一、选题背景随着人们生活水平的提高,私人物品的数量和种类不断增加,如何有效地管理私人物品成为一个重要问题。关于私人物品管理平台的研究,现有研究......