Java反射机制详解
一、Java反射机制
在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。
反射机制主要是用来破解类文件的:
正常的文件执行:在源代码已知的情况下导入类路径,通过new关键字实例化该类对象,通过对象名使用这个类。
反射文件执行:在源代码未知的情况下,对类文件进行破解,然后再去使用这个类。
要想解剖一个类,必须先要获取到该类的字节码文件对象,而解剖使用的就是Class类中的方法,所以先要获取到每一个字节码文件对应的Class类型的对象。
二、 Class类
想要破解类,就必须对类进行了解,类其实也是一种类型,对于一个类来讲,应该是由属性、普通方法、构造方法等等构成的。API中同样提供了一个Class类,用来对类本身进行操作,通过Class可以完整的得到一个类的结构,例如:方法、属性...
Class没有公共构造方法,Class对象是在加载类时由Java虚拟机以及通过调用类加载器中的defineClass方法自动构成的。
获取Class对象:
Class类没有公共构造方法,创建Class对象,获取Class对象的三种方式:
-
通过Object类中的getClass()方法:对象名.getClass();
-
通过类名.class获取到字节码文件对象(任意数据类型都具备一个class静态属性,看上去要比第一种简单):类名.class;
-
通过Class类中的方法(将类名作为字符串传递给Class类中的静态方法forName即可):Class.forName("完整包名.类名");
注:第三种方式与前两种的区别:前两种必须明确类的类型,第三种是指定这种类型的字符串就行,这种扩展性更强,灵活性较强,不需要知道你的类,只提供字符串,按照配置文件加载就可以了。
三、通过反射获取构造方法并使用
在反射机制中,把类中的成员(构造方法、成员方法、成员变量)都封装成了对应的类进行表示,其中构造方法使用类Constructor表示,可通过Class类中提供的方法获取构造方法:
// 获取一个构造方法:
// 获取public修饰,指定参数类型所对应的构造方法:
public Constructor<T> getConstructor(Class<?> parameterTypes);
// 获取指定参数类型所对应的构造方法(包含私有的):
public Consturctor<T> getDeclaredConstructor(Class<?> parameterTypes);
// 返回多个构造方法:
// 获取所有的public 修饰的构造方法:
public Constructor<T>[] getConstructor();
// 获取所有的构造方法(包含私有的):
public Consturctor<T>[] getDeclaredConstructor();
四、利用反射获得到的构造方法创建对象
-
获取到Class对象
-
获取指定的构造方法
-
通过构造方法类Constructor中的方法创建对象:public T newInstance(Object obj);
/**
* copyright(c)2021 zbh.ALL rights Reserved
* <p>
* 描述:利用反射获得到的构造方法创建对象
*
* @author zbh
* @version 1.0
* @date 2021/5/17
*/
public class ConstructorDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, InvocationTargetException {
//1,获取到Class对象
Class c = Class.forName("Person");//包名.类名
//2,获取指定的构造方法
//public Person()
//Constructor con = c.getConstructor(null);
//public Person(int id, String name, String address)
Constructor con = c.getConstructor(int.class, String.class, String.class);
//3,通过构造方法类中Constructor的方法,创建对象
//Object obj = con.newInstance(null);
Object obj = con.newInstance(22, "小明", "哈尔滨");
//显示
System.out.println(obj);// Person{id=22, name='小明', address='哈尔滨'}
}
}
五、利用反射获得的私有构造方法创建对象
AccessibleObject类是Field、Method和Constructor类的父类。它提供了将反射的对象标记为在使用时取消默认Java语言访问控制检查的能力。
对于公共成员、默认(打包)访问成员、受保护成员和私有成员,在分别使用Field、Method或Constructor对象来设置或获取字段、调用方法或者创建和初始化类的实例的时候,会执行访问检查。
在Constructor的父类java.lang.reflect.AccessibleObject的常用方法:public void setAccessible(boolean flag) throws SecurityException 的参数为true时,则指示反射的对象在使用时应该取消Java语言访问检查,参数值为false时,则指示反射的对象应该实施Java语言访问检查。
获取私有构造方法并创建对象,步骤:
-
获取到Class对象。
-
获取指定的构造方法。
-
暴力访问,通过setAccessible(boolean flag)方法。
-
通过构造方法类Constructor中的方法创建对象。
六、通过反射获取属性(成员变量)并使用
在反射机制中,把类中的成员变量使用类Field表示,可通过Class类中提供的方法获取成员变量:
// 返回一个成员变量(属性)
// 获取指定的 public修饰的变量
public Field getField(String name);
// 获取指定的任意变量
public Field getDeclaredField(String name);
// 返回多个成员变量(属性)
// 获取所有public 修饰的变量(属性)
public Field[] getFields();
// 获取所有的 变量 (包含私有)
public Field[] getDeclaredFields();
获取成员变量及其赋值与获取值的步骤:
-
获取Class对象
-
获取构造方法
-
通过构造方法创建对象
-
获取指定的成员变量(私有成员变量通过setAccessible(boolean flag)方法暴力访问)
-
通过方法,给指定对象的指定成员变量赋值或者获取值:
- 在指定对象obj中,将此 Field 对象表示的成员变量设置为指定的新值:public void set(Object obj, Object value)
- 返回指定对象obj中,此 Field 对象表示的成员变量的值:public Object get(Object obj)
/**
* copyright(c)2021 zbh.ALL rights Reserved
* <p>
* 描述:通过反射获取属性(成员变量)并使用
*
* @author zbh
* @version 1.0
* @date 2021/5/17
*/
public class FieldDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchFieldException, InvocationTargetException {
//1,获取Class对象
Class c = Class.forName("Person");
//2,获取构造方法
//public Person(String name)
Constructor con = c.getConstructor(String.class);
//3,通过构造方法,创建对象
Object obj = con.newInstance("小明");
//4,获取指定的成员变量
//public String name;
Field nameField = c.getField("name");
//public int id;
Field idField = c.getField("id");
//private String address;
Field addressField = c.getDeclaredField("address");
addressField.setAccessible(true); //取消 Java 语言访问检查
//5,通过方法,给指定对象的指定成员变量赋值或者获取值
System.out.println("name = "+ nameField.get(obj));// name = 小明
System.out.println("id = "+ idField.get(obj));// id = 0
// 注:如果没有addressField.setAccessible(true);这行会报错
System.out.println("address = "+ addressField.get(obj));// address = null
//赋值
idField.set(obj, 23);
addressField.set(obj, "五一广场");
System.out.println("------------------------");
System.out.println("name = "+ nameField.get(obj));// name = 小明
System.out.println("id = "+ idField.get(obj));// id = 23
System.out.println("address = "+ addressField.get(obj));// address = 五一广场
}
}
七、通过反射获取成员方法并调用
在反射机制中,把类中的成员方法使用Method表示,可通过Class类中提供的方法获取成员方法:
// 1、返回获取一个方法:
// 获取public 修饰的方法
public Method getMethod(String name, Class<?> parameterTypes);
// 获取任意的方法,包含私有的,默认的,protected修饰
public Method getDeclaredMethod(String name, Class<?>parameterTypes);
// 参数1: name 要查找的方法名称; 参数2: parameterTypes 该方法的参数类型
// 2、返回获取多个方法:
// 获取本类与父类中所有public 修饰的方法
public Method[] getMethods();
// 获取本类中所有的方法(包含私有的)
public Method[] getDeclaredMethods();
// Method的常用方法:
getName();//获取方法的名称
getReturnType();//获取方法的返回类型
获取成员方法步骤:
-
获取Class对象。
-
获取构造方法。
-
通过构造方法,创建对象。
-
获取指定的方法。
-
执行找到的方法:public Object invoke(Object obj, Object args); 执行指定对象obj中,当前Method对象所代表的方法,方法要传入的参数通过args指定。
/**
* copyright(c)2021 zbh.ALL rights Reserved
* <p>
* 描述:通过反射获取成员方法并调用
*
* @author zbh
* @version 1.0
* @date 2021/5/17
*/
public class MethodDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
//1, 获取Class对象
Class c = Class.forName("Person");
//2,获取构造方法
//public Person(String name, int age, String address){
Constructor con = c.getConstructor(int.class, String.class, String.class);
//3,通过构造方法,创建对象
Object obj = con.newInstance(23, "小明", "哈尔滨");
//4,获取指定的方法
//public void method1() 没有返回值没有参数的方法
//Method m1 = c.getMethod("method1", null);
//public String method4(String name)
Method m4 = c.getMethod("method4", String.class);
//5,执行找到的方法
//m1.invoke(obj, null);
Object result = m4.invoke(obj, "zhangsan");
System.out.println("result = " + result);// zhangsan
//4,获取指定的方法(访问private修饰符的方法)
//private void method5(){
Method m5 = c.getDeclaredMethod("method5", null);
//5,开启暴力访问
m5.setAccessible(true);
//6,执行找到的方法
m5.invoke(obj, null);
}
}
八、反射的应用场景
-
需要获取Class对象,使用其方法
-
开发代码生成工具时
-
在框架中(如:Hibernate、Spring、Struts2)
-
泛型的擦除:在程序编译后产生的.class文件中是没有泛型约束的,因为在编译时,就将类型替换完成了,没有填写类型的默认是Object,这种现象我们称之为泛型的擦除。
-
利用反射读取配置文件:通过反射配置文件,运行配置文件中指定类的对应方法。Properties类在java.util包中读取Properties.txt文件中的数据,通过反射技术,来完成对象的创建。
九、动态代理
代理:本来应该自己做的事情,却请了别人来做,被请的人就是代理对象。
Java中的动态代理:在程序运行过程中产生的这个对象,而程序运行过程中产生对象其实就是反射讲解的内容,所以,动态代理其实就是通过反射来生成一个代理。
在Java中java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口就可以生成动态代理对象,JDK提供的代理只能针对接口做代理。而我们又更强大的代理cglib
Proxy类中的方法创建动态代理类对象:public static Object
newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h);
Proxy类中创建动态代理对象的三个参数:
-
ClassLoader对象:定义了由哪个ClassLoader对象来对生成的代理对象进行加载
-
Interface对象的数组:表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了。
-
InvocationHandler对象:表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上。
最终会调用InvocationHandler的方法:InvocationHandler Object invoke(Object proxy,Method method,Object[] args);
InvocationHandler接口中invoke方法的三个参数:
-
proxy:代表动态代理对象
-
method:代表正在执行的方法
-
args:代表调用目标方法时传入的实参
每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的invoke 方法来进行调用。
Proxy.newProxyInstance:创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号。