Java 的反射机制

本部分主要介绍:Java 的反射机制的原理及使用

先知知识点

  1. Java 的 接口 (interface)

    • 对外提供规则的,表现形式是实现接口,必须实现接口中的抽象方法
  2. Java 的 字节码文件 (.class)

    • Java 的 源文件(.java)通过编译得到字节码文件(.class)

    • Java 的虚拟机(JVM)运行的就是字节码文件,固又称 "运行文件"

  3. Java 的 类加载过程

    • 类加载过程
  4. Java 的 Class 类

    • 此类是对 运行文件(.class) 的描述类
  5. 应用程序配置文件

    • 存储着程序中依据环境变化的 变量

    • 通过 IO 流与程序进行通信

  6. 反射机制,将 Java 的 "一切皆对象" 演绎得淋淋尽致

反射机制介绍

  1. 概念

    • Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键

      百度百科

  2. 应用场景

    • 程序如图

      应用场景.jpg
    • 如何扩展应用程序

      1. 实现应用程序对外的接口

      2. 如何可以在 "正在运行的程序" 获取实现接口的类( .class 文件),并对其调用呢?

      3. 应用程序都有配置文件,可将实现接口的类名称写入配置文件

      4. 应用程序实现反射机制,可调用实现接口类

  3. 实例

    • Tomcat 服务程序

      1. 提供了处理请求和应答的一般方式

      2. 而具体的如何处理请求和应答,需要根据实际生产环境决定

      3. 解决方法

        • 对外提供接口(serlet 接口)

        • Tomcat 服务具有配置文件(web.xml 配置文件)

      4. 如何将实现接口的类,应用到 Tomcat 服务中呢?

        • Tomcat 的反射机制 + 配置文件 + 接口 = Tomcat 动态获取实现类的信息

反射 "如何反射" ?

  1. 问题描述

    • 如何反射?如何获取运行文件(.class 文件)中的属性和方法?如何进行获取和赋值、如何进行调用呢?
  2. .class 文件

    • Demo.java 是 Java 的源文件。便于人理解

    • Demo.java + 编译器 = Demo.class

    • Demo.class 是 JVM 运行文件(二进制文件),称之为 Java 的运行文件

    • .class 文件包含的内容

      1. 属性

      2. 构造器

      3. 方法

      4. 等等

  3. Java 一切皆对象

    • Java 对运行文件(.class 文件)也有描述类,此类为 Class 类 。就好比对学生、教师进行抽象一个 Person 类的作用相同

    • Class 类中定义了运行文件(.class)中通用的东西

      1. 定义描述 .class 文件的 属性

      2. 定义描述 .class 文件的 构造函数

      3. 定义描述 .class 文件的 方法

      4. 定义描述 .class 文件的 类名(包名)等……

    • Class 类中也定义了如何获取这些东西的方法

      1. 获取 .class 文件中的属性,如同 Person 的 get 方法
  4. 获取类文件(.class)的 Class 对象的 4 中方法

    • Object 类中的 getClass() 方法,可以获取 Class 对象(对象的运行时类型)

      1. 要明确类(.class 文件) ,为类创建对象才可调用 getClass() 方法,需要使用到此类的 构造函数
    • 任何数据类型都具备静态属性 .class 可以获取 Class 对象

      1. 无需使用此类的构造函数,使用静态属性
    • 通过给定的类(.class)字符串名称,就可以获取 Class 对象。Class 类中的静态forName(String className)

      1. Class clazz = Class.forName(className)

      2. 参数 calssName 需要自定包名

    • 通过类加载器对象的方法的获取 Class 对象(通常用在自定义类加载器对象去加载指定路径下的类)

      1. 获取类加载器

        Class clazzA = A.class;
        ClassLoader loader = clazz.getClassLoader();
        
      2. 在获取某一特定的 Class类的对象

        Class clazzB = loader.loadClass("类全名");
        
      3. 注意类加载器有 4 个层次,使用较高层次的类加载器去加载类(通过类名)会报异常 NullPointerException具体原因不太清除

        Class c1 = int.class;
        ClassLoader loader = c1.getClassLoader(); // 类加载器层次较高
        Class c2 = loader.loadClass("java.lang.String"); // 报异常
        
  5. 刨析 .class 对应的类

    • ReflectDemo.Person person = new ReflectDemo.Person(); // ReflectDemo 为包名

      1. new 时,先根据类的名称,寻找类的字节码文件,并加载进内存

      2. 再根据字节码文件创建 Class 对象

      3. 最后创建字节码文件对应的 Person 类,创建 person 对象

    • Class clazz = Class.forName(ReflectDemo.Person);

      1. 先寻找对应的字节码文件(.class),并加载进内存

      2. 再依据字节码文件创建 Class 对象

      3. 并不会创建字节码文件对应的类对象

    • 如何根据 Class 对象 clazz 创建 Person 类对应的 person 对象呢?

      1. Class 类中的 newInstance() 方法,可以获取 person 对象。这里注意区分一下:编译时类型;运行时类型,使用 newInstance() 获取的对象编译类型为 Object,运行类型为 Person 类型

        • 代码
          Class clazz = Class.forName("Day1.src.ReflectDemo.People");
          
          Object obj = clazz.newInstance(); // 使用 Object 类型接受
          System.out.println(obj.getClass()); // Class 对应的类为 People 类
          
      2. newInstance() 方法,实际是调用 Person 类的空参构造函数

        • 没有空参构造器,则会报 InstantationException 异常

        • 空参构造器私有化,则会报 IlleagalAcessException 异常

      3. 构造方法有参数,怎么创建 Person 对象呢?

        • newInstance() 方法不行!!!

        • 但是可以使用 Class 类中方法获取 Person 的字节码文件( .class)中的属性、方法、构造器啊!!!

    • 使用 Class 类中的方法来获取 Person 构造器,创建 person 对象

      说明:Java 的 java.lang.reflect 包,包含 Constructor、Field、Method 类

      1. 获取构造函数方法

        • getConstructors() ,获取所有公共的构造函数,返回 Constructor (构造器对象)对象数组

        • getDeclareConstructors() ,获取所有构造函数(各种权限的),返回 Constructor 对象数组

        • getConstructor() ,获取公共的构造函数,返回 Constructor 对象

        • getDeclareConstructor() ,获取构造函数(各种权限的),返回 Constructor 对象

      2. 获取指定构造函数

        • Constructor constructor = clazz.getConstructor(String.class, int.class);

        • Object obj = constructor.newInstace(className, age); ,返回 Object 对象

    • 使用 Class 类获取属性

      1. 获取属性的方法

        • getField() ,获取共有属性,返回 Feild 对象

        • getDeclareField() ,获取任何权限的属性,返回 Field 对象

      2. 使用 Field 对象,设置、获取属性值

        • field.get(Object) ,需要明确属性的具体对象

        • 私有属性当然是不可访问的,报 IllegalAccessException 异常。这里还是遵循 Java 的基本权限语法的

      3. 如何获取私有属性

        • Constructor构造器类、Field属性类、Method方法类 的父类 AccessibleObject

        • 它提供了将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力。

        • 对于公共成员、默认(打包)访问成员、受保护成员和私有成员,在分别使用 Field、Method 或 Constructor 对象来设置或获取字段、调用方法,或者创建和初始化类的新实例的时候,会执行访问查。

        • 暴力访问 AccessileObject 的 setAccessible(boolean flag) 方法 ,true 为取消权限,再进行访问

          Field field = clazz.getDeclareField("name");
          field.setAccessible(true);
          String name = field.get(Object);
          
      4. 使用 Class 类获取方法

        • 获取方法的方法

          1. getMethod() ,获取共有方法,返回 Method 对象

          2. getDeclareMethod() ,获取任何权限的方法,返回 Method 对象

        • 使用 Method 对象,获取方法,并执行

          1. Method method = clazz.getMethod(方法名, 方法参数) ,方法没有参数方法参数位置为 null ,有参数 String.class

          2. 运行方法 method.invoke(对象,方法参数) 。静态方法不需要对象,对象位置为 null 表示调用静态方法。

        • 私有方法,可使用 暴力访问

      5. 使用 Class 类获取注解信息

        • 有待补充……
      6. 使用 Class 类获取泛型类的实参类型

        • Type 接口以及子类的的认识

        • 有待补充……

      7. 代码块问题

        • 这里需要理解类的初始化过程,以及 .class 字节码文件格式。在字节码文件中,没有所谓的代码块,所以无法获取代码块的相关信息。

        • 静态代码块会在 clinit 方法中

        • 非静态代码块会在 init 方法中

      8. 使用 Class 类获取其它内容

        • 包名(Package 类)

        • 类名

        • 直接父类

        • 接口数组(实现的接口不止一个)

        • 类的修饰符(Modifier 类,表示程序元素(如类、方法或字段)上的修饰符)。实现方法很有意思。

          Class clazz = A.class; // 获取 Class 对象
          int classMod = zz.getModifier();  // 返回整数
          System.out.println(Modifier.toString(classMod)); // 打印返回字符串,此处有按位与运算
          

你可能感兴趣的:(Java 的反射机制)