Java 反射机制
Reflect
在计算机学中,反射式编程(英语:reflective programming)或反射(英语:reflection),是指计算机程序在运行时(runtime)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。
要注意术语“反射”和“内省”(type introspection)的关系。内省(或称“自省”)机制仅指程序在运行时对自身信息(称为元数据)的检测;反射机制不仅包括要能在运行时对程序自身信息进行检测,还要求程序能进一步根据这些信息改变程序状态或结构。
- 反射让开发人员可以通过外部类的全路径名创建对象,并使用这些类,实现一些扩展的功能。
- 反射让开发人员可以枚举出类的全部成员,包括构造函数、属性、方法。以帮助开发者写出正确的代码。
- 测试时可以利用反射 API 访问类的私有成员,以保证测试代码覆盖率。
也就是说,Oracle 希望开发者将反射作为一个工具,用来帮助程序员实现本不可能实现的功能(perform operations which would otherwise be impossible)。正如《人月神话》一书中所言:软件工程没有银弹。很多程序架构,尤其是三方框架,无法保证自己的封装是完美的。如果没有反射,对于外部类的私有成员,我们将一筹莫展,所以我们有了反射这一后门,为程序设计提供了更大的灵活性。工具本身并没有错,关键在于如何正确地使用。
Java 反射 API
-
Class反射对象描述类语义结构,可以从Class对象中获取构造函数、成员变量、方法类等类元素的反射对象,并以编程的方式通过这些反射对象对目标类对象进行操作。这些反射对象类在java.reflect包中定义
-
Constructor:类的构造函数反射类,通过Class#getConstructors()方法可以获得类的所有构造函数反射对象数组。在JDK5.0中,还可以通过getConstructor(Class… parameterTypes)获取拥有特定入参的构造函数反射对象。Constructor的一个主要方法是newInstance(Object[] initargs),通过该方法可以创建一个对象类的实例,相当于new关键字。在JDK5.0中该方法演化为更为灵活的形式:newInstance (Object… initargs)。
-
Method:类方法的反射类,通过Class#getDeclaredMethods()方法可以获取类的所有方法反射类对象数组Method[]。在JDK5.0中可以通过getDeclaredMethod(String name, Class… parameterTypes)获取特定签名的方法,name为方法名;Class…为方法入参类型列表。Method最主要的方法是invoke(Object obj, Object[] args),obj表示操作的目标对象;args为方法入参,代码清单3 10③处演示了这个反射类的使用方法。在JDK 5.0中,该方法的形式调整为invoke(Object obj, Object… args)。此外,Method还有很多用于获取类方法更多信息的方法:
-
1)Class getReturnType():获取方法的返回值类型;
-
2)Class[] getParameterTypes():获取方法的入参类型数组;
-
3)Class[] getExceptionTypes():获取方法的异常类型数组;
-
4)Annotation[][] getParameterAnnotations():获取方法的注解信息,JDK 5.0中的新方法;
-
Field:类的成员变量的反射类,通过Class#getDeclaredFields()方法可以获取类的成员变量反射对象数组,通过Class#getDeclaredField(String name)则可获取某个特定名称的成员变量反射对象。Field类最主要的方法是set(Object obj, Object value),obj表示操作的目标对象,通过value为目标对象的成员变量设置值。如果成员变量为基础类型,用户可以使用Field类中提供的带类型名的值设置方法,如setBoolean(Object obj, boolean value)、setInt(Object obj, int value)等。
Class
除了 int
等基本类型外,Java 的其他类型全部都是 class(包括 interface)。
class(包括interface)的本质是数据类型(Type)。
每加载一种 class
,JVM 就为其创建一个 Class
类型的实例,并关联起来。注意:这里的 Class
类型是一个名叫 Class
的 class
。
|
|
由于JVM为每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果获取了某个Class实例,我们就可以通过这个Class实例获取到该实例对应的class的所有信息。
获取 Class 类对象有三种方法
|
|
-
使用 Class.forName 静态方法。当你知道该类的全路径名时,你可以使用该方法获取 Class 类对象。
-
使用 .class 方法。这种方法只适合在编译前就知道操作的 Class。
-
使用类对象的 getClass() 方法。
-
JVM为每个加载的class及interface创建了对应的Class实例来保存class及interface的所有信息;
-
获取一个class对应的Class实例后,就可以获取该class的所有信息;
-
通过Class实例获取class信息的方法称为反射(Reflection);
-
JVM总是动态加载class,可以在运行期根据条件来控制加载class。
访问字段
Class类提供了以下几个方法来获取字段:
- Field getField(name):根据字段名获取某个public的field(包括父类)
- Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
- Field[] getFields():获取所有public的field(包括父类)
- Field[] getDeclaredFields():获取当前类的所有field(不包括父类)
Java的反射API提供的Field类封装了字段的所有信息:
- 通过Class实例的方法可以获取Field实例:getField(),getFields(),getDeclaredField(),getDeclaredFields();
- 通过Field实例可以获取字段信息:getName(),getType(),getModifiers();
- 通过Field实例可以读取或设置某个对象的字段,如果存在访问限制,要首先调用setAccessible(true)来访问非public字段。
- 通过反射读写字段是一种非常规方法,它会破坏对象的封装。
调用方法
Class类提供了以下几个方法来获取Method:
- Method getMethod(name, Class…):获取某个public的Method(包括父类)
- Method getDeclaredMethod(name, Class…):获取当前类的某个Method(不包括父类)
- Method[] getMethods():获取所有public的Method(包括父类)
- Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)
Java的反射API提供的Method对象封装了方法的所有信息:
- 通过Class实例的方法可以获取Method实例:getMethod(),getMethods(),getDeclaredMethod(),getDeclaredMethods();
- 通过Method实例可以获取方法信息:getName(),getReturnType(),getParameterTypes(),getModifiers();
- 通过Method实例可以调用某个对象的方法:Object invoke(Object instance, Object… parameters);
- 通过设置setAccessible(true)来访问非public方法;
- 通过反射调用方法时,仍然遵循多态原则。
调用构造函数
通常使用new操作符创建新的实例:
Person p = new Person();
如果通过反射来创建新的实例,可以调用Class提供的newInstance()方法:
Person p = Person.class.newInstance();
调用Class.newInstance()的局限是,它只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。
|
|
Constructor对象封装了构造方法的所有信息;
- 通过Class实例的方法可以获取Constructor实例:getConstructor(),getConstructors(),getDeclaredConstructor(),getDeclaredConstructors();
- 通过Constructor实例可以创建一个实例对象:newInstance(Object… parameters); 通过设置setAccessible(true)来访问非public构造方法。
获取继承关系
获取父类的Class
|
|
Integer的父类类型是Number,Number的父类是Object,Object的父类是null。
获取 interface
|
|
继承关系
当我们判断一个实例是否是某个类型时,正常情况下,使用instanceof操作符:
|
|
如果是两个Class实例,要判断一个向上转型是否成立,可以调用isAssignableFrom():
|
|
-
通过Class对象可以获取继承关系:
- Class getSuperclass():获取父类类型;
- Class[] getInterfaces():获取当前类实现的所有接口。
-
instanceof操作,判断一个实例是否是某个类型。
-
通过Class对象的isAssignableFrom()方法可以判断一个向上转型是否可以实现。
|
|
动态代理
有没有可能不编写实现类,直接在运行期创建某个interface的实例呢?
这是可能的,因为Java标准库提供了一种动态代理(Dynamic Proxy)的机制:可以在运行期动态创建某个interface的实例。
JDK的动态代理主要涉及到java.lang.reflect包中的两个类:Proxy和InvocationHandler。其中InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编织在一起。
而Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。
|
|
在运行期动态创建一个interface实例的方法如下:
- 定义一个InvocationHandler实例,它负责实现接口的方法调用;
- 通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:
- 使用的ClassLoader,通常就是接口类的ClassLoader;
- 需要实现的接口数组,至少需要传入一个接口进去;
- 用来处理接口方法调用的InvocationHandler实例。
- 将返回的Object强制转型为接口。
动态代理实际上是JVM在运行期动态创建class字节码并加载的过程。
Java标准库提供了动态代理功能,允许在运行期动态创建一个接口的实例;
动态代理是通过Proxy创建代理对象,然后将接口方法“代理”给InvocationHandler完成的。
Spring 中的反射
Java 反射性能优化
-
对于method/field/constructor的invoke这类的反射,设置 “setAccessible(true)”
-
当然了,将得到的method/field/constructor对象做缓存这是基本的