什么是反射
说起框架,相信很多人都用过,如今的开发基本离不开框架,从spring,strtus等到如今的spring boot,框架已经深入到了开发的点点滴滴,相信很多人在使用spring框架时都是这样使用的,在xml文件中配置相应的Java类和方法等,这样的话使用注解就不需要自己的new一个对象了,spring框架已经帮我们处理好了,这就是反射。
反射就是运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
反射机制可以用来:
•在运行时分析类的能力。
•在运行时查看对象,例如,编写一个toString方法供所有类使用
•实现通用的数组操作代码。
•利用Method对象,这个对象很像C++中的函数指针。
反射是一种功能强大且复杂的机制。
在程序运行期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识,这个信息跟踪着每个对象所属的类,虚拟机利用运行时类型信息选择相应的方法执行,可以通过专门的Java类访问这些信息,保存这些信息的类被称为Class,一个Class对象实际上表示的是一个类型,而这个类型未必一定是一种类。例如, int不是类,但int.class是一个Class类型的对象。Class类实际上是一个泛型类,我们都知道Java程序编译后会编译成一个个的class文件,这个class文件实际上就相当于一个个的Class.
利用反射得到类的属性
在java.lang.reflect包中有3个类Field、Method和Constructor分別用于描述类的域、方 法和构造器-这三个类都有一个叫getName的方法,用來返冋项目的名称。Field类有一 个getType方法,用来返回描述域所属类型的Class对象 Method和Constructor类有能够报告参数类型的方法,Method类还有一个可以报告返冋类型的方法。
Class类屮的getFields、getMethods和getConstruc tors方法将分别返回类提供的public域、方法和构造器数组,其中包括超类的公有成员。Class类的getDeclareFields. getDeclareMethods和getDeclaredConstructors方法将分别返回类中声明的全部域、方法和构造器,其中包括私有和受保护成员,但不包括超类的成员。
通过反射得到相应的Java类属性:
- 通过反射得到Java构造方法:
- 首先添加一个Java类
package reflect;
public class reflectTest1 {
public reflectTest1() {
System.out.println("无参构造方法");
}
public reflectTest1(int i) {
System.out.println("参数为整数类型构造方法");
}
public reflectTest1(int i, String a) {
System.out.println("参数为多个的构造方法");
}
}
3. 通过反射得到相应的构造方法:
@Test
public void test1() throws Exception {
// 根据包名.类名得到Class字节码对象
Class cl = Class.forName("reflect.reflectTest1");
// 得到类的无参构造方法
Constructor constructor = cl.getConstructor(null);
// 创建对象,相当于new
constructor.newInstance(null);
// 得到一个参数的构造方法
Constructor constructor2 = cl.getConstructor(int.class);
// 创建对象
constructor2.newInstance(1);
// 得到多个参数的构造方法
Constructor constructor3 = cl.getConstructor(int.class, String.class);
// 创建对象
constructor3.newInstance(1, "test");
}
结果如图所示:
如果该构造方法是私有的构造方法,那么就需要强制反射,这个时候使用的是另一个方法
Constructor constructor4 = cl.getDeclaredConstructor(String.class);
// 私有的构造方法强制反射
constructor4.setAccessible(true);
// 创建对象
constructor4.newInstance("test");
通过反射执行Java方法,首先定义几个方法:
public void test1() {
System.out.println("test1.....");
}
public void test2(String val) {
System.out.println("test2....." + val);
}
public static void test3(String v, String u) {
System.out.println("test3....." + v + "......" + u);
}
通过反射执行相应的方法:
@Test
public void test2() throws Exception {
// 根据包名.类名得到Class字节码对象
Class cl = Class.forName("reflect.reflectTest1");
// 得到类的无参构造方法
Constructor constructor = cl.getConstructor(null);
// 创建对象
Object obj = cl.newInstance();
// 得到test1方法,第一个参数为方法名,第二个参数为参数类型
Method method = cl.getMethod("test1", null);
// 执行方法,第一个参数为class对象,第二个为方法的参数
method.invoke(obj, null);
Method method2 = cl.getMethod("test2", String.class);
method2.invoke(obj, "test");
Method method3 = cl.getMethod("test3", String.class, String.class);
method3.invoke(null, "test", "123");
}
同样的,如果该方法是私有的,那么必须要使用
Method m = cl.getDeclaredMethod(“方法名”,参数类型);
m.setAccessible(true);
特别提出:如果方法的参数是数组,不可以直接在调用的传输数组进去,那样的话会出现参数类型不一致的错误,解决方法是:
// 如果参数是数组类型,正确写法
Method method4 = cl.getMethod("test4", String[].class);
method4.invoke(obj, new Object[] { new String[] { "a", "b" } });
// 如果参数是数组类型,错误写法
Method method5 = cl.getMethod("test4", String[].class);
method5.invoke(obj, new String[] { "a", "b" });
如果按照错误写法,会出现错误参数类型不一致。
java.lang.IllegalArgumentException: wrong number of arguments
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
原因就是jdk1.4和jdk1.5处理invoke方法的问题,但是就算使用的是比1.4更高的版本,为了向下兼容,这种写法也是错误的,,因为在jdk1.5中,整个数组被当成一个参数,而在jdk1.4的源码中,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,会把数组拆分,这样就不是一个参数,而是多个参数,就会出现参数不一致的问题,所以,需要将String数组放入一个object数组中,这样整个的string数组就是一个参数。
除此之外,还有一种方法,就是将string类型的数组转成object类型,即:method5.invoke(obj, (Object) new String[] { "a", "b" });
通过反射得到Java 类的成员变量
private String name = "reflect";
public int age = 23;
private static final Boolean isStudent = true;
@Test
public void test3() throws Exception {
// 根据包名.类名得到Class字节码对象
Class cl = Class.forName("reflect.reflectTest1");
// 创建对象
Object obj = cl.newInstance();
// 成员变量为私有
Field nameField = cl.getDeclaredField("name");
nameField.setAccessible(true);
System.out.println(nameField.get(obj));
// 改变成员变量的值
nameField.set(obj, "changeReflect");
System.out.println(nameField.get(obj));
// public
Field ageField = cl.getField("age");
System.out.println(ageField.get(obj));
Field isStudentField = cl.getDeclaredField("isStudent");
isStudentField.setAccessible(true);
System.out.println(isStudentField.get(obj));
}
运行结果:
除此之外,还有批量获取成员变量,批量获取类方法等方法,具体可查看JavaAPI文档
看到这里,相信你对框架的设计有了更深的理解,明白了为什么框架需要配置Java类,Java方法,其实就是这个道理,通过配置文件实现了Java的反射,而用户自己则 不必建里太多的对象