zoukankan      html  css  js  c++  java
  • java学习笔记13--反射机制与动态代理

    本文地址:http://www.cnblogs.com/archimedes/p/java-study-note13.html,转载请注明源地址。

    Java的反射机制

    在Java运行时环境中,对于任意一个类,能否知道这个类有哪些属性和方法?对于任意一个对象,能否调用它的任意一个方法?答案是肯定的。

    这种动态获取类的信息以及动态调用对象的方法的功能来自于Java 语言的反射(Reflection)机制。

    Java 反射机制主要提供了以下功能:

    在运行时判断任意一个对象所属的类。

    在运行时构造任意一个类的对象。

    在运行时判断任意一个类所具有的成员变量和方法。

    在运行时调用任意一个对象的方法

    Reflection 是Java被视为动态(或准动态)语言的一个关键性质。这个机制允许程序在运行时透过Reflection API取得任何一个已知名称的class的内部信息,包括其modifiers(诸如public, static 等等)、superclass(例如Object)、实现之interfaces(例如Serializable),也包括fields和methods的所有信息,并可于运行时改变fields内容或调用methods

    在JDK中,主要由以下类来实现Java反射机制,这些类都位于java.lang.reflect包中

    Class类:代表一个类。

    Field 类:代表类的成员变量(成员变量也称为类的属性)。

    Method类:代表类的方法。

    Constructor 类:代表类的构造方法。

    Array类:提供了动态创建数组,以及访问数组的元素的静态方法

    Proxy类以及InvocationHandler接口:提供了动态生成代理类以及实例的方法

    其中,Class类是Reflection API 中的核心类,它有以下方法:

    •     getName():获得类的完整名字 

    •     getFields():获得类的public类型的属性 

    •     getDeclaredFields():获得类的所有属性 

    •     getMethods():获得类的public类型的方法 

    •     getDeclaredMethods():获得类的所有方法 

    •     getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes 参数指定方法的参数类型 

    •     getConstructors():获得类的public类型的构造方法 

    •     getConstructor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes 参数指定构造方法的参数类型 

    •     newInstance():通过类的不带参数的构造方法创建这个类的一个对象 

    每当一個类被载入时,JVM就自动为其生成一个Class对象,通过操作class对象,我们可以得到该对象的所有成员并操作它们,举个例子:

    package javatest;
    import java.util.*;
    class Student {
        private String name;
        private int age;
        private int ID;
        public Student() {
            
        }
        public Student(String name, int age, int ID) {
            this.name = name;
            this.age = age;
            this.ID = ID;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    }
    public class javatest {
        public static void main(String[] args) {
            Student s1 = new Student("java", 20, 123);
            Class ss = s1.getClass();
            System.out.println("getName: " + ss.getName());
            System.out.println("getFields: " + ss.getFields());
            System.out.println("getDeclaredFields: " + ss.getDeclaredFields());
            System.out.println("getMethods: " + ss.getMethods());
            System.out.println("isInterface: " + ss.isInterface());
            System.out.println("isPrimitive: " + ss.isPrimitive());
            System.out.println("isArray: " + ss.isArray());
            System.out.println("SuperClass: " + ss.getSuperclass().getName());
        }
    }

    运行结果如下:

    getName: javatest.Student
    getFields: [Ljava.lang.reflect.Field;@4e25154f
    getDeclaredFields: [Ljava.lang.reflect.Field;@70dea4e
    getMethods: [Ljava.lang.reflect.Method;@5c647e05
    isInterface: false
    isPrimitive: false
    isArray: false
    SuperClass: java.lang.Object

    通过反射得到类对象:

    获取方式

    说明

    示例

    object.getClass()

    每个对象都有此方法

    获取指定实例对象的Class

    List list = new ArrayList();

    Class listClass = list.getClass();

    class. getSuperclass()

    获取当前Class的继承类Class

    List list = new ArrayList();

    Class listClass = list.getClass();

    Class superClass = listClass. getSuperclass();

    Object.class

    .class直接获取

    Class listClass = ArrayList.class;

    Class.forName(类名)

    用Class的静态方法,传入类的全称即可

    try {

    Class c = Class.forName("java.util.ArrayList");

    } catch (ClassNotFoundException e) {

    e.printStackTrace();

    }

    Primitive.TYPE

    基本数据类型的封装类获取Class的方式

    Class longClass = Long.TYPE;

    Class integerClass = Integer.TYPE;

    Class voidClass = Void.TYPE;

    平常情况我们通过new Object来生成一个类的实例,但有时候我们没法直接new,只能通过反射动态生成。

    通过反射实例化对象:

    实例化无参构造函数的对象,两种方式:

    ① Class. newInstance();

    ② Class. getConstructor (new Class[]{}).newInstance(new Object[]{})

    实例化带参构造函数的对象:

    class.getConstructor(Class<?>... parameterTypes) . newInstance(Object... initargs)

    接下来举个例子实战一下:

    package javatest;
    import java.util.*;
    
    class BaseUser {
        public int baseId;
        public int getBaseId() {
            return baseId;
        }
        public void setBaseId(int baseId) {
            this.baseId = baseId;
        }
    }
    
    class User extends BaseUser {
        private int id;
        public String name;
        public User(){}
        public User(String name) {
            this.name = name;
        }
        private int getId() {
            return id;
        }
        private void serId(int id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    }
    
    public class javatest {
        public static void main(String[] args) {
            Class<?> userClass = User.class;
            try {
                User user = (User)userClass.newInstance();
                System.out.println("1.反射实例化(无参): " + user);
                User user2 = (User)userClass.getConstructor(new Class[]{}).newInstance(new Object[]{});
                System.out.println("2.反射实例化(无参): " + user2);
                User user3 = (User)userClass.getConstructor(new Class[]{String.class}).newInstance(new Object[]{"test"});
                System.out.println("反射实例化(带参): " + user3 + " 属性Name的值: " + user3.getName());
                User user4 = new User();
                System.out.println("正常实例化: " + user4);
            } catch(Exception e) {
                e.printStackTrace();
            }
        }
    }

    运行结果如下:

    1.反射实例化(无参): javatest.User@7852e922
    2.反射实例化(无参): javatest.User@4e25154f
    反射实例化(带参): javatest.User@70dea4e 属性Name的值: test
    正常实例化: javatest.User@5c647e05

    通过反射调用Method(方法):

    获得当前类以及超类的public Method:

    Method[] arrMethods = classType.getMethods();

    获得当前类申明的所有Method:

    Method[] arrMethods = classType.getDeclaredMethods();

    获得当前类以及超类指定的public Method:

    Method method = classType.getMethod(String name, Class<?>... parameterTypes);

    获得当前类申明的指定的Method:

    Method method = classType.getDeclaredMethod(String name, Class<?>... parameterTypes)

    通过反射动态运行指定Method:

    Object obj = method.invoke(Object obj, Object... args) 

    例:动态操纵Method,改变一下上面的例子的主函数:

    public class reflectMethodDemo {
        public static void main(String[] args) {
            User user = new User();
            Class<?> userClass = User.class;
            
            Method[] publicMethod = userClass.getMethods();
            for(Method method : publicMethod) {
                System.out.println("获得当前类以及超类的所有publicMethod: " + method);
            }
            
            Method[] currentMethod = userClass.getDeclaredMethods();
            for(Method method : currentMethod) {
                System.out.println("获得当前类自己声明的所有的Method: " + method);
            }
            try {
                Method setBaseIdMethod = userClass.getMethod("setBaseId", new Class[]{int.class});
                System.out.println("获得当前类或超类的public Method setBaseId: " + setBaseIdMethod);
                Method setIdMethod = userClass.getDeclaredMethod("setId", new Class[]{int.class});
                System.out.println("获得当前类的Method setId: " + setIdMethod);
                setIdMethod.setAccessible(true);
                setIdMethod.invoke(user, new Object[]{110});
                Method getIdMethod = userClass.getDeclaredMethod("getId", new Class[]{});
                getIdMethod.setAccessible(true);
                Integer getId = (Integer)getIdMethod.invoke(user, new Object[]{});
                System.out.println("调用getId方法获得: " + getId);
            } catch(Exception e) {
                e.printStackTrace();
            }
        }
    }

    通过反射调用Field(变量):

    获得当前类以及超类的public Field:

    Field[] arrFields = classType.getFields();

    获得当前类申明的所有Field:

    Field[] arrFields = classType.getDeclaredFields();

    获得当前类以及超类指定的public Field:

    Field field = classType.getField(String name);

    获得当前类申明的指定的Field:

    Field field = classType.getDeclaredField(String name);

    通过反射动态设定Field的值:

    fieldType.set(Object obj, Object value);

    通过反射动态获取Field的值:

    Object obj = fieldType.get(Object obj) ;

    例:动态操纵Field,改变一下上面的例子的主函数:

    public class reflectFieldDemo {
        public static void main(String[] args) {
            User1 user = new User1();
            Class<?> userClass = user.getClass();
            Field[] publicField = userClass.getFields();
            for(Field field : publicField) {
                System.out.println("获得该类及超类所有public Field: " + field);
            }
            
            Field[] currentField = userClass.getDeclaredFields();
            for(Field field : currentField) {
                System.out.println("获得该类自己声明的所有Field: " + field);
            }
            try {
                Field baseIdField = userClass.getField("baseId");
                System.out.println("获得该类或超类名为baseId的public Field: " + baseIdField);
                Field idField = userClass.getDeclaredField("id");
                System.out.println("获得该类自己声明的名为id的Field: " + idField);
                idField.setAccessible(true);
                idField.set(user, 110);
                Integer id = (Integer)idField.get(user);
                System.out.println("id的值为: " + id);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    Java反射总结:

    1、只要用到反射,先获得Class Object

    2、没有方法能获得当前类的超类的private方法和属性,你必须通过getSuperclass()找到超类以后再去尝试获得

    3、通常情况即使是当前类,private属性或方法也是不能访问的,你需要设置压制权限setAccessible(true)来取得private的访问权。但说实话,这已经破坏了面向对象的规则,所以除非万不得已,请尽量少用。

    Java的动态代理机制

    代理:设计模式

    代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。

    图 1. 代理模式

    为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。Java 动态代理机制以巧妙的方式近乎完美地实践了代理模式的设计理念。

    相关的类和接口:

    要了解 Java 动态代理的机制,首先需要了解以下相关的类或接口:

    java.lang.reflect.Proxy:这是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。

    清单 1. Proxy 的静态方法

    // 方法 1: 该方法用于获取指定代理对象所关联的调用处理器
    static InvocationHandler getInvocationHandler(Object proxy) 
    
    // 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
    static Class getProxyClass(ClassLoader loader, Class[] interfaces) 
    
    // 方法 3:该方法用于判断指定类对象是否是一个动态代理类
    static boolean isProxyClass(Class cl) 
    
    // 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
    static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)

    java.lang.reflect.InvocationHandler:这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。

    清单 2. InvocationHandler 的核心方法

    // 该方法负责集中处理动态代理类上的所有方法调用。第一个参数既是代理类实例,第二个参数是被调用的方法对象
    // 第三个方法是调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上发射执行
    Object invoke(Object proxy, Method method, Object[] args)

    每次生成动态代理类对象时都需要指定一个实现了该接口的调用处理器对象(参见 Proxy 静态方法 4 的第三个参数)。

    java.lang.ClassLoader:这是类装载器类,负责将类的字节码装载到 Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。Proxy 静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。

    每次生成动态代理类对象时都需要指定一个类装载器对象(参见 Proxy 静态方法 4 的第一个参数)

    代理机制及其特点:

    首先让我们来了解一下如何使用 Java 动态代理。具体有如下四步骤:

    1、通过实现 InvocationHandler 接口创建自己的调用处理器;

    2、通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;

    3、通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;

    4、通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。

    清单 3. 动态代理对象创建过程

    // InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
    // 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用
    InvocationHandler handler = new InvocationHandlerImpl(..); 
    // 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象 Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... });
    // 通过反射从生成的类对象获得构造函数对象 Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });
    // 通过构造函数对象创建动态代理类实例 Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });

    实际使用过程更加简单,因为 Proxy 的静态方法 newProxyInstance 已经为我们封装了步骤 2 到步骤 4 的过程,所以简化后的过程如下

    清单 4. 简化的动态代理对象创建过程

    // InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
    InvocationHandler handler = new InvocationHandlerImpl(..); 
    // 通过 Proxy 直接创建动态代理类实例 Interface proxy = (Interface)Proxy.newProxyInstance( classLoader, new Class[] { Interface.class }, handler );

    接下来让我们来了解一下 Java 动态代理机制的一些特点。

    首先是动态生成的代理类本身的一些特点。

    1)包:如果所代理的接口都是 public 的,那么它将被定义在顶层包(即包路径为空),如果所代理的接口中有非 public 的接口(因为接口不能被定义为 protect 或 private,所以除 public 之外就是默认的 package 访问级别),那么它将被定义在该接口所在包(假设代理了 com.cnblogs.wu 包中的某非 public 接口 A,那么新生成的代理类所在的包就是  com.cnblogs.wu),这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问;

    2)类修饰符:该代理类具有 final 和 public 修饰符,意味着它可以被所有的类访问,但是不能被再度继承;

    3)类名:格式是“$ProxyN”,其中 N 是一个逐一递增的阿拉伯数字,代表 Proxy 类第 N 次生成的动态代理类,值得注意的一点是,并不是每次调用 Proxy 的静态方法创建动态代理类都会使得 N 值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会很聪明地返回先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率。

    4)类继承关系:该类的继承关系如图:

    图 2. 动态代理类的继承图

    由图可见,Proxy 类是它的父类,这个规则适用于所有由 Proxy 创建的动态代理类。而且该类还实现了其所代理的一组接口,这就是为什么它能够被安全地类型转换到其所代理的某接口的根本原因。

    接下来让我们了解一下代理类实例的一些特点。每个实例都会关联一个调用处理器对象,可以通过 Proxy 提供的静态方法 getInvocationHandler 去获得代理类实例的调用处理器对象。在代理类实例上调用其代理的接口中所声明的方法时,这些方法最终都会由调用处理器的 invoke 方法执行,此外,值得注意的是,代理类的根类 java.lang.Object 中有三个方法也同样会被分派到调用处理器的 invoke 方法执行,它们是 hashCode,equals 和 toString,可能的原因有:一是因为这些方法为 public 且非 final 类型,能够被代理类覆盖;二是因为这些方法往往呈现出一个类的某种特征属性,具有一定的区分度,所以为了保证代理类与委托类对外的一致性,这三个方法也应该被分派到委托类执行。当代理的一组接口有重复声明的方法且该方法被调用时,代理类总是从排在最前面的接口中获取方法对象并分派给调用处理器,而无论代理类实例是否正在以该接口(或继承于该接口的某子接口)的形式被外部引用,因为在代理类内部无法区分其当前的被引用类型。

    接着来了解一下被代理的一组接口有哪些特点。首先,要注意不能有重复的接口,以避免动态代理类代码生成时的编译错误。其次,这些接口对于类装载器必须可见,否则类装载器将无法链接它们,将会导致类定义失败。再次,需被代理的所有非 public 的接口必须在同一个包中,否则代理类生成也会失败。最后,接口的数目不能超过 65535,这是 JVM 设定的限制。

    最后再来了解一下异常处理方面的特点。从调用处理器接口声明的方法中可以看到理论上它能够抛出任何类型的异常,因为所有的异常都继承于 Throwable 接口,但事实是否如此呢?答案是否定的,原因是我们必须遵守一个继承原则:即子类覆盖父类或实现父接口的方法时,抛出的异常必须在原方法支持的异常列表之内。所以虽然调用处理器理论上讲能够,但实际上往往受限制,除非父接口中的方法支持抛 Throwable 异常。那么如果在 invoke 方法中的确产生了接口方法声明中不支持的异常,那将如何呢?放心,Java 动态代理类已经为我们设计好了解决方法:它将会抛出 UndeclaredThrowableException 异常。这个异常是一个 RuntimeException 类型,所以不会引起编译错误。通过该异常的 getCause 方法,还可以获得原来那个不受支持的异常对象,以便于错误诊断。

    机制和特点都介绍过了,接下来让我们通过源代码来了解一下 Proxy 到底是如何实现的。

    首先记住 Proxy 的几个重要的静态变量:

    清单 5. Proxy 的重要静态变量

    // 映射表:用于维护类装载器对象到其对应的代理类缓存
    private static Map loaderToCache = new WeakHashMap(); 
    // 标记:用于标记一个动态代理类正在被创建中 private static Object pendingGenerationMarker = new Object();
    // 同步表:记录已经被创建的动态代理类类型,主要被方法 isProxyClass 进行相关的判断 private static Map proxyClasses = Collections.synchronizedMap(new WeakHashMap());
    // 关联的调用处理器引用 protected InvocationHandler h;

    然后,来看一下 Proxy 的构造方法:

    清单 6. Proxy 构造方法

    // 由于 Proxy 内部从不直接调用构造函数,所以 private 类型意味着禁止任何调用
    private Proxy() {} 
    
    // 由于 Proxy 内部从不直接调用构造函数,所以 protected 意味着只有子类可以调用
    protected Proxy(InvocationHandler h) {this.h = h;}

    接着,可以快速浏览一下 newProxyInstance 方法,因为其相当简单:

    清单 7. Proxy 静态方法 newProxyInstance

    public static Object newProxyInstance(ClassLoader loader, 
                Class<?>[] interfaces, 
                InvocationHandler h) 
                throws IllegalArgumentException { 
        
        // 检查 h 不为空,否则抛异常
        if (h == null) { 
            throw new NullPointerException(); 
        } 
    
        // 获得与制定类装载器和一组接口相关的代理类类型对象
        Class cl = getProxyClass(loader, interfaces); 
    
        // 通过反射获取构造函数对象并生成代理类实例
        try { 
            Constructor cons = cl.getConstructor(constructorParams); 
            return (Object) cons.newInstance(new Object[] { h }); 
        } catch (NoSuchMethodException e) { throw new InternalError(e.toString()); 
        } catch (IllegalAccessException e) { throw new InternalError(e.toString()); 
        } catch (InstantiationException e) { throw new InternalError(e.toString()); 
        } catch (InvocationTargetException e) { throw new InternalError(e.toString()); 
        } 
    }

    由此可见,动态代理真正的关键是在 getProxyClass 方法,该方法负责为一组接口动态地生成代理类类型对象。在该方法内部,您将能看到 Proxy 内的各路英雄(静态变量)悉数登场。该方法总共可以分为四个步骤:

    1、对这组接口进行一定程度的安全检查,包括检查接口类对象是否对类装载器可见并且与类装载器所能识别的接口类对象是完全相同的,还会检查确保是 interface 类型而不是 class 类型。这个步骤通过一个循环来完成,检查通过后将会得到一个包含所有接口名称的字符串数组,记为 String[] interfaceNames。总体上这部分实现比较直观,所以略去大部分代码,仅保留留如何判断某类或接口是否对特定类装载器可见的相关代码。

    清单 8. 通过 Class.forName 方法判接口的可见性

    try { 
        // 指定接口名字、类装载器对象,同时制定 initializeBoolean 为 false 表示无须初始化类
        // 如果方法返回正常这表示可见,否则会抛出 ClassNotFoundException 异常表示不可见
        interfaceClass = Class.forName(interfaceName, false, loader); 
    } catch (ClassNotFoundException e) { 
    }

    2、从 loaderToCache 映射表中获取以类装载器对象为关键字所对应的缓存表,如果不存在就创建一个新的缓存表并更新到 loaderToCache。缓存表是一个 HashMap 实例,正常情况下它将存放键值对(接口名字列表,动态生成的代理类的类对象引用)。当代理类正在被创建时它会临时保存(接口名字列表,pendingGenerationMarker)。标记 pendingGenerationMarke 的作用是通知后续的同类请求(接口数组相同且组内接口排列顺序也相同)代理类正在被创建,请保持等待直至创建完成。

    清单 9. 缓存表的使用

    do { 
        // 以接口名字列表作为关键字获得对应 cache 值
        Object value = cache.get(key); 
        if (value instanceof Reference) { 
            proxyClass = (Class) ((Reference) value).get(); 
        } 
        if (proxyClass != null) { 
            // 如果已经创建,直接返回
            return proxyClass; 
        } else if (value == pendingGenerationMarker) { 
            // 代理类正在被创建,保持等待
            try { 
                cache.wait(); 
            } catch (InterruptedException e) { 
            } 
            // 等待被唤醒,继续循环并通过二次检查以确保创建完成,否则重新等待
            continue; 
        } else { 
            // 标记代理类正在被创建
            cache.put(key, pendingGenerationMarker); 
            // break 跳出循环已进入创建过程
            break; 
    } while (true);

    3、动态创建代理类的类对象。首先是确定代理类所在的包,其原则如前所述,如果都为 public 接口,则包名为空字符串表示顶层包;如果所有非 public 接口都在同一个包,则包名与这些接口的包名相同;如果有多个非 public 接口且不同包,则抛异常终止代理类的生成。确定了包后,就开始生成代理类的类名,同样如前所述按格式“$ProxyN”生成。类名也确定了,接下来就是见证奇迹的发生 —— 动态生成代理类:

    清单 10. 动态生成代理类

    // 动态地生成代理类的字节码数组
    byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces); 
    try { 
        // 动态地定义新生成的代理类
        proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0, 
            proxyClassFile.length); 
    } catch (ClassFormatError e) { 
        throw new IllegalArgumentException(e.toString()); 
    } 
    
    // 把生成的代理类的类对象记录进 proxyClasses 表
    proxyClasses.put(proxyClass, null);

    由此可见,所有的代码生成的工作都由神秘的 ProxyGenerator 所完成了,当你尝试去探索这个类时,你所能获得的信息仅仅是它位于并未公开的 sun.misc 包,有若干常量、变量和方法以完成这个神奇的代码生成的过程,但是 sun 并没有提供源代码以供研读。至于动态类的定义,则由 Proxy 的 native 静态方法 defineClass0 执行。

    代码生成过程进入结尾部分,根据结果更新缓存表,如果成功则将代理类的类对象引用更新进缓存表,否则清楚缓存表中对应关键值,最后唤醒所有可能的正在等待的线程。

    走完了以上四个步骤后,至此,所有的代理类生成细节都已介绍完毕,剩下的静态方法如 getInvocationHandler 和 isProxyClass 就显得如此的直观,只需通过查询相关变量就可以完成,所以对其的代码分析就省略了。

    代理类实现推演:

    分析了 Proxy 类的源代码,相信对 Java 动态代理机制形成一个更加清晰的理解,整理一下思绪,一起来完成一次完整的推演过程吧。

    清单 11. 代理类中方法调用的分派转发推演实现

    // 假设需代理接口 Simulator 
    public interface Simulator { 
        short simulate(int arg1, long arg2, String arg3) throws ExceptionA, ExceptionB;
    } 
    
    // 假设代理类为 SimulatorProxy, 其类声明将如下
    final public class SimulatorProxy implements Simulator { 
        
        // 调用处理器对象的引用
        protected InvocationHandler handler; 
        
        // 以调用处理器为参数的构造函数
        public SimulatorProxy(InvocationHandler handler){ 
            this.handler = handler; 
        } 
        
        // 实现接口方法 simulate 
        public short simulate(int arg1, long arg2, String arg3) 
            throws ExceptionA, ExceptionB {
    
            // 第一步是获取 simulate 方法的 Method 对象
            java.lang.reflect.Method method = null; 
            try{ 
                method = Simulator.class.getMethod( 
                    "simulate", 
                    new Class[] {int.class, long.class, String.class} );
            } catch(Exception e) { 
                // 异常处理 1(略)
            } 
            
            // 第二步是调用 handler 的 invoke 方法分派转发方法调用
            Object r = null; 
            try { 
                r = handler.invoke(this, 
                    method, 
                    // 对于原始类型参数需要进行装箱操作
                    new Object[] {new Integer(arg1), new Long(arg2), arg3});
            }catch(Throwable e) { 
                // 异常处理 2(略)
            } 
            // 第三步是返回结果(返回类型是原始类型则需要进行拆箱操作)
            return ((Short)r).shortValue();
        } 
    }

    模拟推演为了突出通用逻辑所以更多地关注正常流程,而淡化了错误处理,但在实际中错误处理同样非常重要。从以上的推演中我们可以得出一个非常通用的结构化流程:第一步从代理接口获取被调用的方法对象,第二步分派方法到调用处理器执行,第三步返回结果。在这之中,所有的信息都是可以已知的,比如接口名、方法名、参数类型、返回类型以及所需的装箱和拆箱操作。

    接下来让我们把注意力重新回到先前被淡化的错误处理上来。在异常处理 1 处,由于我们有理由确保所有的信息如接口名、方法名和参数类型都准确无误,所以这部分异常发生的概率基本为零,所以基本可以忽略。而异常处理 2 处,我们需要思考得更多一些。回想一下,接口方法可能声明支持一个异常列表,而调用处理器 invoke 方法又可能抛出与接口方法不支持的异常,再回想一下先前提及的 Java 动态代理的关于异常处理的特点,对于不支持的异常,必须抛 UndeclaredThrowableException 运行时异常。所以通过再次推演,我们可以得出一个更加清晰的异常处理 2 的情况:

    清单 12. 细化的异常处理 2

    Object r = null; 
    
    try { 
        r = handler.invoke(this, 
            method, 
            new Object[] {new Integer(arg1), new Long(arg2), arg3}); 
    
    } catch( ExceptionA e) { 
    
        // 接口方法支持 ExceptionA,可以抛出
        throw e; 
    
    } catch( ExceptionB e ) { 
        // 接口方法支持 ExceptionB,可以抛出
        throw e; 
    
    } catch(Throwable e) { 
        // 其他不支持的异常,一律抛 UndeclaredThrowableException 
        throw new UndeclaredThrowableException(e); 
    }

    参考资料

    http://www.ibm.com/developerworks/cn/java/j-lo-proxy1/

    您还可能感兴趣:

    java学习笔记系列:

    java学习笔记12--异常处理

    java学习笔记11--集合总结

    java学习笔记10--泛型总结 

    java学习笔记9--内部类总结

    java学习笔记8--接口总结

    java学习笔记7--抽象类与抽象方法

    java学习笔记6--类的继承、Object类

    java学习笔记5--类的方法 

    java学习笔记4--对象的初始化与回收

    java学习笔记3--类与对象的基础 

    java学习笔记2--数据类型、数组

    java学习笔记1--开发环境平台总结

  • 相关阅读:
    高性能javascript 笔记 第一章 loading and executing (一)
    css 微信webui
    C语言之指针
    Java中的异常
    Java中的接口
    SQL Server中自连接和联合的用法
    SQL Server中多对多关系的实现
    SQL Server中用SQL命令建表和主外键约束
    SQL Server中的分页查询
    SQL Server中的内连接
  • 原文地址:https://www.cnblogs.com/wuyudong/p/java-study-note13.html
Copyright © 2011-2022 走看看