zoukankan      html  css  js  c++  java
  • 深入分析JDK动态代理

    一、动态代理的使用

    public class Test {
        //被代理的接口
        public interface IHello {
            void sayHello();
        }
        //接口的实现类
        static class Hello implements IHello {
            public void sayHello() {
                System.out.println("Hello world!!");
            }
        }
    
        //自定义InvocationHandler
        static class MyInvocationHandler implements InvocationHandler {
            //目标对象
            private Object target;
    
            public MyInvocationHandler(Object target) {
                this.target = target;
            }
    
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("------插入前置通知代码-------------");
                //执行相应的目标方法
                Object rs = method.invoke(target, args);
                System.out.println("------插入后置处理代码-------------");
                return rs;
            }
        }
    
        public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
            //生成$Proxy0的class文件,需要在根目录下创建com.sun.proxy路径,否则会报错
            System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
            IHello ihello = (IHello) Proxy.newProxyInstance(IHello.class.getClassLoader(),  //加载接口的类加载器
                    new Class[]{IHello.class},      //一组接口
                    new MyInvocationHandler(new Hello())); //自定义的InvocationHandler
            ihello.sayHello();
        }
    }

    输出结果

    ------插入前置通知代码-------------
    Hello world!!
    ------插入后置处理代码-------------

    二、源码分析

    1、以newProxyInstance方法为切入点来剖析代理类的生成及代理方法的调用

    public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h)
                throws IllegalArgumentException
        {
            if (h == null) {   //如果h为空直接抛出异常,所以InvocationHandler实例对象是必须的
                throw new NullPointerException();
            }
            final Class<?>[] intfs = interfaces.clone();
            //一些安全的权限检查
            final SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
                checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
            }
            //产生代理类
            Class<?> cl = getProxyClass0(loader, intfs);
    
            //获取代理类的构造函数对象
            //参数constructorParames为常量值:private static final Class<?>[] constructorParams = { InvocationHandler.class };
            final Constructor<?> cons = cl.getConstructor(constructorParames);
            final InvocationHandler ih = h;
            //根据代理类的构造函数对象来创建代理类对象
            return newInstance(cons, ih);
        }

      其中newInstance只是调用Constructor.newInstance来构造相应的代理类实例,这里重点是看getProxyClass0这个方法的实现:

    private static Class<?> getProxyClass0(ClassLoader loader,  
                                           Class<?>... interfaces) {  
        // 代理的接口数量不能超过65535(没有这种变态吧)  
        if (interfaces.length > 65535) {  
            throw new IllegalArgumentException("interface limit exceeded");  
        }  
        // JDK对代理进行了缓存,如果已经存在相应的代理类,则直接返回,否则才会通过ProxyClassFactory来创建代理  
        return proxyClassCache.get(loader, interfaces);  
    }  

      其中代理缓存是使用WeakCache实现的,如下

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

      具体的缓存逻辑这里暂不关心,只需要关心ProxyClassFactory是如何生成代理类的,ProxyClassFactory是Proxy的一个静态内部类,实现了WeakCache的内部接口BiFunction的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;  
                /*验证你传入的接口中是否有非public接口,只要有一个接口是非public的,那么这些接口都必须在同一包中
                这里的接口修饰符直接影响到System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true")所生成
                的代理类的路径,往下看!!*/
                for (Class<?> intf : interfaces) {
                    int flags = intf.getModifiers();
                    if (!Modifier.isPublic(flags)) {
                        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) {
                    /*如果都是public接口,那么生成的代理类就在com.sun.proxy包下*/            
                    proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
                }
    
                //将当前nextUniqueNumber的值以原子的方式的加1,所以第一次生成代理类的名字为$Proxy0.class
                long num = nextUniqueNumber.getAndIncrement();
                //代理类的完全限定名,如com.sun.proxy.$Proxy0.calss,
                String proxyName = proxyPkg + proxyClassNamePrefix + num;
                //生成代理类字节码文件            
                byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                    proxyName, interfaces);
                try {
                    return defineClass0(loader, proxyName,
                                        proxyClassFile, 0, proxyClassFile.length);
                } catch (ClassFormatError e) {
                    throw new IllegalArgumentException(e.toString());
                }
            }
        }

      而生成代理类字节码文件又主要通过ProxyGenerate的generateProxyClass(proxyName,interfaces)

    public static byte[] generateProxyClass(final String var0, Class[] var1) {
            ProxyGenerator var2 = new ProxyGenerator(var0, var1);
           //生成代理类字节码文件的真正方法
            final byte[] var3 = var2.generateClassFile();
            //这里根据参数配置,决定是否把生成的字节码(.class文件)保存到本地磁盘
            if(saveGeneratedFiles) {
                AccessController.doPrivileged(new PrivilegedAction() {
                    public Void run() {
                        try {
                            FileOutputStream var1 = new FileOutputStream(ProxyGenerator.dotToSlash(var0) + ".class");
                            var1.write(var3);
                            var1.close();
                            return null;
                        } catch (IOException var2) {
                            throw new InternalError("I/O exception saving generated file: " + var2);
                        }
                    }
                });
            }
            return var3;
        }

      层层调用后,最终generateClassFile才是真正生成代理类字节码文件的方法

    private byte[] generateClassFile() {
        //第一步, addProxyMethod系列方法就是将接口的方法和Object的hashCode,equals,toString方法添加到代理方法容器(proxyMethods)
        //首先为代理类生成toString, hashCode, equals等代理方法
        addProxyMethod(hashCodeMethod, Object.class);
        addProxyMethod(equalsMethod, Object.class);
        addProxyMethod(toStringMethod, Object.class);
        //遍历每一个接口的每一个方法, 并且为其生成ProxyMethod对象
        for (int i = 0; i < interfaces.length; i++) {
            Method[] methods = interfaces[i].getMethods();
            for (int j = 0; j < methods.length; j++) {
                addProxyMethod(methods[j], interfaces[i]);
            }
        }
        //对于具有相同签名的代理方法, 检验方法的返回值是否兼容
        //因为不可能有两个方法名相同,参数相同,而返回值却不同的方法
        for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
            checkReturnTypes(sigmethods);
        }
    
        //第二步, 组装要生成的class文件的所有的字段信息和方法信息
        try {
            //添加构造器方法
            methods.add(generateConstructor());
            //遍历缓存中的代理方法
            for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
                for (ProxyMethod pm : sigmethods) {
                    //添加代理类的静态字段, 例如:private static Method m1;
                    fields.add(new FieldInfo(pm.methodFieldName,
                            "Ljava/lang/reflect/Method;", ACC_PRIVATE | ACC_STATIC));
                    //添加代理类的代理方法
                    methods.add(pm.generateMethod());
                }
            }
            //添加代理类的静态字段初始化方法
            methods.add(generateStaticInitializer());
        } catch (IOException e) {
            throw new InternalError("unexpected I/O Exception");
        }
    
        //验证方法和字段集合不能大于65535
        if (methods.size() > 65535) {
            throw new IllegalArgumentException("method limit exceeded");
        }
        if (fields.size() > 65535) {
            throw new IllegalArgumentException("field limit exceeded");
        }
    
        //第三步, 写入最终的class文件
        //验证常量池中存在代理类的全限定名
        cp.getClass(dotToSlash(className));
        //验证常量池中存在代理类父类的全限定名, 父类名为:"java/lang/reflect/Proxy"
        cp.getClass(superclassName);
        //验证常量池存在代理类接口的全限定名
        for (int i = 0; i < interfaces.length; i++) {
            cp.getClass(dotToSlash(interfaces[i].getName()));
        }
        //接下来要开始写入文件了,设置常量池只读
        cp.setReadOnly();
    
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        DataOutputStream dout = new DataOutputStream(bout);
        try {
            //1.写入魔数
            dout.writeInt(0xCAFEBABE);
            //2.写入次版本号
            dout.writeShort(CLASSFILE_MINOR_VERSION);
            //3.写入主版本号
            dout.writeShort(CLASSFILE_MAJOR_VERSION);
            //4.写入常量池
            cp.write(dout);
            //5.写入访问修饰符
            dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER);
            //6.写入类索引
            dout.writeShort(cp.getClass(dotToSlash(className)));
            //7.写入父类索引, 生成的代理类都继承自Proxy
            dout.writeShort(cp.getClass(superclassName));
            //8.写入接口计数值
            dout.writeShort(interfaces.length);
            //9.写入接口集合
            for (int i = 0; i < interfaces.length; i++) {
                dout.writeShort(cp.getClass(dotToSlash(interfaces[i].getName())));
            }
            //10.写入字段计数值
            dout.writeShort(fields.size());
            //11.写入字段集合 
            for (FieldInfo f : fields) {
                f.write(dout);
            }
            //12.写入方法计数值
            dout.writeShort(methods.size());
            //13.写入方法集合
            for (MethodInfo m : methods) {
                m.write(dout);
            }
            //14.写入属性计数值, 代理类class文件没有属性所以为0
            dout.writeShort(0);
        } catch (IOException e) {
            throw new InternalError("unexpected I/O Exception");
        }
        //转换成二进制数组输出
        return bout.toByteArray();
    }

      可以看到generateClassFile()方法是按照Class文件结构进行动态拼接的。Java程序的执行只依赖于Class文件,这个Class文件描述了一个类的信息,当我们需要使用到一个类时,Java虚拟机就会提前去加载这个类的Class文件并进行初始化和相关的检验工作,Java虚拟机能够保证在你使用到这个类之前就会完成这些工作,我们只需要安心的去使用它就好了,而不必关心Java虚拟机是怎样加载它的。当然,Class文件并不一定非得通过编译Java文件而来,你甚至可以直接通过文本编辑器来编写Class文件。在这里,JDK动态代理就是通过程序来动态生成Class文件的。我们再次回到上面的代码中,可以看到,生成Class文件主要分为三步:
      第一步:收集所有要生成的代理方法,将其包装成ProxyMethod对象并注册到Map集合中。
      第二步:收集所有要为Class文件生成的字段信息和方法信息。
      第三步:完成了上面的工作后,开始组装Class文件。
      我们知道一个类的核心部分就是它的字段和方法。我们重点聚焦第二步,看看它为代理类生成了哪些字段和方法。在第二步中,按顺序做了下面四件事。

    • 为代理类生成一个带参构造器,传入InvocationHandler实例的引用并调用父类的带参构造器。
    • 遍历代理方法Map集合,为每个代理方法生成对应的Method类型静态域,并将其添加到fields集合中。
    • 遍历代理方法Map集合,为每个代理方法生成对应的MethodInfo对象,并将其添加到methods集合中。
    • 为代理类生成静态初始化方法,该静态初始化方法主要是将每个代理方法的引用赋值给对应的静态字段。

      通过以上分析,我们可以大致知道JDK动态代理最终会为我们生成如下结构的代理类:

    public class Proxy0 extends Proxy implements UserDao {
    
        //第一步, 生成构造器
        protected Proxy0(InvocationHandler h) {
            super(h);
        }
    
        //第二步, 生成静态域
        private static Method m1;   //hashCode方法
        private static Method m2;   //equals方法
        private static Method m3;   //toString方法
        private static Method m4;   //...
    
        //第三步, 生成代理方法
        @Override
        public int hashCode() {
            try {
                return (int) h.invoke(this, m1, null);
            } catch (Throwable e) {
                throw new UndeclaredThrowableException(e);
            }
        }
    
        @Override
        public boolean equals(Object obj) {
            try {
                Object[] args = new Object[] {obj};
                return (boolean) h.invoke(this, m2, args);
            } catch (Throwable e) {
                throw new UndeclaredThrowableException(e);
            }
        }
    
        @Override
        public String toString() {
            try {
                return (String) h.invoke(this, m3, null);
            } catch (Throwable e) {
                throw new UndeclaredThrowableException(e);
            }
        }
    
        @Override
        public void save(User user) {
            try {
                //构造参数数组, 如果有多个参数往后面添加就行了
                Object[] args = new Object[] {user};
                h.invoke(this, m4, args);
            } catch (Throwable e) {
                throw new UndeclaredThrowableException(e);
            }
        }
    
        //第四步, 生成静态初始化方法
        static {
            try {
                Class c1 = Class.forName(Object.class.getName());
                Class c2 = Class.forName(UserDao.class.getName());    
                m1 = c1.getMethod("hashCode", null);
                m2 = c1.getMethod("equals", new Class[]{Object.class});
                m3 = c1.getMethod("toString", null);
                m4 = c2.getMethod("save", new Class[]{User.class});
                //...
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
    }

      至此,经过层层分析,深入探究JDK源码,我们还原了动态生成的代理类的本来面目,之前心中存在的一些疑问也随之得到了很好的解释

    • 代理类默认继承Porxy类,因为Java中只支持单继承,所以JDK动态代理只能去实现接口。
    • 代理方法都会去调用InvocationHandler的invoke()方法,因此我们需要重写InvocationHandler的invoke()方法。
    • 调用invoke()方法时会传入代理实例本身,目标方法和目标方法参数。
    public static void main(String[] args) {
            //新建目标对象
            Object target = new UserDaoImpl();
            //创建事务处理器
            TransactionHandler handler = new TransactionHandler(target);
            //构造方法参数
            User user = new User();
            user.setName("小明");
            //生成代理类
            UserDao userDao = new Proxy0(handler);
            //针对接口进行方法调用
            userDao.save(user);
        }
        输出结果:开启事务控制
                保存用户
                结束事务控制

      使用刚刚构造出来的Proxy0作为代理类再次进行测试,可以看到最终的结果与使用JDK动态生成的代理类的效果是一样的,再次验证了我们的分析是可靠且准确的。

    转载自:http://www.cnblogs.com/liuyun1995/p/8144706.html

  • 相关阅读:
    mysql 1449 : The user specified as a definer ('root'@'%') does not exist 解决方法
    java中使用正则表达式
    Timer与ScheduledThreadPoolExecutor的比较
    Java同步块
    java中的浅拷贝与深拷贝
    java的关闭钩子(Shutdown Hook)
    JVM系列三:JVM参数设置、分析
    java虚拟机参数设置
    UTF-8编码规则(转)
    过滤3个字节以上的utf-8字符
  • 原文地址:https://www.cnblogs.com/qingchen521/p/8575764.html
Copyright © 2011-2022 走看看