JAVA 反射
JAVA 反射
什么是JAVA反射
- 就是在运行时候可以动态的获取成员变量,方法。动态调用该类的方法。被称之为JAVA反射
- 要使用该类的反射,必须获取到该类的字节码对象.解剖该类就要使用到Class类中的方法。所以先要获取到每一个字节码文件对应的Class类型的对象。
- 反射就是把JAVA类中的各种成分映射成一个个java对象。
- 例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象
类的加载过程
-
Class 对象的由来是将clas文件读入内存,并为之创建一个Class对象。
-
- 编译: .java代码源文件 通过编译器转换为.class字节码
-
- 加载: .class 字节码加载到JVM方法区
-
类加载器(ClassLoader)
- BootStrapClassLoader (启动类加载器) 加载java 核心类库 JAVA_HOME\lib目录下的rt.jar中的类。
- Extension ClassLoader (扩展类加载器) 加载JAVA_HOME\lib\ext目录中的类库
- Application Class Loader (应用程序类加载器) 它负责加载用户类路径(classpath)上指定的类库。如果应用程序中没有使用自定义的类加载器,那么这个类加载器通常是程序中默认的类加载器
它们按照层次关系进行组织,而且每个类加载器都有自己独立的命名空间,保证了不同类加载器之间的隔离性
Java虚拟机将会检查类文件的格式、语义等内容,确保其符合Java规范,否则将抛出ClassFormatError等异常
-
- 校验: 校验.class是否合法
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用验证
-
- 分配内存: 为类分配内存,类变量分配到内存并初始化为为二进制
-
- 解析: 将常量池中的符合引用改为直接引用
-
- 初始化:初始化类变量,静态语句块
-
- 使用: 使用
-
- 卸载:卸载
-
类加载器什么事双亲委派模型?
- 类加载器的双亲委派模型是指当一个类加载器需要加载一个类时,它首先会将这个任务委托给它的父类加载器去完成,如果父类加载器无法加载,则再由自己来尝试加载
- 双亲委派模型保证了 Java 程序的稳定运行,可以避免类的重复加载(JVM 区分不同类的方式不仅仅根据类名,相同的类文件被不同的类加载器加载产生的是两个不同的类),也保证了 Java 的核心 API 不被篡改。
-
类加载器的缓存机制是什么?
- 类加载器的缓存机制是指当一个类被某个类加载器加载后,该类及其依赖的类将被缓存到该类加载器中,以供后续使用
-
如何自定义类加载器?
- 以通过继承ClassLoader类并重写findClass()方法来自定义类加载器。通常情况下,自定义类加载器会从指定的路径或者网络地址上加载字节码文件
-
什么是热部署(HotSwap)?怎样实现Java代码的热部署?
- 热部署是指在不停止Java应用程序的情况下,动态地替换或更新Java类或资源文件。实现Java代码的热部署可以使用一些工具,如JRebel、DCEVM等。这些工具通常通过改变类加载器的行为,使得修改后的Java类能够被重新加载到JVM中。
-
JVM 判定两个 Java 类是否相同的具体规则
- JVM 不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。即使两个类来源于同一个 Class 文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相同
-
双亲委派模型带来的问题,以及如何处理?
- 问题:
- 当两个类的全限定名相同的时候,就不可以同时加载。Tomcat 运行两个web服务时候就会出现冲突(相同类名) tomcat为每一个应用分配一个应用类加载器。实现了web应用的隔离 打破双亲委派机制。
- 比如我们编写一个称为 java.lang.Object 类的话,那么程序运行的时候,系统就会出现两个不同的 Object 类。双亲委派模型可以保证加载的是 JRE 里的那个 Object 类,而不是你写的 Object 类。这是因为 AppClassLoader 在加载你的 Object 类时,会委托给 ExtClassLoader 去加载,而 ExtClassLoader 又会委托给 BootstrapClassLoader,BootstrapClassLoader 发现自己已经加载过了 Object 类,会直接返回,不会去加载你写的 Object 类。
- 如果我们不想打破双亲委派模型,就重写 ClassLoader 类中的 findClass() 方法即可,无法被父类加载器加载的类最终会通过这个方法被加载。但是,如果想打破双亲委派模型则需要重写 loadClass() 方法
- 类加载器在进行类加载的时候,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成(调用父加载器 loadClass()方法来加载类)
- 问题:
Class 详解
- Class 类的实例表示正在运行的Java 应用程序中的类和接口。也是jvm中有N多的实例每个类都有该Class对象(包括基本数据类型)
- Class 没有公共构造方法。Class 对象是在加载类时由Java虚拟机调用类加载器中的fefineClass方法自动构造的。也就是不需要我们去处理创建对象,JVM已经帮我们创建好了。
-
获取Class对象的三种方式
- Object -> getClass 方法
- 任何对象包含基本数据类型都有一个静态的class的属性
- 通过Class类的静态方法 forName 指定类的所在的全路径(包含包名和类名)
其中1是因为Object类中的getClass方法、因为所有类都继承Object类。从而调用Object类来获取
一般使用第三种,第一种基本已经拿到了对象,无需在反射; 第二种需要显示导入包,无包路径则编译报错,强依赖;第三种 传入一个字符串 可以从配置文件中读取 方便
在运行期间,一个类,只有一个Class对象产生。 -
获取构造
- 获取公有
- 获取私有 私有方法需要 con.setAccessible(true)
- 支持批量获取
- newInstance是 Constructor类的方法(管理构造函数的类) 返回的是Object类
-
获取成员变量并调用
-
Field[] getFields()获取所有的公有字段
-
Field[] getDeclaredFields() 获取所有字段,包括:私有、受保护、默认、公有;
-
Field getField(String fieldName) 获取某个公有的字段
-
Field getDeclaredField(String fieldName):获取某个字段(可以是私有的)
-
设置字段的值 ield --> public void set(Object obj,Object value): obj:要设置的字段所在的对象; value:要为字段设置的值
-
内省机制
- 由于发射机制对成员字段操作不方便(成员字段私有化,在获取权限后 才能操作);java提出了内省机制解决该问题。
- 反射机制强调不使用new关键字实例化对象,故不推荐有参构造器赋值。内省机制实现原理是获取对应字段的set方法进行赋值(私有化成员赋值有参构造器或set方法)
- 核心类 Introspector
- 内省操作字段示例
// JavaBean转换Map集合 public class EmployeeTest { @Test public void javaBeanToMap()throws Exception{ // 创建一个Map集合并赋值 Map<String,Object> map=new HashMap<>(); Employee ep=new Employee(1L,"橘子",true); // 通过内省获得类信息 BeanInfo eps= Introspector.getBeanInfo(Employee.class, Object.class); // 获取字段数组 PropertyDescriptor[] pds=eps.getPropertyDescriptors(); for (PropertyDescriptor pd:pds) { // 分别取一个属性的key和value 放入Map集合中 String key=pd.getName(); Object value=pd.getReadMethod().invoke(ep); map.put(key,value); } System.err.println(map); } } // Map集合转JavaBean public class EmployeeTest { @Test public void mapToJavaBean()throws Exception{ // 创建一个Map集合并赋值 Map<String,Object> map=new HashMap<>(); map.put("id",66L); map.put("name","火龙果"); map.put("isWork",false); // 通过内省获得类信息 Employee ep=Employee.class.newInstance(); BeanInfo eps= Introspector.getBeanInfo(Employee.class, Object.class); // 获取字段数组 PropertyDescriptor[] pds=eps.getPropertyDescriptors(); for (PropertyDescriptor pd:pds) { // 分别取一个属性的key和value 放入Map集合中 String key=pd.getName(); Object value=map.get(key); // 将值赋值给字段 pd.getWriteMethod().invoke(ep,value); } System.err.println(ep); } }
-
-
获取成员方法并调用
- Method[] getMethods():获取所有"公有方法";(包含了父类的方法也包含Object类)
- Method[] getDeclaredMethods():获取所有的成员方法,包括私有的(不包括继承的)
- Method getMethod(String name,Class<?>... parameterTypes): name : 方法名 Class ... : 形参的Class类型对象
- Method getDeclaredMethod(String name,Class<?>... parameterTypes)
- 调用方法:public Object invoke(Object obj,Object... args): obj : 要调用方法的对象 args:调用方式时所传递的实参
-
反射方法使用场景
- 通过反射运行配置文件内容 当类发生变化只需要修改配置文件 无需修改代码.
- 通过反射越过泛型检查
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具