章节9-反射&注解

126 阅读7分钟

类加载器

执行流程

  1. 编译

    • 在 Java 中 .java 文件是 Java 源文件,需要通过 Java 编译器(javac)将其编译成字节码文件,即.class文件。这个编译过程会将 Java 源文件中的代码转换为 JVM 能够理解的字节码指令。
  2. 程序启动

    ==注:== 当程序启动后, 并不会直接进行类加载, Java 采用了延迟加载机制, 只有当程序实际使用到某个类时, 才会触发类加载过程。

    • 类的延迟加载机制
      • 当程序首次主动使用某个类时,才会将该类的字节码载入方法区。主动使用的情况包括创建类的实例、访问类的静态变量或静态方法、调用类的构造方法等。
  3. 类加载

    1. 加载阶段

      • 类加载器负责查找字节码文件,并将其以二进制流的方式读入内存 (读入方法区) ,在内存 (方法区) 中生成一个代表该类的java.lang.Class对象。这个对象是后续在 Java 程序中访问和使用该类的基础
    2. 链接阶段

      • 验证阶段 : 确保被加载的类的字节码符合 JVM 规范,没有安全隐患,例如检查字节码的格式是否正确、是否存在非法的指令等。

      • 准备阶段 : 为类的静态变量分配内存空间,并设置默认初始值。例如,对于int类型的静态变量,会将其初始值设为 0;对于Object类型的静态变量,会将其初始值设为null。

      • 解析阶段 : 将类中的符号引用(全类名定位符)转换为直接引用。当字节码文件还未被加载时, 类中的各个类名,方法名等等都只是一个全类名定位符, 用于描述逻辑上的关系. 解析就是给这个定位符指向一块内存中的地址, 相当于定金, 但不会分配具体运行时内存容量.

        ==注:== 解析阶段不直接为字段、方法或类实例分配运行时内存,而是基于类文件的元数据和符号引用来确定布局和引用关系。JVM 通过字节码描述符、常量池以及元数据结构,能够在解析阶段准确判断字段和方法的容量需求,而实际的内存分配则延迟到类初始化或对象实例化时进行。

    3. 初始化阶段

      • 执行类的初始化代码,包括静态变量的显式赋值和静态代码块中的语句。这个阶段是类加载的最后一步,完成后类就可以被正常使用了。

类加载器的分类

  • Bootstrap ClassLoader:启动类加载器
    • 最顶层的类加载器,主要负责加载 Java 的核心类库,如java.lang、java.util等包下的类。这个类加载器是由 C++ 实现的,是 Java 虚拟机的一部分,它没有父类加载器。
    • 输出打印为null
  • Extension ClassLoader: 扩展类加载器
    • 上级加载器是 Bootstrap ClassLoader,主要负责加载 Java 的扩展类库,即%JRE_HOME%\lib\ext目录下的所有类库,或者由java.ext.dirs系统属性指定的目录下的类库。(jdk9以前)
    • 输出打印为ExtClassLoader
  • Application ClassLoader:应用程序类加载器
    • 上级加载器是 Extension ClassLoader,也称为系统类加载器,是负责加载应用程序 classpath 下的类库。一般来说,我们编写的 Java 代码所生成的类,都是由这个类加载器来加载的
    • 输出打印为AppClassLoader
  • ==注:== 上述三个加载器无继承关系, 三个加载器都是继承BuiltinClassLoader类的, 属于同级别. 只是进行类加载时, 层级不同而已, 应用程序类加载器为最底层, 启动类加载器为最顶层. 当需要进行类加载时, 会先判断最底层构造器是否加载, 依次往上. (双亲委派机制)
  • 自定义类加载器:

类加载器的工作机制

  • 双亲委派机制 : 和Hash表原理差不多
    1. 如果一个类加载器收到了类加载请求,它并不会自己先去加载
    2. 而是把这个请求委托给父类的加载器去执行
    3. 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归
    4. 请求最终将到达顶层的启动类加载器
    5. 如果父类加载器可以完成类加载任务,就成功返回
    6. 倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式
  • 作用 : 避免类的重复加载
  • 示例图 屏幕截图 2024-12-20 102036.png

反射机制

作用

  • 当程序首次主动使用某个类时, 会进行类加载, 根据对应 .class 文件生成Class对象, 一个类对应一个Class对象, 操作Class对象, 就是操作对应类的class文件, 反射就是通过获取 Class 对象进行操作 .class 文件 (对格式化的数据进行操作)

Class实现类测试

  • 获取Class对象

    • //使用全类名获取Class对象
      Class<?> aClass = Class.forName("com.itheima.pojo.Student");
      
      //使用类名.class获取Class对象
      Class<Student> bClass = Student.class;
      
      //使用Object类继承的getClass()方法获取Class对象
      Class<? extends Student> cClass = new Student().getClass();
      
  • Class对象作用

    • 获取类的成员
      • 获取构造器
        • Constructor<?>[] getConstructors() 返回所有公共构造方法对象的数组
        • Constructor<?>[] getDeclaredConstructors() 返回所有构造方法对象的数组 (暴力反射)
        • Constructor getConstructor(Class<?>... para) 返回单个公共构造方法对象
        • Constructor getDeclaredConstructor(Class<?>... para) 返回单个构造方法对象(暴力反射)
      • 获取成员变量
      • 获取成员方法
    • 创建类的实例
      • T newInstance(Object...para) 根据指定的构造方法创建对象
    • 判断类的关系
    • 获取类的父类和接口

注解

概述与作用

  • 常用方式

    1. 框架标注

      • 案例一

      • 原理 : 通过反射根据传入的Class对象, 获取到对象中的所有方法数组, 然后依次判断方法是否被数组标注, 如此注解的第一个作用就是做一个标记, 然后使用 isAnnotationPresent() 方法判断是否被标注, 然后进行处理

      • public class MyAnnoTest {
            public static void main(String[] args) throws Exception {
                methodTest(AnnoDemo.class);
            }
        
            //需求: 设计框架, 要求只要标注@MyTest注解的方法, 就会执行
            public static void methodTest(Class clazz) throws Exception {
                //通过传入的Class对象,获取所有的方法
                Method[] methods = clazz.getMethods();
                //遍历所有方法,判断上方是否有@MyTest注解
                for (Method method : methods) {
                    //MyTest.class : @MyTest注解的
                    if (method.isAnnotationPresent(MyTest.class)){ Class对象
                        //执行方法
                        method.invoke(clazz.newInstance());
                    }
                }
            }
        }
        
    2. 代替配置文件

      • XML配置文件开发原理
        • 使用servlet开发时, tomcat 会读取 web.xml 中配置的信息, 存储在map集合中. 然后根据请求路径 (键) 找到值 (servlet对象全类名) , 然后根据反射机制创建 servert 对象.
      • 注解的具体工作原理
        • ​ 程序启动时, Tomcat 使用类加载器扫描项目的所有 .class 文件, 然后通过反射检查是否存在如 @WebServlet@WebFilter 等注解 使用 isAnnotationPresent(MyTest.class) 方法. 一旦发现了符合条件的类,Tomcat 会将注解中的信息(如 URL 映射)存储到内部的映射表中,类似于 web.xml 中的配置信息。后面也就一样了.

自定义注解

  • 格式

  • public @interface MyTest {
        /**
         * 换个写法,与此对应
         * name() ==> name
         * default ==> =
         *String name="liny";
         */
        String name() default "liny";
    
        int age() default 23;
        
        //没有赋值,使用该注解时,必须传入值
        String address();
    
        Class clazz() default String.class;
    
        int[] arr() default {1, 2, 3};
    }
    

元注解

  • 作用, 使用在注解中, 为定义的注解添加规则

  • @Retention (RetentionPolicy.RUNTIME) : 可以理解为保留时间(生命周期)

    • public enum RetentionPolicy {
          //编译
          SOURCE,
      
          CLASS,
          
          RUNTIME
      }
      
  • @Target (ElementType.ANNOTATION_TYPE) : 指定了注解能在哪里使用

    • public enum ElementType {
          TYPE   			//类,接口
              
          FIELD  			//成员变量
              
          METHOD 			//成员方法
              
          PARAMETER		//方法参数
              
          CONSTRUCTOR		//构造方法
              
          LOCAL_VARIABLE	//局部变量
      }