编辑推荐: |
本文重点介绍了如何通过反射获取到某个类的方法、成员变量、构造函数等信息,同时也介绍动态代理的用法,这些都是反射的基础功能,反射的其他功能里就不一一介绍了。
本文来自于Vi的技术博客,由火龙果软件Anna编辑、推荐。 |
|
反射是java提供的一个重要功能,可以在运行时检查类、接口、方法和变量等信息,无需知道类的名字,方法名等。还可以在运行时实例化新对象,调用方法以及设置和获取变量值。
反射非常强大和有用,很多java框架中都有反射的影子,例如spring、mybatis等等,
JDBC利用反射将数据库的表字段映射到java对象的getter/setter方法。
Jackson, GSON, Boon等类库也是利用反射将JSON文件的属性映射到java对的象getter/setter方法。
可见,只要使用java,反射就无处不在。
Class对象
检查一个类之前,必须获取到java.lang.Class对象,java中的所有类型,包括long,int,数组等基本数据类型,都和Class对象有关系。
我们很多人去医院参加体检的时候,都做过B超检查,医生只需把一个探头在我们身上滑动就可以将我们体内的肝、胆、肾等器官反射到B超设备上显示。
Class类对象就相当于B超的探头,将一个类的方法、变量、接口、类名、类修饰符等信息告诉运行的程序。
Java提供了两种方式获取Class对象,一种是使用.class,另外一种是使用Class.forName()。
.class方式适用于在编译时已经知道具体的类。
Class alunbarClass
= Alunbar.class;
|
Class.forName()方式适用于运行时动态获取Class对象,只需将类名作为forName方法的参数:
try{
Class alunbarClass1 = Class.forName("Alunbar");
}
catch(ClassNotFoundException e){
System.out.println("找不到Alunbar类");
} |
这个方法会出现类找不到的情况,因此使用这个方法获取Class对象时,必须捕获ClassNotFoundException异常。
获取类名
package cn.alunbar;
public class Alunbar {
public static void main(String arts[]){
Class alunbarClass = Alunbar.class;
System.out.println (alunbarClass.getName());
System.out.println (alunbarClass.getSimpleName());
}
} |
上面代码运行结果如下:
cn.alunbar.Alunbar
Alunbar
|
getName()方法获取的类名包含包信息。getSimpleName()方法只是获取类名,不包含包信息。
获取类修饰符
public class
Alunbar {
public static void main(String arts[]){
Class alunbarClass = Alunbar.class;
System.out.println(alunbarClass.getModifiers());
System.out.println (Modifier.isPublic (alunbarClass.getModifiers()));
Class birdClass = Bird.class;
System.out.println(birdClass.getModifiers());
System.out.println (Modifier.isPublic (birdClass.getModifiers())); } private class Bird{ }
} |
类修饰符有public、private等类型,getModifiers()可以获取一个类的修饰符,但是返回的结果是int,结合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) |
获取包信息
package cn.alunbar;
public class Alunbar {
public static void main(String arts[]){ Class birdClass = Bird.class;
System.out.println(birdClass.getPackage()); } private class Bird{ }
} |
getPackage()方法获取包信息。上面代码运行的结果:
获取父类的Class对象
public class
Alunbar {
public static void main(String arts[]){ Class birdClass = Bird.class;
Class superclass = birdClass.getSuperclass();
System.out.println(superclass.getSimpleName());
} private class Bird extends Animal{ } private class Animal{ }
} |
上面代码运行的结果:
getSuperclass()方法返回的父类的Class对象。
获取接口信息
获取接口信息的方法:
Class[] interfaces
= birdClass.getInterfaces(); |
一个类可以实现多个接口,所以getInterfaces()方法返回的是Class[]数组。 注意:getInterfaces()只返回指定类实现的接口,不会返父类实现的接口。
获取构造函数Constructor
获取构造函数的方法:
Class birdClass
= Bird.class;
Constructor[] constructors = birdClass.getConstructors(); |
一个类会有多个构造函数,getConstructors()返回的是Constructor[]数组,包含了所有声明的用public修饰的构造函数。
如果你已经知道了某个构造的参数,可以通过下面的方法获取到回应的构造函数对象:
public class
Alunbar {
public static void main(String arts[]){ Class birdClass = Bird.class;
try{
Constructor constructors = birdClass.getConstructor(new
Class[]{String.class});
}catch(NoSuchMethodException e){ }
} private class Bird {
public Bird(){ } public Bird(String eat){ }
}
} |
上面获取构造函数的方式有2点需要注意:
1、只能获取到public修饰的构造函数。
2、需要捕获NoSuchMethodException异常。
获取构造函数的参数
获取到构造函数的对象之后,可以通过getParameterTypes()获取到构造函数的参数。
Constructor
constructors = birdClass.getConstructor(new
Class[]{String.class});
Class[] parameterTypes = constructors.getParameterTypes();
|
初始化对象
通过反射获取到构造器之后,通过newInstance()方法就可以生成类对象。
public class
Alunbar {
public static void main(String arts[]){ Class birdClass = Bird.class;
try{
Constructor constructors = birdClass.getConstructor(new
Class[]{String.class});
Bird bird = (Bird)constructors.newInstance ("eat
tea"); }catch(Exception e){
System.out.println("没有对应的构造函数");
}
} class Bird {
public Bird(){ } protected Bird(String eat){ }
}
} |
newinstance()方法接受可选数量的参数,必须为所调用的构造函数提供准确的参数。如果构造函数要求String的参数,在调用newinstance()方法是,必须提供String类型的参数。
获取Methods方法信息
下面代码是通过反射可以获取到该类的声明的成员方法信息:
Method[] metchods
= birdClass.getMethods();
Method[] metchods1 = birdClass.getDeclaredMethods();
Method eatMetchod = birdClass.getMethod ("eat",
new Class[]{int.class});
Method eatMetchod1 = birdClass.getDeclaredMethod
("eat", new Class[]{int.class});
|
无参的getMethods()获取到所有public修饰的方法,返回的是Method[]数组。 无参的getDeclaredMethods()方法到的是所有的成员方法,和修饰符无关。
对于有参的getMethods()方法,必须提供要获取的方法名以及方法名的参数。如果要获取的方法没有参数,则用null替代:
Method eatMetchod
= birdClass.getMethod("eat", null); |
无参的getMethods()和getDeclaredMethods()都只能获取到类声明的成员方法,不能获取到继承父类的方法。
获取成员方法参数
Class birdClass
= Bird.class;
Class[] parameterTypes = eatMetchod1.getParameterTypes(); |
获取成员方法返回类型
Class birdClass
= Bird.class;
Class returnType = eatMetchod1.getReturnType(); |
invoke()方法
java反射提供invoke()方法,在运行时根据业务需要调用相应的方法,这种情况在运行时非常常见,只要通过反射获取到方法名之后,就可以调用对应的方法:
Class birdClass
= Bird.class;
Constructor constructors1 = birdClass.getConstructor();
Method eatMetchod = birdClass.getMethod ("eat",
new Class[]{int.class});
System.out.println (eatMetchod.invoke (constructors1.newInstance(),
2)); |
invoke方法有两个参数,第一个参数是要调用方法的对象,上面的代码中就是Bird的对象,第二个参数是调用方法要传入的参数。如果有多个参数,则用数组。
如果调用的是static方法,invoke()方法第一个参数就用null代替:
public class
Alunbar {
public static void main(String arts[]){
try{
Class birdClass = Bird.class;
Constructor constructors1 = birdClass.getConstructor();
Method eatMetchod = birdClass.getMethod ("eat",
new Class[]{int.class});
System.out.println (eatMetchod.invoke(null, 2));
}catch(Exception e){
e.printStackTrace();
System.out.println("没有对应的构造函数");
}
} } class Bird{
public static int eat(int eat){
return eat;
}
public Bird(){ } public Bird(String eat){ } private void talk(){}
} class Animal{
public void run(){ }
} |
使用反射可以在运行时检查和调用类声明的成员方法,可以用来检测某个类是否有getter和setter方法。getter和setter是java
bean必须有的方法。 getter和setter方法有下面的一些规律: getter方法以get为前缀,无参,有返回值
setter方法以set为前缀,有一个参数,返回值可有可无, 下面的代码提供了检测一个类是否有getter和setter方法:
public static
void printGettersSetters(Class aClass){
Method[] methods = aClass.getMethods(); for(Method method : methods){
if(isGetter(method)) System.out.println ("getter:
" + method);
if(isSetter(method)) System.out.println ("setter:
" + method);
}
}
public static boolean isGetter (Method method){
if(!method.getName().startsWith("get"))
return false;
if(method.getParameterTypes( ).length != 0) return
false;
if(void.class.equals (method.getReturnType())
return false;
return true;
} public static boolean isSetter (Method method){
if(!method.getName().startsWith ("set"))
return false;
if(method.getParameterTypes( ).length != 1)
return false;
return true;
} |
获取成员变量
通过反射可以在运行时获取到类的所有成员变量,还可以给成员变量赋值和获取成员变量的值。
Class birdClass
= Bird.class;
Field[] fields1 = birdClass.getFields();
Field[] fields2 = birdClass.getDeclaredFields();
Field fields3 = birdClass.getField("age");
Field fields4 = birdClass.getDeclaredField("age");
|
getFields()方法获取所有public修饰的成员变量,getField()方法需要传入变量名,并且变量必须是public修饰符修饰。
getDeclaredFields方法获取所有生命的成员变量,不管是public还是private。
获取成员变量类型
Field fields4
= birdClass.getDeclaredField("age");
Object fieldType = fields4.getType(); |
成员变量赋值和取值
一旦获取到成员变量的Field引用,就可以获取通过get()方法获取变量值,通过set()方法给变量赋值:
Class birdClass
= Bird.class;
Field fields3 = birdClass.getField("age");
Bird bird = new Bird();
Object value = fields3.get(bird);
fields3.set(bird, value); |
访问私有变量
有很多文章讨论禁止通过反射访问一个对象的私有变量,但是到目前为止所有的jdk还是允许通过反射访问私有变量。
使用 Class.getDeclaredField (String
name)或者Class.getDeclaredFields()才能获取到私有变量。
package field; import java.lang.reflect.Field; public class PrivateField {
protected String name; public PrivateField(String name){
this.name = name;
}
} public class PrivateFieldTest {
public static void main (String args[])throws
Exception{
Class privateFieldClass = PrivateField.class;
Field privateName = privateFieldClass.getDeclaredField ("name");
privateName.setAccessible(false);
PrivateField privateField = new PrivateField ("Alunbar");
String privateFieldValue = (String) privateName.get (privateField);
System.out.println ("私有变量值:" + privateFieldValue);
}
} |
上面的代码有点需要注意:必须调用setAccessible(true)方法,这是针对私有变量而言,public和protected等都不需要。这个方法是允许通过反射访问类的私有变量。
访问私有方法
和私有变量一样,私有方法也是不允许其他的类随意调用的,但是通过反射可以饶过这一限制。
使用Class.getDeclaredMethod(String name, Class[] parameterTypes)或者Class.getDeclaredMethods()方法获取到私有方法。
public class
PrivateMethod {
private String accesPrivateMethod(){
return "成功访问私有方法";
}
} public class PrivateMethodTest {
public static void main (String args[])throws
Exception{
Class privateMethodClass = PrivateMethod.class; Method privateStringMethod = privateMethodClass.getDeclaredMethod
("accesPrivateMethod", null);
privateStringMethod.setAccessible(true);
String returnValue = (String) privateStringMethod.invoke(new
PrivateMethod(), null); System.out.println ("returnValue = "
+ returnValue);
}
} |
和访问私有变量一样,也要调用setAccessible(true)方法,允许通过反射访问类的私有方法。
访问类注解信息
通过反射可以在运行时获取到类、方法、变量和参数的注解信息。
访问类的所有注解信息:
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());
}
} |
访问类特定的注解信息:
Class aClass
= TheClass.class;
Annotation annotation = aClass.getAnnotation(MyAnnotation.class); if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
} |
访问方法注解信息:
Method method
= ... //obtain method object
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());
}
} |
访问特定方法注解信息:
Method method
= ... // obtain method object
Annotation annotation = method.getAnnotation(MyAnnotation.class); if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
} |
访问参数注解信息:
Method method
= ... //obtain method object
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());
}
}
} |
Method.getParameterAnnotations()方法返回的是一个二维的Annotation数组,其中包含每个方法参数的注解数组。
访问类所有变量注解信息:
Field field
= ... //obtain field object
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());
}
} |
访问类某个特定变量的注解信息:
Field field
= ... // obtain method object
Annotation annotation = field.getAnnotation(MyAnnotation.class); if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
} |
获取泛型信息
很多人认为java类在编译的时候会把泛型信息给擦除掉,所以在运行时是无法获取到泛型信息的。其实在某些情况下,还是可以通过反射在运行时获取到泛型信息的。
获取到java.lang.reflect.Method对象,就有可能获取到某个方法的泛型返回信息。
泛型方法返回类型
下面的类中定义了一个返回值中有泛型的方法:
public class
MyClass { protected List<String> stringList =
...; public List<String> getStringList(){
return this.stringList;
}
} |
下面的代码使用反射检测getStringList()方法返回的是List<String>而不是List
Method method
= MyClass.class.getMethod ("getStringList",
null); Type returnType = method.getGenericReturnType(); if (returnType instanceof ParameterizedType){
ParameterizedType type = (ParameterizedType)
returnType;
Type[] typeArguments = type.getActualTypeArguments();
for(Type typeArgument : typeArguments){
Class typeArgClass = (Class) typeArgument;
System.out.println("typeArgClass = "
+ typeArgClass);
}
} |
上面这段代码会打印:typeArgClass = java.lang.String
泛型方法参数类型
下面的类定义了一个有泛型参数的方法setStringList():
public class
MyClass {
protected List<String> stringList = ...;
public void setStringList (List<String>
list){
this.stringList = list;
}
} |
Method类提供了getGenericParameterTypes()方法获取方法的泛型参数。
method = Myclass.class.getMethod
("setStringList", List.class); Type[] genericParameterTypes = method.getGenericParameterTypes(); for(Type genericParameterType : genericParameterTypes){
if(genericParameterType instanceof ParameterizedType){
ParameterizedType aType = (ParameterizedType)
genericParameterType;
Type[] parameterArgTypes = aType.getActualTypeArguments();
for(Type parameterArgType : parameterArgTypes){
Class parameterArgClass = (Class) parameterArgType;
System.out.println ("parameterArgClass =
" + parameterArgClass);
}
}
} |
上面的代码会打印出parameterArgType = java.lang.String
泛型变量类型
通过反射也可以获取到类的成员泛型变量信息——静态变量或实例变量。下面的类定义了一个泛型变量:
public class
MyClass {
public List<String> stringList = ...;
}
|
通过反射的Filed对象获取到泛型变量的类型信息:
Field field
= MyClass.class.getField("stringList"); Type genericFieldType = field.getGenericType(); if(genericFieldType instanceof ParameterizedType){
ParameterizedType aType = (ParameterizedType)
genericFieldType;
Type[] fieldArgTypes = aType.getActualTypeArguments();
for(Type fieldArgType : fieldArgTypes){
Class fieldArgClass = (Class) fieldArgType;
System.out.println("fieldArgClass = "
+ fieldArgClass);
}
} |
Field对象提供了getGenericType()方法获取到泛型变量。 上面的代码会打印出:fieldArgClass
= java.lang.String
动态代理
使用反射可以在运行时创建接口的动态实现,java.lang.reflect.Proxy类提供了创建动态实现的功能。我们把运行时创建接口的动态实现称为动态代理。
动态代理可以用于许多不同的目的,例如数据库连接和事务管理、用于单元测试的动态模拟对象以及其他类似aop的方法拦截等。
创建代理
调用java.lang.reflect.Proxy类的newProxyInstance()方法就可以常见动态代理,newProxyInstance()方法有三个参数:
1、用于“加载”动态代理类的类加载器。
2、要实现的接口数组。
3、将代理上的所有方法调用转发到InvocationHandler的对象。
代码如下:
InvocationHandler
handler = new MyInvocationHandler();
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
MyInterface.class.getClassLoader(),
new Class[] { MyInterface.class },
handler);
|
运行上面代码后,proxy变量包含了MyInterface接口的动态实现。
对代理的所有调用都将由到实现了InvocationHandler接口的handler 对象来处理。
InvocationHandler
如上面说的一样,必须将InvocationHandler的实现传递给Proxy.newProxyInstance()方法。对动态代理的所有方法调用都转发到实现接口的InvocationHandler对象。
InvocationHandler代码:
public interface
InvocationHandler{
Object invoke (Object proxy, Method method,
Object[] args)
throws Throwable;
} |
实现InvocationHandler接口的类:
public class
MyInvocationHandler implements InvocationHandler{ public Object invoke (Object proxy, Method
method, Object[] args)
throws Throwable {
//do something "dynamic"
}
} |
下面详细介绍传递给invoke方法的三个参数。
Object proxy参数,实现接口的动态代理对象。通常不需要这个对象。
Method method参数,表示在动态代理实现的接口上调用的方法。通过Method对象,可以获取到方法名,参数类型,返回类型等信息。
Object[] args参数,包含调用接口中实现的方法时传递给代理的参数值。注意:如果接口中的参数是int、long等基本数据时,这里的args必须使用Integer,
Long等包装类型。
上面代码中会生成一个MyInterface接口的对象proxy,通过proxy对象调用的方法都会由MyInvocationHandler类的invoke方法处理。
动态代理使用场景:
1、数据库连接和事务管理。例如Spring框架有一个事务代理,可以启动和提交/回滚事务
2、用于单元测试的动态模拟对象
3、类似AOP的方法拦截。
本文就介绍到这里,反射的其他功能里就不一一介绍了。 |