欧博在线:java动态署理基本原理及proxy源码剖析一

admin 1个月前 (07-09) 科技 24 0

本系列文章主要是博主在学习spring aop的历程中领会到其使用了java动态署理,本着究根问底的态度,于是对java动态署理的本质原理做了一些研究,于是便有了这个系列的文章

为了尽快进入正题,这里先跳过spring aop和java动态署理的使用流程的解说,这部门内容后面再单独写文章整理

 

不外,我们首先照样先看下java dynamic proxy的基本使用方式,假定我们要署理的工具是一个Map,则代码如下:

Map proxyInstance = (Map) Proxy.newProxyInstance(
                HashMap.class.getClassLoader(),
                new Class[]{Map.class},
                new DynamicInvocationHandler());

之后proxyInstance就可以作为一个正常的Map工具举行使用了

为了对天生工具的属性做一个基本的领会,我们先打印一下proxyInstance的现实类型名称

System.out.println(proxyInstance.getClass().getName());

获得效果

com.sun.proxy.$Proxy11

若是使用多了,就会发现所有的署理类的名称都是$Proxy加一个数字,且包名是com.sun.proxy

 

当我们查看Proxy.newProxyInstance方式时,会发现它返回的实在是一个Object工具

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

而在现实使用的历程中,它是可以被直接转型成我们传入的接口类型,因此可以推测出,该proxyInstance工具的现实类型肯定是实现了我们传入的接口

我们打印一下该类实现的接口

for (Class intf : proxyInstance.getClass().getInterfaces()) {
    System.out.println(intf.getName());
}

获得效果

java.util.Map

相符我们之前的推测

 

接着我们再打印一下该类的父类

System.out.println(proxyInstance.getClass().getSuperclass().getName());

获得效果

java.lang.reflect.Proxy

 

因此总结一下,该proxyInstance工具有以下3个属性
1.继续了Proxy类
2.实现了我们传入的接口
3.以$Proxy+随机数字的命名

那么动态天生署理类的功效究竟是若何实现的呢?接下去就来看java的源码
由于源码有点多,以是我只贴出要害的部门

入口自然是Proxy.newProxyInstance方式
其中有2个部门我们需要体贴

第一部门,类的建立

/*
 * Look up or generate the designated proxy class.
 */
Class<?> cl = getProxyClass0(loader, intfs);

这个就是现实天生类的方式,后面我们会继续深究,先略放一放

 

第二部门,实例的建立

final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
... 
return cons.newInstance(new Object[]{h});

最终工具的实例化历程就是通过之前天生的class,获取其指定参数的组织函数,并将InvocationHandler工具传入

查看constructorParams字段

/** parameter types of a proxy class constructor */
private static final Class<?>[] constructorParams =
        { InvocationHandler.class };

简直就是获取InvocationHandler工具的一个组织函数

回忆一下之前类界说的第一条,继续了Proxy类,因此我们去Proxy类中找一下

    /**
     * Constructs a new {@code Proxy} instance from a subclass
     * (typically, a dynamic proxy class) with the specified value
     * for its invocation handler.
     *
     * @param  h the invocation handler for this proxy instance
     *
     * @throws NullPointerException if the given invocation handler, {@code h},
     *         is {@code null}.
     */
    protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }

在该组织函数中就是将参数h赋值给了成员变量h,这里名称h可以记一下,在之后的文章中还会遇到

 

看完实例的建立,让我们回到更主要的第一部门,类的天生
进入getProxyClass0(loader, intfs)方式

    /**
     * Generate a proxy class.  Must call the checkProxyAccess method
     * to perform permission checks before calling this.
     */
    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);
    }

该方式很简单,直接从一个cache中拿取工具

查看proxyClassCache工具

    /**
     * a cache of proxy classes
     */
    private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

该工具本质就是一个类似于Map的缓存,不外使用的是WeakCache,这个WeakCache自己的特征我们放到另一篇文章中讨论,本文专注于Proxy
我们可以看到该缓存的组织函数获取了2个Factory,顾名思义,第一个是天生key的,第二个是天生ProxyClass的,自然我们需要继续看第二个Factory

类的注解如下

/**
* A factory function that generates, defines and returns the proxy class given
* the ClassLoader and array of interfaces.
*/
private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>

这个就是我们要寻找的卖力详细天生类的工厂了,查看其apply方式

首先其会对传入的接口类型做一些校验,包罗loader能否加载到传入的接口,接口是否现实上是接口(由于数组的类型是Class),接口是否有重复

            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.
                 */
                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());
                }
            }

接着设置类的默认access_flag,public final

int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

接着检查传入的接口数组中是否包罗非public的接口,若是有,则天生的类需要和该接口处于同一个package,且接见属性会去掉public,只保留final。若是有多个差别package中的非public接口,则报错
(详细缘故原由人人应该都可以明白)

            /*
             * 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();
                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");
                    }
                }
            }

若是没有非public类,则会使用默认的package名,即com.sun.proxy

            if (proxyPkg == null) {
                // if no non-public proxy interfaces, use com.sun.proxy package
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
            }

然后获取一个静态自增的int

            /*
             * Choose a name for the proxy class to generate.
             */
            long num = nextUniqueNumber.getAndIncrement();

牢固的类名前缀

        // prefix for all proxy class names
        private static final String proxyClassNamePrefix = "$Proxy";

将上面三者组合成最终的类名(回忆之前我们打印出的实例的类名)

String proxyName = proxyPkg + proxyClassNamePrefix + num;

上面这几个步骤确定了类的名称,但照样皮毛,接下去是天生类的血肉:字节码

            /*
             * Generate the specified proxy class.
             */
            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());
            }

而该方式是一个native的方式,以是暂时就无法继续探讨了,不外知道了这个方式后,若是我们自己有需要,也可以行使这种机制实现自己的动态类天生,后面会想设施做一个demo,本文就不做探讨了

private static native Class<?> defineClass0(ClassLoader loader, String name,
                                                byte[] b, int off, int len);

 

 

 

之前实在都是开胃菜,现在回到之前天生字节码的方式,查看方式源码

    public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
        ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
        final byte[] var4 = var3.generateClassFile();
        if (saveGeneratedFiles) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    try {
                        int var1 = var0.lastIndexOf(46);
                        Path var2;
                        if (var1 > 0) {
                            Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));
                            Files.createDirectories(var3);
                            var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
                        } else {
                            var2 = Paths.get(var0 + ".class");
                        }

                        Files.write(var2, var4, new OpenOption[0]);
                        return null;
                    } catch (IOException var4x) {
                        throw new InternalError("I/O exception saving generated file: " + var4x);
                    }
                }
            });
        }

        return var4;
    }

中心if部门的代码可以先忽略,不外我们会在后面的文章中使用到这部门功效,这里先关注下面这2行代码

        ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
        final byte[] var4 = var3.generateClassFile();

这里让我们记一下
var0是类名
var1是接口
var3是access_flag
后面我会只管将这些varX转换成更现实的命名,利便人人明白

之后就是本文的最终的重点,也是难点,即二进制字节码的现实天生历程,包罗jvm操作指令,以是我们需要先对class文件的结构和jvm操作指令有一个领会,见下篇文章

总结而言:java动态署理的基本原理就是在运行时天生字节码,并通过一个native方式将其转换成Class工具供我们使用

,

Allbet Gmaing官网

欢迎进入Allbet Gmaing官网(Allbet Game):www.aLLbetgame.us,欧博官网是欧博集团的官方网站。欧博官网开放Allbet注册、Allbe代理、Allbet电脑客户端、Allbet手机版下载等业务。

皇冠体育声明:该文看法仅代表作者自己,与本平台无关。转载请注明:欧博在线:java动态署理基本原理及proxy源码剖析一

标签列表

    文章归档

    站点信息

    • 文章总数:485
    • 页面总数:0
    • 分类总数:8
    • 标签总数:904
    • 评论总数:78
    • 浏览总数:8005