栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

jdk动态代理的使用及源码分析

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

jdk动态代理的使用及源码分析

文章目录
  • 前言
  • 一、什么是动态代理?
  • 二、如何使用JDK动态代理
    • 1 写一个被代理类
    • 2 实现InvocationHandler
    • 3 创建代理对象
  • 三 源码分析
    • isProxyClass
    • getInvocationHandler
    • newProxyInstance
    • getProxyClass
  • 总结


前言

常用的动态代理实现有JDK动态代理以及CGLIB动态代理,本文对jdk动态代理的使用和原理做了粗浅的分析。

一、什么是动态代理?

动态代理就是,在程序运行期,创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术。在生成代理对象的过程中,目标对象不变,代理对象中的方法是目标对象方法的增强方法。可以理解为运行期间,对象中方法的动态拦截,在拦截方法的前后执行功能操作。
代理类在程序运行期间,创建的代理对象称之为动态代理对象。这种情况下,创建的代理对象,并不是事先在Java代码中定义好的。而是在运行期间,根据我们在动态代理对象中的“指示”,动态生成的。也就是说,你想获取哪个对象的代理,动态代理就会为你动态的生成这个对象的代理对象。动态代理可以对被代理对象的方法进行功能增强。有了动态代理的技术,那么就可以在不修改方法源码的情况下,增强被代理对象的方法的功能,在方法执行前后做任何你想做的事情。

二、如何使用JDK动态代理

JDK动态代理所需要使用到得类为java.lang.reflect.Proxy
代码示例如下:

1 写一个被代理类
public class Runner implements Runnable {
    
    @Override
    public void run() {
        Random random = new Random();
        try {
            TimeUnit.SECONDS.sleep(random.nextInt(5));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("run run run");
    }
}
2 实现InvocationHandler

我们的代理逻辑就是实现在这个类里

public class CostTimeInvocationHandler implements InvocationHandler {

    // 保存被代理对象
    private final Runnable runnable;

    public CostTimeInvocationHandler(Runnable runnable) {
        this.runnable = runnable;
    }

    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String name = method.getName();
        System.out.println("开始执行"+ name + "方法");
        long s = System.nanoTime();
        Object result = method.invoke(runnable, args);
        long e = System.nanoTime();
        System.out.println(name+"方法执行了"+ (e-s)/1000000 + "ms");
        return result;
    }
}
3 创建代理对象
public class Main {
    public static void main(String[] args) {
        Runner runner = new Runner(); //创建被代理对象
        Runnable runnable = (Runnable)Proxy.newProxyInstance(runner.getClass().getClassLoader(), 
                new Class[]{Runnable.class},
                new CostTimeInvocationHandler(runner));
        runnable.run();
    }
}

输出结果

开始执行run方法
run run run
run方法执行了2001ms
三 源码分析

Proxy暴露了四个静态方法

  1. isProxyClass(判断传入的类是否是代理类)
  2. getInvocationHandler(根据传入的代理对象返回与之关联的InvocationHandler对象)
  3. newProxyInstance(生成代理对象)
  4. getProxyClass (生成代理类)
isProxyClass

这个方法的作用是判断一个类是否是Proxy类生成的代理类

    public static boolean isProxyClass(Class cl) {
        return Proxy.class.isAssignableFrom(cl) && proxyClassCache.containsValue(cl);
    }

可以看到实现很简单,主要是判断传入进来的类是否是Proxy类的子类以及proxyClassCache中是否包含了该类
看到这里,我们就应该要知道以下两点:

  1. 生成的代理类是Proxy类的派生类
  2. 创建出来的代理类被缓存在了一个叫proxyClassCache的缓存中
getInvocationHandler

这个方法更简单,判断传入的对象是否是代理对象,如果不是则抛出异常,否则强转为Proxy,返回该代理对象关联的InvocationHandler对象

简化后的代码如下

   public static InvocationHandler getInvocationHandler(Object proxy)
        throws IllegalArgumentException
    {
        if (!isProxyClass(proxy.getClass())) {
            throw new IllegalArgumentException("not a proxy instance");
        }
        final Proxy p = (Proxy) proxy;
        final InvocationHandler ih = p.h;
        return ih;
    }
newProxyInstance

简化后的代码如下

// loader  用来加载创建的代理类
// interfaces 代理类需要实现的接口,也可以说是代理哪些接口
// h 此类来实现具体的代理逻辑
public static Object newProxyInstance(ClassLoader loader,
                                          Class[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h); // 断言入参不为空
        final Class[] intfs = interfaces.clone(); // 克隆一份
        Class cl = getProxyClass0(loader, intfs); // 生成代理类
        final Constructor cons = cl.getConstructor(constructorParams); // 获取构造方法
        final InvocationHandler ih = h;
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction() {
                public Void run() {
                    cons.setAccessible(true); 
                    return null;
                }
            });
        }
        return cons.newInstance(new Object[]{h}); // 调用构造方法
    }

首先校验入参是否为空,如果为空则抛异常
然后克隆一份传入的interfaces,为什么要克隆一份呢?因为在缓存代理类的时候使用到了interfaces和ClassLoader作为缓存的Key,所以自然不希望key会被用户改变。
随后调用getProxyClass0来创建代理类,创建代理的具体实现下静态方法再分析。
最后便是获取代理类的构造函数,通过反射的方式创建代理对象。

getProxyClass

方法本身没什么逻辑,就是调用getProxyClass0而已

    public static Class getProxyClass(ClassLoader loader,
                                         Class... interfaces)
        throws IllegalArgumentException
    {
        final Class[] intfs = interfaces.clone();
        return getProxyClass0(loader, intfs);
    }

看看getProxyClass0的源码

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
        return proxyClassCache.get(loader, interfaces);
    }

该方法校验了传入的接口数组的数量必须小于或等于65535,为什么要这个做呢?为什么是65535?
在字节码文件中,需要保存该类实现的接口,而java虚拟机规范是这么定义class文件格式的

interfaces_count表示该类实现接口的数量,它是使用两个字节存储的,所以如果超过两个字节所能表示的范围,那么是无法生成class的。传送门

校验接口数量后直接从proxyClassCache也就是缓存中获取代理类。看到这里肯定一脸问号,我明明是还没创建呢,怎么就从缓存中获取了呢?带着疑问看proxyClassCache是个什么鬼。

proxyClassCache的定义

    
    private static final WeakCache[], Class>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

好吧,继续看WeakCache的说明吧,WeakCache类上的注释是这么写的(机翻),应该还是能看懂的

缓存(key, sub-key) -> value映射对。 键和值是弱引用,但子键被强引用。 键直接传递给get方法,该方法也接受一个parameter 。 使用传递给构造函数的subKeyFactory函数根据键和参数计算子键。 使用传递给构造函数的valueFactory函数从键和参数计算值。 键可以为null并通过标识进行比较,而subKeyFactory返回的子键或valueFactory返回的值不能为空。 子键使用它们的equals方法进行比较。 当清除键的 WeakReferences 时,条目会在每次调用get 、 containsValue或size方法时从缓存中延迟删除。 清除对单个值的 WeakReferences 不会导致删除,但此类条目在逻辑上被视为不存在,并根据对其键/子键的请求触发对valueFactory重新评估。

构造函数

    public WeakCache(BiFunction subKeyFactory,
                     BiFunction valueFactory) {
        this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
        this.valueFactory = Objects.requireNonNull(valueFactory);
    }

读了文档就知道当没有找到对应的缓存也就是代理类时,将会调用valueFactory去生成,这下直接看ProxyClassFactory的实现就行了。
ProxyClassFactory类是Proxy的内部类,源码如下

    private static final class ProxyClassFactory
        implements BiFunction[], Class>
    {
        // 生成的代理类的名字的前缀
        private static final String proxyClassNamePrefix = "$Proxy";

        // 原子Long类型,每个代理类都有唯一的序号,就是通过这个生成的
        private static final AtomicLong nextUniqueNumber = new AtomicLong();

        // 这里的loader就是入参一路传过来的,interfaces就是入参的克隆
        public Class apply(ClassLoader loader, Class[] interfaces) {

            Map, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
            for (Class intf : interfaces) {
                
                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;     // package to define proxy class in
            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");
                    }
                }
            }
            if (proxyPkg == null) {
                // 如果没有非公共代理接口,使用 com.sun.proxy 包
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
            }

            
            long num = nextUniqueNumber.getAndIncrement(); // 生成代理类的唯一ID
            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());
            }
        }
    }

以上是ProxyClassFactory的代码,实际上它只是做了很多校验,然后生成类名,随后调用ProxyGenerator的generateProxyClass方法生成字节码。继续看ProxyGenerator类

generateProxyClass方法

    
    public static byte[] generateProxyClass(final String name,
                                            Class[] interfaces,
                                            int accessFlags)
    {
    	// 创建ProxyGenerator类实例,生成字节码
        ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags);
        final byte[] classFile = gen.generateClassFile();
		// 如果需要保存为文件就执行保存操作
        if (saveGeneratedFiles) {
            java.security.AccessController.doPrivileged(
            new java.security.PrivilegedAction() {
                public Void run() {
                    try {
                        int i = name.lastIndexOf('.');
                        Path path;
                        if (i > 0) {
                            Path dir = Paths.get(name.substring(0, i).replace('.', File.separatorChar));
                            Files.createDirectories(dir);
                            path = dir.resolve(name.substring(i+1, name.length()) + ".class");
                        } else {
                            path = Paths.get(name + ".class");
                        }
                        Files.write(path, classFile);
                        return null;
                    } catch (IOException e) {
                        throw new InternalError(
                            "I/O exception saving generated file: " + e);
                    }
                }
            });
        }

        return classFile;
    }

该方法调用generateClassFile方法去真正的生成字节码,随后判断是否需要保存,如果需要保存就保存。

    private final static boolean saveGeneratedFiles =
        java.security.AccessController.doPrivileged(
            new GetBooleanAction(
                "sun.misc.ProxyGenerator.saveGeneratedFiles")).booleanValue();

从这个字段的定义来看,只要我们在启动时设置对应的系统属性就能把字节码文件给保存下来,试试吧

可以看到确实生成了class文件,使用Idea打开,可以看到反编译后的结果,一切都跟之前看源码分析的那样,代理类继承了Proxy,实现了我们传入的接口

接下来继续看generateClassFile方法

 private byte[] generateClassFile() {

        
        addProxyMethod(hashCodeMethod, Object.class);
        addProxyMethod(equalsMethod, Object.class);
        addProxyMethod(toStringMethod, Object.class);

        
        for (Class intf : interfaces) {
            for (Method m : intf.getMethods()) {
                addProxyMethod(m, intf);
            }
        }

        
        for (List sigmethods : proxyMethods.values()) {
            checkReturnTypes(sigmethods);
        }
        try {
        	// 添加构造器
            methods.add(generateConstructor());
			// 为正在生成的类中的所有字段和方法组装 FieldInfo 和 MethodInfo 结构
            for (List sigmethods : proxyMethods.values()) {
                for (ProxyMethod pm : sigmethods) {

                    // add static field for method's Method object
                    fields.add(new FieldInfo(pm.methodFieldName,
                        "Ljava/lang/reflect/Method;",
                         ACC_PRIVATE | ACC_STATIC));

                    // generate code for proxy method and add it
                    methods.add(pm.generateMethod());
                }
            }

            methods.add(generateStaticInitializer());

        } catch (IOException e) {
            throw new InternalError("unexpected I/O Exception", e);
        }

        if (methods.size() > 65535) {
            throw new IllegalArgumentException("method limit exceeded");
        }
        if (fields.size() > 65535) {
            throw new IllegalArgumentException("field limit exceeded");
        }
        cp.getClass(dotToSlash(className));
        cp.getClass(superclassName);
        for (Class intf: interfaces) {
            cp.getClass(dotToSlash(intf.getName()));
        }
        cp.setReadOnly();

        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        DataOutputStream dout = new DataOutputStream(bout);

        try {
            
                                        // u4 magic;
            dout.writeInt(0xCAFEBABE);
                                        // u2 minor_version;
            dout.writeShort(CLASSFILE_MINOR_VERSION);
                                        // u2 major_version;
            dout.writeShort(CLASSFILE_MAJOR_VERSION);

            cp.write(dout);             // (write constant pool)

                                        // u2 access_flags;
            dout.writeShort(accessFlags);
                                        // u2 this_class;
            dout.writeShort(cp.getClass(dotToSlash(className)));
                                        // u2 super_class;
            dout.writeShort(cp.getClass(superclassName));

                                        // u2 interfaces_count;
            dout.writeShort(interfaces.length);
                                        // u2 interfaces[interfaces_count];
            for (Class intf : interfaces) {
                dout.writeShort(cp.getClass(
                    dotToSlash(intf.getName())));
            }

                                        // u2 fields_count;
            dout.writeShort(fields.size());
                                        // field_info fields[fields_count];
            for (FieldInfo f : fields) {
                f.write(dout);
            }

                                        // u2 methods_count;
            dout.writeShort(methods.size());
                                        // method_info methods[methods_count];
            for (MethodInfo m : methods) {
                m.write(dout);
            }

                                         // u2 attributes_count;
            dout.writeShort(0); // (no ClassFile attributes for proxy classes)

        } catch (IOException e) {
            throw new InternalError("unexpected I/O Exception", e);
        }

        return bout.toByteArray();
    }

方法里的内容只需要稍微了解字节码的格式就能理解,本质上就是按照字节码文件的规范,一步步写进去

总结

本文对jdk动态代理做了一点简单的分析。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/582653.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号