本文旨在学习java的发射机制(java reflection),欢迎交流讨论指正。
1)先来看看什么是所谓的反射机制。Java反射机制主要提供了以下功能: 在运行时判断任意一个对象所属的类;在运行时构造任意一个类的对象;在运行时判断任意一个类所具有的成员变量和方法;在运行时调用任意一个对象的方法;生成动态代理。(来自于百度百科的定义)
简而言之,java反射指的是允许用户借由某个Class相关的元信息对象间接调用Class对象的功能。
2.1)废话少说,直接开场。看看我们怎么来获得一个类的变量、构造函数、方法。为此,我们先构建一个基础POJO类。
package com.learn.reflect; public class Car { private String brand; private String color; private int maxSpeed; public Car(){} public Car(String brand, String color, int maxSpeed){ this.brand = brand; this.color = color; this.maxSpeed = maxSpeed; } public void introduce(){ System.out.println("brand:" + brand + ";color:" + color + ";maxSpeed:" + maxSpeed); } public String getBrand() { return brand; } public void setBrand(String brand) { this.brand = brand; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public int getMaxSpeed() { return maxSpeed; } public void setMaxSpeed(int maxSpeed) { this.maxSpeed = maxSpeed; } }
2.2)下面我们将看到java是如何取得所有的变量、构造函数、方法的。
package com.learn.reflect; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; public class ReflectTest { private ClassLoader loader = Thread.currentThread().getContextClassLoader(); private void testReflectionMethod() throws Throwable{ Class<?> clazz = loader.loadClass("com.learn.reflect.Car"); System.out.println("-----Field-----"); for(Field field : clazz.getDeclaredFields()) System.out.println(field); System.out.println("-----Constructor-----"); for(Constructor<?> cons : clazz.getDeclaredConstructors()) System.out.println(cons); System.out.println("-----Method-----"); for(Method method : clazz.getDeclaredMethods()) System.out.println(method); }public static void main(String[] args) throws Throwable { ReflectTest rt = new ReflectTest(); rt.testReflectionMethod(); } }下面是Console的输出结果:
-----Field----- private java.lang.String com.learn.reflect.Car.brand private java.lang.String com.learn.reflect.Car.color private int com.learn.reflect.Car.maxSpeed -----Constructor----- public com.learn.reflect.Car() public com.learn.reflect.Car(java.lang.String,java.lang.String,int) -----Method----- public void com.learn.reflect.Car.setBrand(java.lang.String) public void com.learn.reflect.Car.setMaxSpeed(int) public void com.learn.reflect.Car.introduce() public java.lang.String com.learn.reflect.Car.getBrand() public int com.learn.reflect.Car.getMaxSpeed() public void com.learn.reflect.Car.setColor(java.lang.String) public java.lang.String com.learn.reflect.Car.getColor()
从输出可以看到,上述的方法确实获得了所有的变量、构造函数、方法。在这里需要提及的是,不论对于任何Field、Constructor、Method,如果有在方法名中有Declared字段,则可以获得所有的访问控制的变量、构造函数、方法。比如说getFields方法,只能获得public权限的变量,而getDeclaredFields方法则可以获得所有控制权限的变量。获得构造函数与方法的方法与此类似。同样,如果你知道某个构造函数或者方法的配参,那么你可以直接获得相对应的构造函数或者方法。可以看下面的代码。
2.3)来看看如何利用反射来构建一个对象的实例。
package com.learn.reflect; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; public class ReflectTest { private ClassLoader loader = Thread.currentThread().getContextClassLoader(); private Car initByDefaultConst() throws Throwable{ Class<?> clazz = loader.loadClass("com.learn.reflect.Car"); Constructor<?> cons = clazz.getDeclaredConstructor((Class[])null); Car car = (Car)cons.newInstance(); Method setBrand = clazz.getMethod("setBrand", String.class); setBrand.invoke(car, "红旗CA72"); Method setColor = clazz.getMethod("setColor", String.class); setColor.invoke(car, "黑色"); Method setMaxSpeed = clazz.getMethod("setMaxSpeed", int.class); setMaxSpeed.invoke(car, 200); return car; } public static void main(String[] args) throws Throwable { ReflectTest rt = new ReflectTest(); Car car = rt.initByDefaultConst(); car.introduce(); } }Console的输出结果是:
brand:红旗CA72;color:黑色;maxSpeed:200
可以看到,使用newInstance()函数便可以在知道constructor的情况下获得一个对象的实例。而同时可以用反射获得的对应方法的invoke函数,来为相对应的变量进行赋值。这样最终就可以获得一个实例,而其与用构造函数定义的实例并无二致。
就这样,我们很容易利用反射机制来新建一个的实例。
2.4)下面主要讨论如何对于一个private变量进行赋值的问题。
package com.learn.reflect; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; public class ReflectTest { private ClassLoader loader = Thread.currentThread().getContextClassLoader(); private void testClassField() throws Throwable{ System.out.println("The output is from class testClassField."); Class<?> clazz = loader.loadClass("com.learn.reflect.Car"); Method getColor = clazz.getMethod("getColor", null); System.out.println(getColor.getReturnType()); //class java.lang.String Field field = clazz.getDeclaredField("color"); System.out.println(field); //private java.lang.String com.learn.reflect.Car.color //22222 Car car = initByDefaultConst(); car.introduce(); //11111 // field.set(car, "红色"); //Error because the variable color is private. //AAAAA car.setColor("红色"); //The set method //BBBBB car.introduce(); //33333 Method setColor = clazz.getMethod("setColor", String.class); setColor.invoke(car, "黄色"); //The invoke method of setColor //CCCCC car.introduce(); //44444 field.setAccessible(true); // To change accessibility of field becomes true, then the method set of field can be used. System.out.println(field); //private java.lang.String com.learn.reflect.Car.color //66666 field.set(car, "蓝色"); //DDDDD car.introduce(); //55555 field = clazz.getDeclaredField("maxSpeed"); field.setAccessible(true); field.setInt(car, 250); car.introduce(); } public static void main(String[] args) throws Throwable { ReflectTest rt = new ReflectTest(); rt.testClassField(); } }
下面是Console的输出结果:
The output is from class testClassField. class java.lang.String private java.lang.String com.learn.reflect.Car.color //22222 brand:红旗CA72;color:黑色;maxSpeed:200 //11111 brand:红旗CA72;color:红色;maxSpeed:200 //33333 brand:红旗CA72;color:黄色;maxSpeed:200 //44444 private java.lang.String com.learn.reflect.Car.color //66666 brand:红旗CA72;color:蓝色;maxSpeed:200 //55555 brand:红旗CA72;color:蓝色;maxSpeed:250
下面对产生的结果进行一些简单的分析。
a. 首先可以使用initByDefaultConst中所使用的方法构建一个对应的实例(请看代码中11111位置产生的输出)。
b. 尽管可以用2.2中所介绍的clazz.getDeclaredFields函数获得对应的变量(查看22222处)。但是却无法使用field.set直接更改color这个变量的值(查看AAAAA处)。不过这个时候,还是可以直接调用car.setColor来更改变量的值(查看BBBBB处)。所以产生了(33333处)更改了color后的输出。
c. 使用clazz.getMethod获得相对应的方法,此后在调用invoke函数便可以修改掉变量的值(查看CCCCC处,及44444处的输出)。这很重要!因为invoke函数获得的是Method,而method是public的,所以可以直接对相对应的变量进行改动,这和b中的setColor是一样的。也就是说如果你对一个private变量,设置了public的set函数,那么就可以用getMethod获得修改这个private的能力。反射逆天之一。
d. 来看看反射逆天之二。field.setAccessible(true)。b中已经说了在调用这句话前(AAAAA处),field.set是无效的。但是在这个函数后(DDDDD处),却成功地调用了field.set函数。并且成功的修改了变量的值(看55555处的输出)。而实际上,在调用了field.setAccessible(true)之后,66666处的输出显示,field仍然是private。但是,却明明可以肆无忌惮的修改变量了啊!
综上所述,反射机制就像是能看见现实中的任何阴影一样,让你避无可避,逃无可逃。即使你是private变量,仍然可以随意修改你的值,想怎么改就怎么改。注意两大逆天神器!
提供一个相关的学习链接:http://my.oschina.net/u/1407116/blog/209383?fromerr=9FCqOwmP