本部分主要介绍:Java 的反射机制的原理及使用
先知知识点
-
Java 的 接口 (interface)
- 对外提供规则的,表现形式是实现接口,必须实现接口中的抽象方法
-
Java 的 字节码文件 (.class)
Java 的 源文件(.java)通过编译得到字节码文件(.class)
Java 的虚拟机(JVM)运行的就是字节码文件,固又称 "运行文件"
-
Java 的 类加载过程
-
Java 的 Class 类
- 此类是对 运行文件(.class) 的描述类
-
应用程序配置文件
存储着程序中依据环境变化的 变量
通过 IO 流与程序进行通信
反射机制,将 Java 的 "一切皆对象" 演绎得淋淋尽致
反射机制介绍
-
概念
Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键
-
应用场景
-
程序如图
-
如何扩展应用程序
实现应用程序对外的接口
如何可以在 "正在运行的程序" 获取实现接口的类(
.class
文件),并对其调用呢?应用程序都有配置文件,可将实现接口的类名称写入配置文件
应用程序实现反射机制,可调用实现接口类
-
-
实例
-
Tomcat 服务程序
提供了处理请求和应答的一般方式
而具体的如何处理请求和应答,需要根据实际生产环境决定
-
解决方法
对外提供接口(serlet 接口)
Tomcat 服务具有配置文件(web.xml 配置文件)
-
如何将实现接口的类,应用到 Tomcat 服务中呢?
- Tomcat 的反射机制 + 配置文件 + 接口 = Tomcat 动态获取实现类的信息
-
反射 "如何反射" ?
-
问题描述
- 如何反射?如何获取运行文件(.class 文件)中的属性和方法?如何进行获取和赋值、如何进行调用呢?
-
.class
文件Demo.java
是 Java 的源文件。便于人理解Demo.java + 编译器 = Demo.class
Demo.class
是 JVM 运行文件(二进制文件),称之为 Java 的运行文件-
.class
文件包含的内容属性
构造器
方法
等等
-
Java 一切皆对象
Java 对运行文件(.class 文件)也有描述类,此类为 Class 类 。就好比对学生、教师进行抽象一个 Person 类的作用相同
-
Class 类中定义了运行文件(.class)中通用的东西
定义描述 .class 文件的 属性
定义描述 .class 文件的 构造函数
定义描述 .class 文件的 方法
定义描述 .class 文件的 类名(包名)等……
-
Class 类中也定义了如何获取这些东西的方法
- 获取 .class 文件中的属性,如同 Person 的 get 方法
-
获取类文件(.class)的 Class 对象的 4 中方法
-
Object 类中的
getClass()
方法,可以获取 Class 对象(对象的运行时类型)- 要明确类(.class 文件) ,为类创建对象才可调用
getClass()
方法,需要使用到此类的 构造函数
- 要明确类(.class 文件) ,为类创建对象才可调用
-
任何数据类型都具备静态属性
.class
可以获取 Class 对象- 无需使用此类的构造函数,使用静态属性
-
通过给定的类(.class)字符串名称,就可以获取 Class 对象。Class 类中的静态
forName(String className)
Class clazz = Class.forName(className)
参数
calssName
需要自定包名
-
通过类加载器对象的方法的获取 Class 对象(通常用在自定义类加载器对象去加载指定路径下的类)
-
获取类加载器
Class clazzA = A.class; ClassLoader loader = clazz.getClassLoader();
-
在获取某一特定的 Class类的对象
Class clazzB = loader.loadClass("类全名");
-
注意类加载器有 4 个层次,使用较高层次的类加载器去加载类(通过类名)会报异常
NullPointerException
,具体原因不太清除Class c1 = int.class; ClassLoader loader = c1.getClassLoader(); // 类加载器层次较高 Class c2 = loader.loadClass("java.lang.String"); // 报异常
-
-
-
刨析 .class 对应的类
-
ReflectDemo.Person person = new ReflectDemo.Person(); // ReflectDemo 为包名
new
时,先根据类的名称,寻找类的字节码文件,并加载进内存再根据字节码文件创建 Class 对象
最后创建字节码文件对应的 Person 类,创建 person 对象
-
Class clazz = Class.forName(ReflectDemo.Person);
先寻找对应的字节码文件(.class),并加载进内存
再依据字节码文件创建 Class 对象
并不会创建字节码文件对应的类对象
-
如何根据 Class 对象 clazz 创建 Person 类对应的 person 对象呢?
-
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 类
- 代码
-
newInstance()
方法,实际是调用 Person 类的空参构造函数没有空参构造器,则会报
InstantationException
异常空参构造器私有化,则会报
IlleagalAcessException
异常
-
构造方法有参数,怎么创建 Person 对象呢?
newInstance()
方法不行!!!但是可以使用 Class 类中方法获取 Person 的字节码文件( .class)中的属性、方法、构造器啊!!!
-
-
使用 Class 类中的方法来获取 Person 构造器,创建 person 对象
说明:Java 的 java.lang.reflect 包,包含 Constructor、Field、Method 类
-
获取构造函数方法
getConstructors()
,获取所有公共的构造函数,返回 Constructor (构造器对象)对象数组getDeclareConstructors()
,获取所有构造函数(各种权限的),返回 Constructor 对象数组getConstructor()
,获取公共的构造函数,返回 Constructor 对象getDeclareConstructor()
,获取构造函数(各种权限的),返回 Constructor 对象
-
获取指定构造函数
Constructor constructor = clazz.getConstructor(String.class, int.class);
Object obj = constructor.newInstace(className, age);
,返回 Object 对象
-
-
使用 Class 类获取属性
-
获取属性的方法
getField()
,获取共有属性,返回 Feild 对象getDeclareField()
,获取任何权限的属性,返回 Field 对象
-
使用 Field 对象,设置、获取属性值
field.get(Object)
,需要明确属性的具体对象私有属性当然是不可访问的,报
IllegalAccessException
异常。这里还是遵循 Java 的基本权限语法的
-
如何获取私有属性
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);
-
使用 Class 类获取方法
-
获取方法的方法
getMethod()
,获取共有方法,返回 Method 对象getDeclareMethod()
,获取任何权限的方法,返回 Method 对象
-
使用 Method 对象,获取方法,并执行
Method method = clazz.getMethod(方法名, 方法参数)
,方法没有参数方法参数位置为null
,有参数String.class
运行方法
method.invoke(对象,方法参数)
。静态方法不需要对象,对象位置为null
表示调用静态方法。
私有方法,可使用 暴力访问
-
-
使用 Class 类获取注解信息
- 使用 Class、Method、Constructor、Field 的
getAnnotation(Class<A> annotationClass)
方法,分别获取对应的注释对象(存在向下转型)
- 有待补充……
- 使用 Class、Method、Constructor、Field 的
-
使用 Class 类获取泛型类的实参类型
Type 接口以及子类的的认识
有待补充……
-
代码块问题
这里需要理解类的初始化过程,以及 .class 字节码文件格式。在字节码文件中,没有所谓的代码块,所以无法获取代码块的相关信息。
静态代码块会在
clinit
方法中非静态代码块会在
init
方法中
-
使用 Class 类获取其它内容
包名(Package 类)
类名
直接父类
接口数组(实现的接口不止一个)
-
类的修饰符(Modifier 类,表示程序元素(如类、方法或字段)上的修饰符)。实现方法很有意思。
Class clazz = A.class; // 获取 Class 对象 int classMod = zz.getModifier(); // 返回整数 System.out.println(Modifier.toString(classMod)); // 打印返回字符串,此处有按位与运算
-
-