Java反射机制

Java反射机制是一种动态相关机制,为Java程序提供了强大的运行时灵活性。该机制提供了很多功能,常见的功能有(参考Java反射机制):

  1. 在运行时判断任Java反射机制个对象所属的类;
  2. 在运行时构造任意一个类的对象;
  3. 在运行时判断任意一个类所具有的成员变量和方法;
  4. 在运行时调用任意一个对象的方法;
  5. 在运行时刻动态修改一个对象的成员变量;
  6. 生成动态代理。

简言之,JAVA反射机制能够在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种强大的功能使得Java反射机制得到广泛的引用;诸如原生实现AOP中的方法拦截功能,Android MultiDex中Secondary Dex的安装等。

其强大的功能和广泛的应用,深入全面学习其原理是非常必要的,这也是笔者撰写这篇博客的动机。本文是在阅读网络中现有的Java反射机制的讲解基础上,结合本人对Java反射机制源码的分析和理解编撰而成,文章从以下几个方面来介绍Java反射机制:

  1. Java反射机制的源码分析;
  2. 如何动态获取一个类的属性和方法;
  3. Java反射机制如何实现方法的动态调用;
  4. Java反射机制的常见应用.

1 Java反射机制源码分析

想要了解Java反射机制,第一步就是知道Java反射机制是什么,Java源码中是如何实现这个机制的。本节主要内容便是分析Java反射机制的源码。

1.1 Java语言元模型浅谈

依稀记得我初学Java时,从书本中看到的各种概念有类、对象实例、属性、方法、构造函数;public、static、final等修饰符。以现在的观点来看,其实这些基础的入门书籍是在向我们描述Java的一个类都是有什么元素组成,或者说Java类的基本组成元素是什么,也就是Java类的元模型。(当然,我没有在哪个参考文献上面看到关于Java类元模型的表述,如有不妥之处,还望批评指正.)

所谓的元模型,也就是模型的模型,它关注的是模型本身的属性,所有的Java工程都是由若干个类组成,可以说这个Java工程的由这些类来描述。而每个Java类也有若干个元素来描述,这些元素便是属性、方法、修饰符、注解、类型等。 Java反射机制便是提供了描述Java类的各个元素(属性、方法、修饰符、注解、类型参数)的定义。

1.2 Java反射机制源码分析

Java反射机制源码位于java.lang.reflect包中,与反射机制相关的类还有java.lang.Class、java.lang.Object、java.lang.Exception类等。Java反射机制需要在程序运行时描述一个类,因此需要包含Java类的元模型。 本小节着重分析java.lang.reflect包中的类。java.lang.reflect包共有22个类(JDK6),这些类可以归结为:Type, Member, Exception,Modifier, AnnotatedElement等元素,以及操作类InvokeHandler、Proxy等。 这几个类的继承结构如下图所示:

元素相关类

Exception相关类

其他类有Array、Modifier、Proxy、InvokeHandler、ReflectAccess、ReflectPermission.其中Array和Modifier也属于元素;Proxy用于生成动态代理,InvokeHandler用于实现方法调用;ReflectPermission是安全相关的类,ReflectAccess类是一个包级别的类,分装了创建、复制、获取Field、Method等的方法。

1.3 类详解

1.3.1 Type类族

Type类是Java语言所有类型的父接口,Type主要用于描述Field的类型、Method的参数类型、返回类型、类的泛型类型参数等。 Java语言中的类型有:

  1. 原生类型(int,long等8种类型);
  2. ParameterizedType代表参数化类型(比如HashMap);
  3. WildcardType代表通配符类型(例如<? extends Number>、<? super Integer>);
  4. GenericArrayType代表的是Array类型,并提供方法获取Array组件的类型;
  5. TypeVariable代表的是类型变量。TypeVariable是一个比较复杂的类型,通过下面一个例子可以比较好的理解这个类型:
public class Test {
    public static void main(String[] args) {
         TypeVariable[] tValue = MyType.class.getTypeParameters();
         for (TypeVariable tv : tValue) {
             System.out.println("TypeVariable = " + tv.toString());
             System.out.println("Name = " + tv.getName());
             System.out.println("GenericDeclaration = " + tv.getGenericDeclaration().toString());
         }
    }
}
public class MyType<ArrayList, String, Integer> {
}
public class MyTypeOne<T extends HashMap <String, ArrayList <Integer >>> {
}

两个案例的输出结果如下图所示:

ExampleOne

ExampleTwo

通过结果我们可以发现,TypeVariable类型表示的是类型变量,而类型变量的类型可能是ParameterizedType、TypeVariable或者Object.class类型。在TypeVariable类定义中使用了接口GenericDeclaration.源码中对该接口的解释是:定义了类型变量的实体的通用接口。

java.lang.reflect.Array提供静态方法以便动态的创建和访问Java Array。Array在get和set的过程中支持向上的类型转换(即由子类型强转成父类型);而不支持由向下的类型转换,否则会出现IllegalArgumentException。

还有一个值得指出的是Class类实现了Type和GenericDeclaration,这表示Class也是一种类型,同时也是一个可作为TypeVariablele类型参数的类。

1.3.2 AccessibleObject类族

AccessibleObject是Field、Method、Constructor类的基类。该类提供了设置/获取Member访问权限的静态方法(这个功能很重要,因为可以通过这个修改私有类成员的访问权限,进而修改这些类成员的取值);该类也简单实现了AnnotationElement中的方法等。

AccessibleObject实现AnnotatedElement接口,其中AnnotatedElement表示在虚拟机中,正在运行的程序的注解元素(注解机制请参考Java注解机制。该接口运行通过反射机制动态的获取注解;通过该Interface中的方法获取的注解是不可变的,而且是可以序列化的。Class、Package也是AnnotatedElement的子类,表示类和包都是一个可注解的元素。

Field、Method、Constructor类都是AccessibleObject的子类,且这三个类都实现了接口Member。Member接口代表的是类的成员,该接口提供方法获取Member所在的类,获取Member的Name,Modifiers,以及是否由编译器产生等。

Field类实现了Member、AnnotationElement、Object类中的相关接口;自定义方法isEnumConstant判断是否是枚举常量;自定义copy方法,复制当前Field对象。Method类与Field实现大体相同,不同的地方是Method具有参数列表,而Field没有。Constructor和Field、Member的不同之处在于它还实现了接口GenericDeclaration.这表示Constructor也可以作为TypeVariablele的类型参数。

Modifier类表示修饰符,该类中定义了各种修饰符常量,比如static、final、public等。同时该类中提供相应的静态方法,用于判断某个是否具有某种修饰符。

1.3.3 Throwable类族

Throwable类族分为Exception和Error;在Java反射机制中定义了3个Exception和1个Error。

  1. InvocationTargetException是一个已检查的Exception,在通过Invoke方式调用Method或者Constructor时可能会出现。
  2. MalformedParameterizedTypeException在parameterized类型遇到语法错误的情形时(例如类型参数不正确)抛出。
  3. UndeclaredThrowableException出现的情形是:Proxy实例的InvokeHandler抛出一个Checked Exception(不属于RuntimeExcpetion或者Error的Throwable对象);而这个Checked Excpetion也不能被Invoke的Method的try-catch捕获。此时,便会抛出一个UndeclaredThrowableException。
  4. GenericSignatureFormatError出现的情形是:因为Type、Method或者Constructor等的使用,一个可反射的方法需要解析generic签名信息时,遇到了语义残缺的签名信息时。(这些签名信息主要是出于安全考虑)

1.3.4 InvokeHandler与Proxy

这两个类是Java进行动态方法调用、生成动态代理的核心所在。

  1. InvocationHandler只定义了一个invoke方法,Proxy类中有InvocationHandler的实例引用。每个Proxy实例都会实现一个InvocationHandler,在Proxy的实例中调用方法时,也会被分发到InvocationHandler的invoke方法中;方法的调用逻辑也被硬编码在InvocationHandler的invoke方法中。
  2. java.lang.reflect.Proxy类提供静态方法,用于创建动态代理类和实例;它也是通过这些方法创建的动态代理类的SuperClass。一个proxy类(Proxy类及其子类)具有以下特性:

    • proxy类是Public、final类型的,而不是abstract类型的;
    • proxy类的名称是不受限制的;以$Proxy开头的字符串类是可接受的;
    • 一个proxy类继承自java.lang.reflect.Proxy类;
    • proxy类实现了它在创建时的所有接口,且实现顺序与创建时的顺序一致。
    • 如果一个proxy类继承自非public接口,它将被定义在于这个接口相同的包中.如果proxy类继承自public接口,则proxy的package则也是未指定的。注意package的封闭机制,在运行时不会妨碍proxy类被定义在一个特定的package中;定义在相同的class Loader中的类,以及在相同的package中具有特殊签名的class,也都不会妨碍proxy被成功定义。
    • 由于proxy类所实现的所有接口都在其创建时被定义,因而调用clazz.getInterfaces()方法将会返回包含所有接口的array,其中array中Interface的顺序与proxy创建时的顺序相同。调用clazz.getMethods方法将会返回一个Method的array,这些Method是proxy类实现的接口中的方法。
    • Proxy.isProxyClass方法可以判断Class对象对应的类是否是proxy类,如果该proxy Class是由Proxy.getProxyClass方法返回的Class对象或者由Proxy.newProxyInstance方法返回的Class对象时,则返回true,否则返回false.
    • 每个proxy类都有一个public构造函数,该构造函数有一个实现了InvokeHandler接口的参数,该参数被设置给Proxy类实例中的h成员变量。然而除了通过反射API来访问public构造器创建对象外,还可以通过调用Proxy.newProxyInstance方法来创建。
    • 一个proxy类的java.security.ProtectionDomain与通过bootstrap class Loader加载的系统类(如java.lang.Object)的ProtectionDomain值是一样的;因为proxy类的代码是由受信的系统代码创建而成的。这个受保护domain被授权的值是java.security.AllPermission.

    一个proxy类的实例proxyobj具有以下特征:

    • proxyobj是实现了Foo接口的proxy类实例,则 proxyobj instanceof Foo 返回true; (Foo) proxyobj类型强转也会成功。
    • proxyobj在创建时,会与其构造函数传递进来的InvocationHandler类实例handler关联起来。proxyobj.getInvocationHandler方法将会返回handler。
    • 在proxyobj上调用其父接口中定义的函数时,该调用会被转发到InvocationHandler.invoke方法上。
    • 在proxyobj上调用java.lang.Object上的方法(比如hashCode、equal、toString等方法)时,与proxyobj父接口中的方法调用类似,这些接口的调用也会被转发到InvocationHandler.invoke方法上,只不过invoke方法参数methodobj所在类的取值是java.lang.Object(即methodobj.getDeclaringClass() = java.lang.Object.class).而除了hashCode、equal、toString等方法,Object类中的其他方法没有被proxy类所复写,因此其他方法的调用,与在一个Object类实例上调用是一样的。
    • 当proxy类实现的父接口中,有两个或者两个以上的接口定义了相同的方法时(名称和参数列表都一样),proxy类的接口列表顺序就变得有意义了。当使用proxyobj来调用重复的方法时,传递给InvocationHandler.invoke方法的Method参数,可以不需要将其declaring class指定为Method所在的Interface。之所有存在这个限制,主要原因是在proxy类中重复方法的实现,不能确认是Interface相应方法的实现。因此,在proxyobj上面调用重复的方法时,InvocationHandler.invoke的methodobj所在的类(methodobj.getClass)被赋予的值是proxy类父Interface中的第一个定义了该方法的Interface。
    • 如果通过proxyobj调用与toString、hashCode、equal等方法(这些方法已在Object中定义,而proxy类的父Interface也定义了这些方法),那么InvokeHandler.invoke的Method参数的声明类被指定为java.lang.Object,即methodobj.getDeclaringClass() = java.lang.Object.class.

1.3.5 Class类

个人认为java.lang.Class是Java语言中,除了java.lang.Object之外最重要的类。前几个小节中对Class类的作用也有所提及,本节就详细介绍该类。 从java.lang.Class类的源码中,我们得到以下信息:

  1. Class类的实例代表的是运行中的Java应用的类和Interface。
  2. enum是一种类,而注解(annotation)是一个接口;所有的数组也属于一个类,该类是一个Class对象,而这个Class对象被类型参数相同、组维数相同的所有数组所共享。Java中的8中原生类型以及void关键字也有相应的类Class对象来表示。
  3. Class类没有public构造器;相反,在classLoader中类被加载以及被defineClass方法调用时,Class对象由JVM来创建。
  4. Class类实现了Type、GenericDeclaration、AnnotatedElement、Serializable接口。这表明,Class类是一个Type,而且可以作为TypeVariable的泛型参数使用。同时,注解Interface也是一个Class.

由于Class的方法众多,本文只是简单介绍几个重要的方法:

  1. 提供forName系列静态方法,用于根据clazzName获取Class对象;该系列函数最终都由一个native方法forName0来实现。
  2. 提供native方法获取类或者对象的特性,比如isInstance 、isAssignableFrom、isInterface、isArray、isPrimitive、isAnnotation、isSynthetic等。
  3. 提供getXXX()系列方法,获取Class类相关的各种信息,比如类的Name、SuperInterface、Modifier、Package、SuperClass、TypeParameters、ClassLoader、Field、Method、Constructor、DeclaringClass等各种信息。

2 Java反射机制常见使用

2.1 修改私有静态成员变量

本小节展示如何修改私有的静态成员变量,该方法可以在运行时修改某个成员变量的值,已达到动态修改的目的。

public class Test {
    public static void main(String[] args) {
        MyDataClass data = new MyDataClass();
        User user = MyDataClass.getUser();
        Class clazz = MyDataClass.class;

        System.out.println("data before modify id = " + MyDataClass.getId());
        System.out.println("data before modify user = " + user.getName() + " " + user.getPwd());
        System.out.println("data before modify value = " + data.getValue());

        Field[] fields = clazz.getDeclaredFields();
        for (Field f : fields) {
            if (!f.isAccessible()) {
                f.setAccessible(true);
            }
            try {
                if (f.getName() == "id") {
                    f.set(new Object(), 110);
                } else if (f.getName() == "user") {
                    if (f.get("user") instanceof User) {
                        User u = (User) f.get("user");
                        u.setName("chaman");
                        u.setPwd("110");
                    }
                } else if (f.getName() == "value") {
                    f.set(data, 233333);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        System.out.println("data after modify id = " + MyDataClass.getId());
        System.out.println("data after modify user = " + user.getName() + " " + user.getPwd());    
        System.out.println("data after modify value = " + data.getValue());
    }
}
public class MyDataClass {
    private static final User user = new User();
    private static int id = 1000;
    private int value = 0;

    public static int getId() {
        return id;
    }
    public static User getUser() {
        return user;
    }
    public int getValue() {
        return value;
    }
}
public class User {
    private String name;
    private String pwd;
    public User() {
        name = "admin";
        pwd = "123456";
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getPwd() {
        return pwd;
    }
    public void setPwd(String pwd) {
        this.pwd = pwd;
    }
}

该方法首先展示了如何通过Java反射机制动态的修改一个私有的类成员变量;一个私有的实例成员变量;同时展示了如何动态修改一个私有的静态final成员变量的内容(final变量不可变),该案例在Android开发中比较实用(比如Android的插件机制等)。 这里需要讲明以下几个问题:

Class类中getFields()与getDeclaredFields两个方法的异同点:

  1. 两个方法都返回一个Field数组;
  2. getDeclaredFields()方法返回的是Class对应的类中定义的Field,不包括该类继承得到的Field;这些Field包括public, protected, default, private类型的所有Field;但返回的数组中,各个Field之间的顺序是不定的。如果Class中没有定义Field或者Class对应的类是原生类型、数组类型或者Void类型的话,返回的Field数组的length都会为0.
  3. getFields()返回的是Class所能够访问的所有的public Field,包括所有的祖先类、祖先接口中定义的public Field。如果类中没有可访问的public Field,或者是原生类型、数组类型或者Void类型,返回的Field数组的length则为0.
  4. 需要特别指出的是,如果Class代表的是一个类的话,则getFields()返回的是所有该类及其所有祖先类中定义的public Field;如果Class代表的是Interface的话,则getFields()返回的是该接口及其所有祖先接口中定义的public Field。
  5. 数组类中隐含的length Field不能通过getFields()反射获得;用户应当使用Array类中的相关方法获得length域。
  6. 通过getFields()、getDeclaredFields()方法获得的Field的accessible属性取值都是false。

Class类还提供方法getField(String name)和getDeclaredField(String name)两个方法来直接通过Name来获取Field:

  1. getField(String name)方法获取的是clazz代表的类或者接口中的public Field。查找的顺序是:首先在调用的类中查找,如果找到,则返回该Field,否则在直接父类;如果在直接父类中找到,则返回,否则在直接父接口中查找;在直接父接口中的查找顺序与Class实现时父接口的顺序一致;如果找到则返回,如果未找到则在祖先中递归查找,找到这返回,否则抛出异常 NoSuchFieldException。
  2. getDeclaredField(String name)返回的是clazz对象所对应的类或者接口中定义的Field(但是不会返回数据的length Field)。

第二个需要讲明是,类成员变量和实例成员变量的修改方式有所差异:对于类成员变量,field.set(obj, value)中的obj不需要特意指定,可以是任意一个Object对象;而对于实例成员变量而言,field.set(obj,value)中的obj必须是想要修改的实例。

2.2 反射机制调用方法

话不多说直接看示例代码:

public class Test {
    public static void main(String[] args) {
        try {
            Constructor constructor = MyMethod.class.getConstructor(int.class); 
            MyMethod obj = (MyMethod) constructor.newInstance(10); 
            Method increase = MyMethod.class.getMethod("increase", int.class); 
            increase.invoke(obj, 5);
            Field field = MyMethod.class.getDeclaredField("count"); 
            field.setAccessible(true);
            System.out.println("count = " + field.getInt(obj));
            Method print = SuperMethod.class.getMethod("print", null);
            print.invoke(new SuperMethod(), null);
        } catch (Exception e) { 
            e.printStackTrace();
        } 
    }
}
public class MyMethod extends SuperMethod {
    private int count;
    public MyMethod(int start) {
        count = start;
    }
    public void increase(int step) {
        System.out.println(MyMethod.class.getSimpleName() + " " + "increase");
        count = count + step;
    }
    @Override
    public void print() {
        System.out.println(MyMethod.class.getSimpleName() + " " + "print");
    }
}
public class SuperMethod {
    public void print() {
        System.out.println(SuperMethod.class.getSimpleName() + " " + "print");
    }
}
输出结果如下:
MyMethod increase
MyMethod print
SuperMethod print
count = 15

本案例主要说明展示如何通过反射机制进行方法调用;以及进行方法调用时,Java多态的问题。 Class的getMethod(String name, Class<?>... parameterTypes)具有以下特征:

  1. 该方法返回的是clazz所代表的类或者接口中特定的public方法。这个Method的由name和parameterTypes共同确定,这两个变量也就是我们常说的方法描述符。
  2. 其中parameterTypes是一个Class类型的数组,数组中元素的顺序需要与方法定义的参数列表的顺序保持一致。如果parameterTypes为null,表示不需要参数的Method。
  3. getMethod方法在查找类的时候具体的算法是:首先在clazz类中查找,如果有该方法,则结束查找;如果没有该方法在递归地在该类的祖先类中查找,如果有该方法,则结束查找;如果在祖先类中没有查找到该方法,则在祖先接口中查找,直到找到为止。如果都没有找到,则抛出NoSuchMethodException。
  4. getMethod(...)的name如果为或者时,会抛出NoSuchMethodException。

Class类的getDeclaredMethod(String name, Class<?>... parameterTypes)获取的是clazz代表的类或者接口中定义的所有的方法(包括private、protected、default、public)。

而getMethods()方法和getDeclaredMethods()方法的异同点,与getFields()和getDeclaredFields()方法的异同点类似。这里就不再赘述。

方法的具体调用是通过Method类中的invoke方法实现的;在invoke方法中,通过MethodAccessor.invoke方法来实现最终的调用。通过阅读OpenJDK源码MethodAccessor以及其实现子类NativeMethodAccessorImpl(该类实用的是典型的代理模式),发现方法调用最终交由native方法实现。其中JVM以及Dalvik虚拟机的构造以及运行机制,将在文章VirtualMachine中进行详细分析。

2.3 Java动态代理机制

2.3.1 动态代理使用

本小节主要展示的是Java动态代理的使用方式,以及展示Proxy代理类的一些特征。请看如下示例:

public class TestProxy {
    public static void main(String[] args) {
        BookFacadeProxy proxy = new BookFacadeProxy();  
        BookFacade bookProxy = (BookFacade) proxy.bind(new BookFacadeImpl());  
        System.out.println("Class Name = " + bookProxy.getClass().getName());
        System.out.println("isProxyClass = " + Proxy.isProxyClass(bookProxy.getClass()));
        bookProxy.addBook();  
    }
}
interface BookFacade {
    public void addBook();
}
public class BookFacadeImpl implements BookFacade{
    @Override
    public void addBook() {
        System.out.println("addBook method is invoked!");
    }
}
public class BookFacadeProxy implements InvocationHandler{
    private Object target;
    public Object bind(Object target) {  
        this.target = target;  
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),  
                target.getClass().getInterfaces(), this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object result=null;  
        System.out.println("InvocationHandler invoke start!");  
        result = method.invoke(target, args);  
        System.out.println("InvocationHandler invoke end!");  
        return result;
    }
}
输出结果如下所示:
Class Name = zju.chk.example.proxy.$Proxy0
isProxyClass = true
InvocationHandler invoke start!
addBook method is invoked!
InvocationHandler invoke end!

本实例主要说明了以下几个问题

  1. 关于动态代理类的package问题:本例中的proxy类实现了接口BookFacade,而BookFacade是一个包级别访问的接口,因而proxy类的包为BookFacade所在的包(详细信息可以参考1.3.4小节)。
  2. 动态代理类方法的调用是由InvocationHandler的invoke方法来实现的。这种方式也为我们在方法调用前后进行特殊操作提供了方便,即通过动态代理机制Hook相应的方法,然后在InvocationHandler.invoke方法中完成额外的操作。这个是Java动态代理机制的重要应用领域。
  3. 通过Proxy.isProxyClass方法可以判断一个类是否是动态代理类。
  4. 将Java动态代理机制放在java.lang.reflect包中,刚开始的时候,总觉得怪怪的,不过在使用动态代理的使用来讲,却也有几分道理。毕竟在创建Proxy实例时,不论Proxy.newInstance()方法,还是通过反射机制查找到构造方法,最终都是使用的Java反射机制;同时Proxy实例在进行方法调用时,最终还是通过Method的反射来实现的。这两点又是动态代理的核心所在,这么看来就合情合理了。

2.3.2 静态代理和动态代理比较

静态代理机制和动态代理机制,这两种机制的实用案例,在本文前面章节也都有提及。比如MethodAccess实现时,实用的是静态代理机制。本小节就首先介绍一下代理机制,然后简单讲一下静态代理机制和动态代理机制的区别。

代理机制其实在生活中很常见,这里举几个例子说明一下代理机制:

1.储户去银行取钱,储户告诉前台柜员要取多少钱;然后如果数额巨大,柜员就会告诉银行行长,最终由银行行长到小金库去取钱。在本例中,银行行长负责真正的取钱操作,其他与储户打交道等琐事则有柜员完成;这里的柜员就是银行行长在存储业务方面的一个代理而已,真正访问小金库有行长完成(想想如果每个柜员都能访问小金库会发生什么)。这里的代理主要用户控制稀有资源的访问。 2.讲讲VPS翻墙的事情。 当我们需要国外某某著名老师的视频教学时,发现国内无法直接访问该网站W;无法获取该视频资源。但是,我们在国外有个自己的云服务器S;我们能够访问云服务器S,而云服务器S的可以访问网站W。那么我们就可以借助诸如Chrome+SwitchOmega+SSH等工具组合的方式,将我们的访问请求转由云服务器S来完成。这里的代理主要完成的事情是,做被代理者(国内网络)所不能做的事情。

这里的代理呢,其实都是静态代理,也就是在使用之前就已经确定好了彼此之间的关系:去小金库取钱,由行长完成;访问国外的某些网站由国外云服务器完成。说的更程序员化一点呢,就是Proxy类和RealObject类都已经指定好了,在编译之前已经确定知道了。

动态代理呢,就是实现不知道具体的工作由谁来完成,只是在具体实施的过程中才确定。比如我们去打印店打印,这个打印店规模大有好多台打印机,只有在具体打印时,我们才知道具体的打印工作由谁完成。

2.3.3 CGLIB简介

Java动态代理机制有一个限制:只能动态代理实现了接口的类,而未实现接口的类就不能实现JDK的动态代理。如果强制使用未实现接口的类来创建代理类,则会抛出java.lang.IllegalArgumentException异常。 而CGLIB是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。CGLIB的使用案例,可以参考博文java动态代理(JDK和cglib).

3 Java反射机制的应用

由于Java反射机制以及动态代理机制的灵活性,其应用范围非常广泛。由于本人才疏学浅,见识有限,只能罗列一些本人接触过的一些应用。这些应用有:

  1. Android MultiDex机制使用Java反射机制。在MultiDex完成了次要Dex文件的解析和安装之后,需要修改dexElements Field来将次要Dex的信息告知系统(具体的实现机制可参考文章Android MultiDex机制).
  2. Android的插件化机制是借助Java反射机制和Java动态代理机制实现,具体可参见博文Android插件化原理解析——Hook机制之动态代理.
  3. 使用Java动态代理机制实现AOP机制。

4 小结

本文首先详细介绍了java.lang.reflect各个类的实现,以及这些类之间的关系。在第二节中,又通过案例介绍了如何利用Java发射机制修改Field,调用Method,以及如何使用动态代理机制(期间还简要介绍了代理机制)。在第三节又简单介绍了Java反射机制和动态代理机制在实际应用中的使用。 其实Java反射机制使用的内容还有很多,比如Java反射机制对性能的影响(可参考Java编程 的动态性,第 2部分: 引入反射);以及反射机制与Java安全机制的关系等。Java反射机制对性能要求较高,因此在对性能要求高的地方,要慎重考虑反射机制的使用。

参考文献

  1. Java编程 的动态性,第 2部分: 引入反射
  2. Java反射机制
  3. java动态代理(JDK和cglib)
  4. Java反射机制详解
  5. Android插件化原理解析——Hook机制之动态代理
  6. The Power of Proxies in Java
  7. 反射(Reflection)

results matching ""

    No results matching ""