本节会深入的去理解Java反射机制,主要阐述Java反射机制的基本原理包括如何去使用数组,注解,泛型以及动态代理还有类的动态加载以及类的重载的实现。同时也会向你展示如何实现一些比较有特性的功能,比如从一个类中读取所有的get/set方法,或者访问一个类的私有变量以及私有方法。同时也会说明一些非反射相关的但是令人困惑的问题,比如哪些泛型信息在运行时是有效的,一些人声称所有的泛型信息在运行期都会消失,其实这是不对的。
Java反射机制功能强大而且非常实用。 让我们在编译期(Compile Time)之外的运行期(Runtime)检查类,接口,变量以及方法的信息。反射还可以让我们在运行期实例化对象,调用方法,通过调用get/set方法获取变量的值。互联网上已经有不胜枚举的Java反射指南,然而大多数的指南包括Sun公司所发布的反射指南中都仅仅只是介绍了一些反射的表面内容以及它的潜能。
Java Reflection:
Java反射机制可以在运行时期检查Java类的信息,检查Java类的信息往往是你在使用Java反射机制的时候所做的第一件事情,通过获取类的信息你可以获取以下相关的内容:
Class对象,类名,修饰符,包信息,父类,实现的接口,构造器,方法,变量,注解
除了上述这些内容,还有很多的信息你可以通过反射机制获得,如果你想要知道全部的信息你可以查看相应的文档https://docs.oracle.com/javase/6/docs/api/java/lang/Class.html里面有详尽的描述。
Class对象:
在你想检查一个类的信息之前,你首先需要获取类的Class对象。Java中的所有类型包括基本类型(int, long, float等等),即使是数组都有与之关联的Class类的对象。如果你在编译期知道一个类的名字的话,那么你可以使用如下的方式获取一个类的Class对象。
Class myObjectClass = MyObject.class;
如果你在编译期不知道类的名字,但是你可以在运行期获得到类名的字符串,那么你则可以这么做来获取Class对象:
String className = ... ;//在运行期获取的类名字符串
Class class = Class.forName(className);
你可以从Class对象中获取两个版本的类名。在使用Class.forName()方法时,你必须提供一个类的全名,这个全名包括类所在的包的名字。例如MyObject类位于com.jenkov.myapp包,那么他的全名就是com.jenkov.myapp.MyObject。
如果在调用Class.forName()方法时,没有在编译路径下(classpath)找到对应的类,那么将会抛出ClassNotFoundException。
类名:
通过getName() 方法返回类的全限定类名(包含包名):
Class aClass = ... //获取Class对象,具体方式可见Class对象小节
String className = aClass.getName();
如果你仅仅只是想获取类的名字(不包含包名),那么你可以使用getSimpleName()方法:
Class aClass = ... //获取Class对象,具体方式可见Class对象小节
String simpleClassName = aClass.getSimpleName();
修饰符:
可以通过Class对象来访问一个类的修饰符,即public,private,static等等的关键字,你可以使用如下方法来获取类的修饰符:
Class aClass = ... //获取Class对象,具体方式可见Class对象小节
int modifiers = aClass.getModifiers();
修饰符都被包装成一个int类型的数字,这样每个修饰符都是一个位标识(flag bit),这个位标识可以设置和清除修饰符的类型。
可以使用java.lang.reflect.Modifier类中的方法来检查修饰符的类型:
Modifier.isAbstract(int modifiers);
Modifier.isFinal(int modifiers);
Modifier.isInterface(int modifiers);
Modifier.isNative(int modifiers);
Modifier.isPrivate(int modifiers);
Modifier.isProtected(int modifiers);
Modifier.isPublic(int modifiers);
Modifier.isStatic(int modifiers);
Modifier.isStrict(int modifiers);
Modifier.isSynchronized(int modifiers);
Modifier.isTransient(int modifiers);
Modifier.isVolatile(int modifiers);
包信息:
可以使用Class对象通过如下的方式获取包信息
Class aClass = ... //获取Class对象,具体方式可见Class对象小节
Package package = aClass.getPackage();
通过Package对象你可以获取包的相关信息,比如包名,你也可以通过Manifest文件访问位于编译路径下jar包的指定信息,比如你可以在Manifest文件中指定包的版本编号。更多的Package类信息可以阅读https://docs.oracle.com/javase/6/docs/api/java/lang/Package.html
父类:
通过Class对象你可以访问类的父类,如下例:
Class superclass = aClass.getSuperclass();
可以看到superclass对象其实就是一个Class类的实例,所以你可以继续在这个对象上进行反射操作。
实现的接口:
可以通过如下方式获取指定类所实现的接口集合:
Class aClass = ... //获取Class对象,具体方式可见Class对象小节
Class[] interfaces = aClass.getInterfaces();
由于一个类可以实现多个接口,因此getInterfaces();方法返回一个Class数组,在Java中接口同样有对应的Class对象。
注意:getInterfaces()方法仅仅只返回当前类所实现的接口。当前类的父类如果实现了接口,这些接口是不会在返回的Class集合中的,尽管实际上当前类其实已经实现了父类接口。
构造器:
你可以通过如下方式访问一个类的构造方法:
Constructor[] constructors = aClass.getConstructors();
返回的Constructor数组包含每一个声明为公有的(Public)构造方法。
如果你知道你要访问的构造方法的方法参数类型,你可以用下面的方法获取指定的构造方法,这例子返回的构造方法的方法参数为String类型:如果没有指定的构造方法能满足匹配的方法参数则会抛出:NoSuchMethodException。
Class aClass = ...//获取Class对象
Constructor constructor =
aClass.getConstructor(new Class[]{String.class});
构造方法参数:
你可以通过如下方式获取指定构造方法的方法参数信息:
Constructor constructor = ... //获取Constructor对象
Class[] parameterTypes = constructor.getParameterTypes();
利用Constructor对象实例化一个类:
你可以通过如下方法实例化一个类:
Constructor constructor = MyObject.class.getConstructor(String.class);
MyObject myObject = (MyObject)
constructor.newInstance("constructor-arg1");
方法:
你可以通过如下方式访问一个类的所有方法:
Method[] method = aClass.getMethods();
方法参数以及返回类型:
你可以获取指定方法的方法参数是哪些:
Method method = ... //获取Class对象 Class[] parameterTypes = method.getParameterTypes(); 你可以获取指定方法的返回类型: Method method = ... //获取Class对象 Class returnType = method.getReturnType();
通过Method对象调用方法:
你可以通过如下方式来调用一个方法:
//获取一个方法名为doSomesthing,参数类型为String的方法
Method method = MyObject.class.getMethod("doSomething", String.class);
Object returnValue = method.invoke(null, "parameter-value1");
变量:
你可以通过如下方式访问一个类的成员变量:
Field[] field= aClass.getFields();
变量名称:
一旦你获取了Field实例,你可以通过调用Field.getName()方法获取他的变量名称,如下例
Field field = ... //获取Field对象
String fieldName = field.getName();
变量类型:
你可以通过调用Field.getType()方法来获取一个变量的类型(如String, int等等):
Field field = aClass.getField("someField");
Object fieldType = field.getType();
获取或设置(get/set)变量值:
一旦你获得了一个Field的引用,你就可以通过调用Field.get()或Field.set()方法,获取或者设置变量的值,如下例:
Class aClass = MyObject.class
Field field = aClass.getField("someField");
MyObject objectInstance = new MyObject();
Object value = field.get(objectInstance);
field.set(objetInstance, value);
传入Field.get()/Field.set()方法的参数objetInstance应该是拥有指定变量的类的实例。在上述的例子中传入的参数是MyObject类的实例,是因为someField是MyObject类的实例。
如果变量是静态变量的话(public static)那么在调用Field.get()/Field.set()方法的时候传入null做为参数而不用传递拥有该变量的类的实例
注解:
注解是Java 5的一个新特性。注解是插入你代码中的一种注释或者说是一种元数据(meta data)。这些注解信息可以在编译期使用预编译工具进行处理(pre-compiler tools),也可以在运行期使用Java反射机制进行处理。下面是一个类注解的例子:
@MyAnnotation(name="someName", value = "Hello World")
public class TheClass {
}
在TheClass类定义的上面有一个@MyAnnotation的注解。注解的定义与接口的定义相似,下面是MyAnnotation注解的定义:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyAnnotation {
public String name();
public String value();
}
在interface前面的@符号表名这是一个注解,一旦你定义了一个注解之后你就可以将其应用到你的代码中,就像之前我们的那个例子那样。
在注解定义中的两个指示@Retention(RetentionPolicy.RUNTIME)和@Target(ElementType.TYPE),说明了这个注解该如何使用。
@Retention(RetentionPolicy.RUNTIME)表示这个注解可以在运行期通过反射访问。如果你没有在注解定义的时候使用这个指示那么这个注解的信息不会保留到运行期,这样反射就无法获取它的信息。
@Target(ElementType.TYPE) 表示这个注解只能用在类型上面(比如类跟接口)。你同样可以把Type改为Field或者Method,或者你可以不用这个指示,这样的话你的注解在类,方法和变量上就都可以使用了。
类注解:
你可以在运行期访问类,方法或者变量的注解信息,下是一个访问类注解的例子:
Class aClass = TheClass.class;
Annotation[] annotations = aClass.getAnnotations();
for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}
方法注解:
下面是一个方法注解的例子:
public class TheClass {
@MyAnnotation(name="someName", value = "Hello World")
public void doSomething(){}
}
你可以像这样访问方法注解:
Method method = ... //获取方法对象
Annotation[] annotations = method.getDeclaredAnnotations();
for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}
参数注解:
方法参数也可以添加注解,就像下面这样:
public class TheClass {
public static void doSomethingElse(
@MyAnnotation(name="aName", value="aValue") String parameter){
}
}
你可以通过Method对象来访问方法参数注解:
Method method = ... //获取方法对象
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
Class[] parameterTypes = method.getParameterTypes();
int i=0;
for(Annotation[] annotations : parameterAnnotations){
Class parameterType = parameterTypes[i++];
for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("param: " + parameterType.getName());
System.out.println("name : " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}
}
变量注解:
下面是一个变量注解的例子:
public class TheClass {
@MyAnnotation(name="someName", value = "Hello World")
public String myField = null;
}
你可以像这样来访问变量的注解:
Field field = ... //获取方法对象</pre>
<pre>Annotation[] annotations = field.getDeclaredAnnotations();
for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}
动态代理:
利用Java反射机制你可以在运行期动态的创建接口的实现。java.lang.reflect.Proxy类就可以实现这一功能。这个类的名字(译者注:Proxy意思为代理)就是为什么把动态接口实现叫做动态代理。动态的代理的用途十分广泛,比如数据库连接和事物管理(transaction management)还有单元测试时用到的动态mock对象以及AOP中的方法拦截功能等等都使用到了动态代理。
创建代理:
你可以通过使用Proxy.newProxyInstance()方法创建动态代理。newProxyInstance()方法有三个参数:
1、类加载器(ClassLoader)用来加载动态代理类。
2、一个要实现的接口的数组。
3、一个InvocationHandler把所有方法的调用都转到代理上。
InvocationHandler handler = new MyInvocationHandler();
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
MyInterface.class.getClassLoader(),
new Class[] { MyInterface.class },
handler);
在执行完这段代码之后,变量proxy包含一个MyInterface接口的的动态实现。所有对proxy的调用都被转向到实现了InvocationHandler接口的handler上。有关InvocationHandler的内容会在下一段介绍。
InvocationHandler接口:
在前面提到了当你调用Proxy.newProxyInstance()方法时,你必须要传入一个InvocationHandler接口的实现。所有对动态代理对象的方法调用都会被转向到InvocationHandler接口的实现上,下面是InvocationHandler接口的定义:
public interface InvocationHandler{
Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
下面是它的实现类的定义:
public class MyInvocationHandler implements InvocationHandler{
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//do something "dynamic"
}
}
传入invoke()方法中的proxy参数是实现要代理接口的动态代理对象。通常你是不需要他的。
invoke()方法中的Method对象参数代表了被动态代理的接口中要调用的方法,从这个method对象中你可以获取到这个方法名字,方法的参数,参数类型等等信息。