JAVA 反射

JAVA 反射

什么是JAVA反射

  • 就是在运行时候可以动态的获取成员变量,方法。动态调用该类的方法。被称之为JAVA反射
  • 要使用该类的反射,必须获取到该类的字节码对象.解剖该类就要使用到Class类中的方法。所以先要获取到每一个字节码文件对应的Class类型的对象。
  • 反射就是把JAVA类中的各种成分映射成一个个java对象。
  • 例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象

类的加载过程

img

  • Class 对象的由来是将clas文件读入内存,并为之创建一个Class对象。

    1. 编译: .java代码源文件 通过编译器转换为.class字节码
    1. 加载: .class 字节码加载到JVM方法区
    • 类加载器(ClassLoader)

      • BootStrapClassLoader (启动类加载器) 加载java 核心类库 JAVA_HOME\lib目录下的rt.jar中的类。
      • Extension ClassLoader (扩展类加载器) 加载JAVA_HOME\lib\ext目录中的类库
      • Application Class Loader (应用程序类加载器) 它负责加载用户类路径(classpath)上指定的类库。如果应用程序中没有使用自定义的类加载器,那么这个类加载器通常是程序中默认的类加载器

      它们按照层次关系进行组织,而且每个类加载器都有自己独立的命名空间,保证了不同类加载器之间的隔离性
      Java虚拟机将会检查类文件的格式、语义等内容,确保其符合Java规范,否则将抛出ClassFormatError等异常

    1. 校验: 校验.class是否合法
    • 文件格式验证
    • 元数据验证
    • 字节码验证
    • 符号引用验证
    1. 分配内存: 为类分配内存,类变量分配到内存并初始化为为二进制
    1. 解析: 将常量池中的符合引用改为直接引用
    1. 初始化:初始化类变量,静态语句块
    1. 使用: 使用
    1. 卸载:卸载
  • 类加载器什么事双亲委派模型?

    • img
    • 类加载器的双亲委派模型是指当一个类加载器需要加载一个类时,它首先会将这个任务委托给它的父类加载器去完成,如果父类加载器无法加载,则再由自己来尝试加载
    • 双亲委派模型保证了 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已经帮我们创建好了。
  1. 获取Class对象的三种方式

    1. Object -> getClass 方法
    2. 任何对象包含基本数据类型都有一个静态的class的属性
    3. 通过Class类的静态方法 forName 指定类的所在的全路径(包含包名和类名)

    其中1是因为Object类中的getClass方法、因为所有类都继承Object类。从而调用Object类来获取
    一般使用第三种,第一种基本已经拿到了对象,无需在反射; 第二种需要显示导入包,无包路径则编译报错,强依赖;第三种 传入一个字符串 可以从配置文件中读取 方便
    在运行期间,一个类,只有一个Class对象产生。

  2. 获取构造

    1. 获取公有
    2. 获取私有 私有方法需要 con.setAccessible(true)
    3. 支持批量获取
    4. newInstance是 Constructor类的方法(管理构造函数的类) 返回的是Object类
  3. 获取成员变量并调用

    1. Field[] getFields()获取所有的公有字段

    2. Field[] getDeclaredFields() 获取所有字段,包括:私有、受保护、默认、公有;

    3. Field getField(String fieldName) 获取某个公有的字段

    4. Field getDeclaredField(String fieldName):获取某个字段(可以是私有的)

    5. 设置字段的值 ield --> public void set(Object obj,Object value): obj:要设置的字段所在的对象; value:要为字段设置的值

    6. 内省机制

      1. 由于发射机制对成员字段操作不方便(成员字段私有化,在获取权限后 才能操作);java提出了内省机制解决该问题。
      2. 反射机制强调不使用new关键字实例化对象,故不推荐有参构造器赋值。内省机制实现原理是获取对应字段的set方法进行赋值(私有化成员赋值有参构造器或set方法)
      3. 核心类 Introspector
      4. 内省操作字段示例
      // 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);
            }
         }
      
      
  4. 获取成员方法并调用

    1. Method[] getMethods():获取所有"公有方法";(包含了父类的方法也包含Object类)
    2. Method[] getDeclaredMethods():获取所有的成员方法,包括私有的(不包括继承的)
    3. Method getMethod(String name,Class<?>... parameterTypes): name : 方法名 Class ... : 形参的Class类型对象
    4. Method getDeclaredMethod(String name,Class<?>... parameterTypes)
    5. 调用方法:public Object invoke(Object obj,Object... args): obj : 要调用方法的对象 args:调用方式时所传递的实参
  5. 反射方法使用场景

    1. 通过反射运行配置文件内容 当类发生变化只需要修改配置文件 无需修改代码.
    2. 通过反射越过泛型检查

参考博客

posted @ 2024-04-10 12:02  贺艳峰  阅读(18)  评论(0编辑  收藏  举报