反射机制
1.一个需求引出反射
-
请看下面的问题
-
根据配置文件re.properties 指定信息,创建Cat对象并调用方法hi
classfullpath=com.hspedu.Cat method=hi
老韩思考:使用现有技术,你能做出来吗?
-
这样的需求在学习框架时特别多,即通过外部文件配置,不修改不源码情况下,来控制程序,也符合设计模式的 ocp原则(开闭原则:不修改源码,扩容功能)
-
快速入门
package com.hspedu.reflection.question; import com.hspedu.Cat; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Properties; /** * @author wzk * @version 1.0 * @date 2022/9/9-19:31 * 反射问题的引入 */ public class ReflectionQuestion { public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { //根据配置文件 re.properties 指定信息,创建Cat对象并调用方法hi //传统方式 new 对象 -> 调用方法 // Cat cat = new Cat(); // cat.hi(); ===> cat.cry() 修改源码 //我们尝试做一做 -> 明白反射 //1.使用 Properties 类,可以读写配置文件 Properties properties = new Properties(); properties.load(new FileInputStream("src/re.properties")); String classfullpath = properties.get("classfullpath").toString();//com.hspedu.Cat String methodName = properties.get("method").toString();//hi System.out.println("classfullpath=" + classfullpath); System.out.println("methodName" + methodName); //2.创建对象,传统方法,行不通 => 反射机制 //new classfullpath() //3.使用反射机制解决 //(1)加载类,返回Class类型的对象 Class<?> cls = Class.forName(classfullpath); //(2)通过 cls 得到你加载的类 com.hspedu.Cat 的对象实例 Object o = cls.newInstance(); System.out.println("o的运行类型=" + o.getClass());//运行类型 //(3)通过 cls 得到你加载的类 com.hspedu.Cat 的 methodName"hi" 的方法对象 // 即 :在反射中,可以把方法视为对象(万物皆对象) Method method1 = cls.getMethod(methodName); //(4)通过 method1 调用方法:即通过方法对象来实现调用方法 method1.invoke(o); } }
-
2.反射机制
-
java Reflection
-
反射机制允许程序在执行期间借助于ReflectionAPI取得任何类的内部信息(比如成员变量,构造器,成员方法等等),并能操作对象的属性及方法。反射在设计模式和框架底层都用的到
-
加载完类之后,在堆中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个Class对象就像一面镜子,透过这个镜子看到类的结构,所以形象的称之为:反射
p 对象 --》 类型 Person类
Class对象 cls --》 类型 Class类
-
-
java反射机制原理示意图!!!
-
java反射机制可以完成
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时得到任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的成员变量和方法
- 生成动态代理
-
反射相关的主要类
- java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象
- java.lang.reflection.Method:代表类的方法,Method对象表示某个类的方法
- java.lang.reflection.Field:代表类的成员变量,Field对象表示某一个类的成员变量
- java.lang.reflection.Constructor:代表类的构造方法,Constructor对象表示构造器
-
反射的优点和缺点
- 优点:可以动态的创建和使用对象(也是框架的底层核心),使用灵活,没有反射机制,框架技术就失去了底层支撑
- 缺点:使用反射基本是解释执行,对执行速度有影响
-
反射调用优化-关闭访问检查
- Method和Field、Constructor对象都有setAccessible()方法
- setAccessible作用是启动和禁用访问安全检查的开关
- 参数值为ture表示 反射的对象在使用时取消访问检查,提高反射的效率、参数值为false则表示反射的对象执行访问检查
3.Class类
- 基本介绍
-
Class也是类,因此也继承Object类
-
Class类对象不是new出来的,而时系统创建的
-
对于某个类的Class对象,在内存中只有一份,因为类只加载一次
-
每个类的实例都会记得自己是由哪一个Class实例所生成的
-
通过Class对象可以完整的得到一个类的完整结构,通过一系列API
-
Class是存放在堆中的
-
类的字节码二进制数据是存放在方法区在,有的地方称为类的元数据(包括 方法代码,变量名,方法名,访问权限等等)
-
Class类的常用方法
- static Class forName(String name):返回指定类名name的Class对象
- Object newInstance():调用无参构造器,返回该Class对象的一个实例
- getName():返回此Class对象所表示的实体(类,接口,数组类,基本类型等)名称
- Class[] getInterfaces():获取当前Class对象的接口
- ClassLoader getClassLoader():获取该类的类加载器
- Class getSuperclass():返回表示此Class所表示的实体的超类的Class
- Constructor[] getConstructors():返回一个包含某Constructor对象的数组
- Field[] getDeclaredFields():返回Field对象的一个数组
- Method getMethod():返回一个Method对象,此对象的形参类型为paramType
4.获取 Class 类对象
-
前提:已知一个类的全类名,且该类在路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException
实例:Class cls = Class.forName(“com.hspedu.Cat”)
应用场景:多用于配置文件,读取类全路径,加载类
-
前提:若已知具体的类,通过类的.class获取,该方式 最为安全可靠,程序性能最高
实例:Class cls = Cat.class
应用场景:多用于参数传递,比如通过反射得到对应构造器对象
-
前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象
实例:Class cls = 对象.getClass();//运行类型
应用场景:通过创建好的对象,获取Class对象
-
其他方式
ClassLoader cl = 对象.getClass().getClassLoader();
Class cls = cl.loadClass(“类的全类名”)
-
基本数据(int,char,boolean,float,double,byte,long,short)按如下方式得到Class类对象
Class cls = 基本数据类型.class
-
基本数据类型对应的包装类,可以通过 .TYPE 得到Class类对象
Class cls = 包装类.TYPE
5.哪些类型有Class对象
- 如下类型有Class对象
- 外部类,成员内部类,静态内部类,局部内部类,匿名内部类
- interface:接口
- 数组
- enum:枚举
- annotation:注解
- 基本数据类型
- void
6.类加载
-
基本说明
反射机制是java实现动态语言的关键,也就是通过反射实现类动态加载
- 静态加载:编译时加载相关的类,如果没有则报错,依赖性太强
- 动态加载:运行时加载相关的类,如果运行时不用该类,即使不存在该类,也不会报错,降低了依赖性
-
类加载时机
- 当创建对象时(new)//静态加载
- 当子类被加载时,父类也加载//静态加载
- 调用类中的静态成员和方法时//静态加载
- 通过反射//动态加载
-
类加载过程图
-
类加载各阶段完成任务
-
加载阶段
JVM 在该阶段的主要目的是将类的字节码从不同的数据源(可能是.class文件、也可能是jar包,甚至网络)转换成二进制字节流加载到内存中,并生成一个代表该类的java.lang.Class对象
-
连接阶段-验证
- 目的是为了确保.Class文件的字节流中包含信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
- 包括:文件格式验证(是否以魔数 oxcafebabe开头)、元数据验证、字节码验证和符号引用验证
- 可以考虑使用 -Xverify:none 参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间
-
连接阶段-准备
-
JVM 会在该阶段对静态变量,分配内存并默认初始化(对应数据类型的默认初始值,如 0、0L、 null、 false等),这些变量使用的内存都在方法区分配
package com.hspedu.reflection.classload_; /** * @author 韩顺平 * @version 1.0 * 我们说明一个类加载的链接阶段-准备 */ public class ClassLoad02 { public static void main(String[] args) { } } class A { //属性-成员变量-字段 //老韩分析类加载的链接阶段-准备 属性是如何处理 //1. n1 是实例属性, 不是静态变量,因此在准备阶段,是不会分配内存 //2. n2 是静态变量,分配内存 n2 是默认初始化 0 ,而不是 20 //3. n3 是 static final 是常量, 他和静态变量不一样, 因为一旦赋值就不变 n3 = 30 public int n1 = 10; public static int n2 = 20; public static final int n3 = 30; }
-
-
连接阶段-解析
- 虚拟机将常量池内的符号引用替换为直接引用的过程
-
Initialization 初始化
- 到初始化阶段,才真正开始执行类中的java代码,此阶段是执行==()==方法的过程
- ==()==方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并
- 虚拟机会保证一个类的==()== 方法在多线程环境中被正确的加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的()方法,其他线程都需阻塞等待,直到活动线程执行()方法完毕
7.通过反射获取类的结构信息
-
第一组:java.lang.Class类
- getName:获取全类名
- getSimpleName:获取简单类名
- getFields:获取本类中的所有public属性,包含本类以及父类的
- getDeclaredFields:获取该类的所有属性
- getMethods:获取该类的所有public方法
- getDeclaredMethods:获取该类的所有方法
- getConstructors:获取该类的所有public构造器
- getDeclaredConstructors:获取该类的所有构造器
- getpackage:以package形式返回 包信息
- getSuperClass:以Class形式返回父类信息
- getInterfaces:以Class[] 形式返回接口信息
- getAnnotations:以Annotation[] 形式返回注解信息
-
第二组:java.lang.reflect.Filed 类
-
getModifiers:以int形式返回修饰符
【说明:默认修饰符是 0,public 是 1,private 是 2,protected 是 4,static 是 8,final 是 16,public+static 是 9】
-
getType:以class形式返回类型
-
getName:返回属性名
-
-
第三组:java.lang.reflect.Method 类
- getModifiers:以int形式返回修饰符
- getReturnType:以class形式获取 返回类型
- getName:返回方法名
- getParameterTypes:以class[]返回参数类型数组
-
第四组:java.lang.reflect.Constructor
- getModifiers:以int形式返回修饰符
- getName:返回构造器名(全类名)
- getParameterTypes:以class[]形式返回参数类型数组
8.通过反射创建对象
- 方式一:调用类中的public修饰的无参构造器
- 方式二:调用类中的指定构造器
- Class类相关方法:
- newInstance:调用类中的无参构造器,获取对应类的对象
- getConstructor(Class…cls):根据参数列表,获取对应的public构造器对象
- getDecalaredConstructor(class…cls):根据参数列表,获取对应的所有构造器
- Constructor类相关方法
- setAccessible:暴破
- newInstance(Object…obj):调用构造器
9.通过反射访问类中的成员
-
访问属性
-
根据属性名获取Field对象
Field name = cls.getDeclaredField(“name”)
-
暴破:name.setAccessible(true);
-
访问
name.get(o);
name.set(o,值);//o是对象
-
注意:如果是静态属性,则set和get中的参数o,可以写成null
-
-
访问方法
-
根据方法名和参数列表获取Method方法对象:Method m = cls.getDeclaredMethod(methodName,param.class)
-
获取对象:Object o = cls.newInstance();
-
暴破:m.setAccessible(true);
-
访问:Object returnValue = m.invoke(o,param.class);
-
访问
name.get(o);
name.set(o,值);//o是对象
-
注意:如果是静态属性,则set和get中的参数o,可以写成null
-
-
访问方法
- 根据方法名和参数列表获取Method方法对象:Method m = cls.getDeclaredMethod(methodName,param.class)
- 获取对象:Object o = cls.newInstance();
- 暴破:m.setAccessible(true);
- 访问:Object returnValue = m.invoke(o,param.class);
- 注意:如果是静态方法,o可以是null