Java反射机制详解

一、Java反射机制

  在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。

反射机制主要是用来破解类文件的:

正常的文件执行:在源代码已知的情况下导入类路径,通过new关键字实例化该类对象,通过对象名使用这个类。

反射文件执行:在源代码未知的情况下,对类文件进行破解,然后再去使用这个类。

  要想解剖一个类,必须先要获取到该类的字节码文件对象,而解剖使用的就是Class类中的方法,所以先要获取到每一个字节码文件对应的Class类型的对象。

二、 Class类

  想要破解类,就必须对类进行了解,类其实也是一种类型,对于一个类来讲,应该是由属性、普通方法、构造方法等等构成的。API中同样提供了一个Class类,用来对类本身进行操作,通过Class可以完整的得到一个类的结构,例如:方法、属性...

  Class没有公共构造方法,Class对象是在加载类时由Java虚拟机以及通过调用类加载器中的defineClass方法自动构成的。

获取Class对象:

  Class类没有公共构造方法,创建Class对象,获取Class对象的三种方式:

  1. 通过Object类中的getClass()方法:对象名.getClass();

  2. 通过类名.class获取到字节码文件对象(任意数据类型都具备一个class静态属性,看上去要比第一种简单):类名.class;

  3. 通过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();

四、利用反射获得到的构造方法创建对象

  1. 获取到Class对象

  2. 获取指定的构造方法

  3. 通过构造方法类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语言访问检查。

获取私有构造方法并创建对象,步骤:

  1. 获取到Class对象。

  2. 获取指定的构造方法。

  3. 暴力访问,通过setAccessible(boolean flag)方法。

  4. 通过构造方法类Constructor中的方法创建对象。

六、通过反射获取属性(成员变量)并使用

  在反射机制中,把类中的成员变量使用类Field表示,可通过Class类中提供的方法获取成员变量:

// 返回一个成员变量(属性)
// 获取指定的 public修饰的变量
public Field getField(String name);
// 获取指定的任意变量
public Field getDeclaredField(String name);

// 返回多个成员变量(属性)
// 获取所有public 修饰的变量(属性)
public Field[] getFields();
// 获取所有的 变量 (包含私有)
public Field[] getDeclaredFields();

获取成员变量及其赋值与获取值的步骤:

  1. 获取Class对象

  2. 获取构造方法

  3. 通过构造方法创建对象

  4. 获取指定的成员变量(私有成员变量通过setAccessible(boolean flag)方法暴力访问)

  5. 通过方法,给指定对象的指定成员变量赋值或者获取值:

  • 在指定对象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();//获取方法的返回类型

获取成员方法步骤:

  1. 获取Class对象。

  2. 获取构造方法。

  3. 通过构造方法,创建对象。

  4. 获取指定的方法。

  5. 执行找到的方法: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);
    }
}

八、反射的应用场景

  1. 需要获取Class对象,使用其方法

  2. 开发代码生成工具时

  3. 在框架中(如:Hibernate、Spring、Struts2)

  4. 泛型的擦除:在程序编译后产生的.class文件中是没有泛型约束的,因为在编译时,就将类型替换完成了,没有填写类型的默认是Object,这种现象我们称之为泛型的擦除。

  5. 利用反射读取配置文件:通过反射配置文件,运行配置文件中指定类的对应方法。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为中,最后一个数字表示对象的标号。

posted @ 2021-05-17 21:26  逍遥客灬  阅读(327)  评论(0编辑  收藏  举报