# 类的生命周期

  • 同一个 JVM 中的所有线程、所有变量都处于同一个进程里
  • Java 的核心类库在 jre/lib/rt.jar 文件中
  1. 类的加载:将类的 class 文件(二进制数据)读入内存,并创建一个对应的 java.lang.Class 对象(由类加载器完成)
  2. 类的连接:把类的二进制数据合并到 JRE 中
    包括:验证、准备(为类的类变量分配内存并设置默认初始值 0 或 null)、解析
  3. 类的初始化:对类变量进行初始化
    包括:声明类变量时指定初始值、使用静态初始化块为类变量指定初始值(执行顺序与它们在源代码中的排列顺序相同)

类的生命周期

当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过加载、连接、初始化三个步骤来对该类进行初始化

类加载器通常无须等到“首次使用”该类时才加载该类,Java 虚拟机规范允许系统预先加载某些类

# 类加载器 ClassLoader

类加载器的层级关系

  1. 启动类加载器(Bootstrap ClassLoader):是由 JVM 自身实现的(用 C 语言),负责加载存放在 $JAVA_HOME/jre/lib 下,或被 -Xbootclasspath 参数指定的路径中的,并且能被虚拟机识别的类库(如 rt.jar,所有的 java. 开头的类均被 Bootstrap ClassLoader 加载)。启动类加载器无法被 Java 程序直接引用的。
  2. 扩展类加载器(Extension ClassLoader):由 sun.misc.Launcher$ExtClassLoader 实现,负责加载 $JAVA_HOME/jre/lib/ext 目录中,或者由 java.ext.dirs 系统变量指定的路径中的所有类库(如 javax.开头的类)。开发者可以直接使用扩展类加载器。
  3. 应用程序类加载器(Application ClassLoader):由 sun.misc.Launcher$AppClassLoader 来实现,负责加载用户类路径(ClassPath)所指定的类。开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
  4. 自定义类加载器:继承 ClassLoader,可打破双亲委派模型。

由 JVM 自带的三种类加载器加载的类在虚拟机的整个生命周期中是不会被卸载的,只有用户自定义的类加载器所加载的类才可以被卸载

# 类加载隔离

  • 在 Java 中,一个类用其全限定类名(包括包名和类名)作为标识,但在 JVM 中,一个类用其全限定类名加载它的类加载器作为其唯一标识。每一个类加载器,都拥有一个独立的类名称空间。
  • 即使来源于同一个 Class 文件,被同一个 Java 虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。
  • 这里所指的“相等”,包括代表类的 Class 对象的 equals()、isAssignableFrom()、islnstance() 方法的返回结果,也包括了使用 instanceof 关键字做对象所属关系判定等各种情况。

# 双亲委派模型

  • 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的加载器都是如此,因此所有的类加载请求都会传给顶层的启动类加载器
  • 只有当父载器反馈自己无法完成该加载请求(该加载器的搜索范围中没有找到对应的类)时,子加载器才会尝试自己去加载
  • 双亲委派模式的好处:避免类的重复加载;避免了 Java 的核心 API 被篡改

# 类加载机制

  • 全盘负责:当一个类加载器负责加载某个 Class 时,该 Class 所依赖的和引用的其他 Class 也将由该类加载器负责载入,除非显式使用另外一个类加载器来载入
  • 父类委托:先让 parent(父)类加载器试图加载该 Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
  • 缓存机制:所有加载过的 Class 都会被缓存,当程序中需要使用某个 Class 时,类加载器先从缓存区中搜寻该 Class,只有当缓存区中不存在该 Class 对象时,系统才会读取该类对应的二进制数据,并将其转换成 Class 对象,存入缓存区中

# SPI

  • SPI:Service Provider Interface(服务提供者接口),一种服务提供发现机制
  • 本质是将接口的实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载对应接口的实现类
  • 服务是一个熟知的接口和类(通常为抽象类)集合
  • 服务提供者是服务的特定实现,提供者中的类通常实现接口,并子类化在服务本身中定义的子类

# Java SPI 机制

  • ServiceLoader<S>

  • 约定:配置文件放置在 META-INF/services/ 目录下,文件名为接口的全限定名,文件内容为接口实现类的全限定名

  • 注意事项:

    1. 实现类必须具有不带参数的构造方法,以便它们可以在加载中被实例化
    2. 文件名称是服务接口类的完全限定类名
    3. 文件内若有多个实现类,每行一个(末尾不要有空格和 , 等符号)
    4. 文件必须使用 UTF-8 编码
  • 如 mysql-connector-java.jar 包中的 META-INF/services/java.sql.Driver 文件内容 com.mysql.jdbc.Driver, JDBC 驱动加载 java.sql.DriverManager

    // java.sql.DriverManager#loadInitialDrivers
    ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
    Iterator<Driver> driversIterator = loadedDrivers.iterator();
    
    1
    2
    3

# Spring SPI 机制

  • SpringFactoriesLoader
  • 约定:配置文件放置在 META-INF/ 目录下,文件名必须为 spring.factories;文件内容为键值对,一个键可以有多个值,多个值时需要用逗号分割,同时键值都需要是类的全限定名
  • 键和值可以没有任何类与类之间的关系,也可以有实现的关系
  • 使用场景:
    1. 自动装配:在 Spring Boot3.0 之前的版本,自动装配是通过 SpringFactoriesLoader 来加载的,但是 SpringBoot 3.0 后不再使用 SpringFactoriesLoader,而是 Spring 从 META-INF/spring/ 目录下的 org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中读取
    2. PropertySourceLoader 的加载

# Dubbo SPI 机制 (opens new window)

  • ExtensionLoader

# Type 接口

  • Type 接口代表所有类型的公共高级接口,Type 包括原始类型、参数化类型、数组类型、类型变量和基本类型等

  • Class 是 Type 接口的实现类

  • ParameterizedType 子接口代表带泛型参数的类型

    • Type getRawType():返回没有泛型信息的原始类型
    • Type getOwnerType():返回该类型的所有者类型(对内部类而言,返回所在类的类型)
    • Type[] getActualTypeArguments():返回泛型参数的类型
    // 获取该类实现接口中的泛型参数的类型
    Class<T> tClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    
    1
    2

# 通过反射查看类信息

  • 反射:在运行时期,动态地去获取一个类中的信息(类的信息、构造器信息、方法信息、字段等信息)

# 获得 Class 对象

  • 在 JVM 中,一个类用其全限定类名其类加载器作为其唯一标识
  • 包装类和 Void 类的静态字段 TYPE 表示其基本类型的 Class 对象
  • 所有的具有相同的维数和相同元素类型的数组共享同一个 Class 对象
  1. 使用 Class 类的 Class<?> forName(String className) 类方法(该字符串参数的值是某个类的全限定类名,会对类初始化)
  2. 调用某个类的 class 属性来获取该类对应的 Class 对象(所有的数据类型都有 class 属性,包括数组、基本类型以及 void
  3. 调用某个对象的 Class<?> getClass() 方法(可能需要强转)

# 从 Class 中获取信息

  1. 获取 Class 对应类所包含的构造器(由 Constructor 对象表示)
    Constructor<T> getConstructor(Class<?>… parameterTypes):获取此 Class 对象对应类的、带指定形参列表public 构造器
    Constructor<T> getDeclaredConstructor(Class<?>.. parameterTypes):获取此 Class 对象对应类的、带指定形参列表的构造器,与访问权限无关
    Constructor<?>[] getConstructors():获取此 Class 对象对应类的所有 public 构造器
    Constructor<?>[] getDeclaredConstructors():获取此 Class 对象对应类的所有构造器,与访问权限无关
  2. 获取 Class 对应类所包含的方法(由 Method 对象表示)
    Method getMethod(String name, Class<?>.. parameterTypes):获取此 Class 对象对应类的、带指定形参列表的 public 方法(包括继承的方法)
    Method getDeclaredMethod(String name, Class<?>.. parameterTypes):获取此 Class 对象对应类的、带指定形参列表的方法,与访问权限无关(不包括继承的方法)
    Method[] getMethods():获取此 Class 对象所表示的类的所有 public 方法(包括继承的方法)
    Method[] getDeclaredMethods():获取此 Class 对象对应类的全部方法,与访问权限无关不包括继承的方法)
  3. 访问 Class 对应类所包含的字符(由 Field 对象表示)
    Field getField(String name):获取此 Class 对象对应类的、指定名称的 public 成员变量(包括继承的字段)
    Field getDeclaredField(String name):获取此 Class 对象对应类的、指定名称的成员变量,与成员变量的访问权限无关(不包括继承的字符)
    Field[] getFields():返回此 Class 对象对应类的所有 public 成员变量(包括继承的字符)
    Field[] getDeclaredFields():获取此 Class 对象对应类的全部成员变量,与成员变量的访问权限无关(不包括继承的字符)
  4. 通过匿名内部类对象获取当前方法名称:new Object(){}.getClass().getEnclosingMethod().getName()
  5. 其它实例方法
    ClassLoader getClassLoader():获取该类的类加载器
    Class<?>[] getInterfaces():获取此 Class 对象所表示的类或接口实现的接口
    Class<? super T> getSuperclass():获取该 Class 对象对应类的超类的 Class 对象 int getModifiers():获取此类或接口的所有修饰符(返回的整数应使用 Modifier 工具类的方法来解码)
    Package getPackage():获取此类的包
    String getName():以字符串形式返回此 Class 对象所表示的类的名称
    String getSimpleName():以字符串形式返回此 Class 对象所表示的类的简称 Class<?> getComponentType():返回表示数组元素类型的 Class
    isArray()isEnum()
    isInterface()isInstance(Object obj)
    isAssignableFrom(Class<?> cls):判定此 Class 对象所表示的类或接口是否是 cls,或者是否是 cls 的超类或超接口
    T cast(Object obj):将一个对象强制转换成此 Class 对象所表示的类或接口
    Type getGenericSuperclass():返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的直接超类的 Type
    Class<?> getComponentType():返回数组元素类型的 Class

# 使用反射生成并操作对象

  • Constructor、Method、Field 都在 java.lang.reflect 包下,直接父类都是 AccessibleObject

  • 如果反射的对象需要调用对应类中的 private 修饰的构造器、方法或成员变量,应调用 Constructor 对象、Method 对象或 Field 对象的如下方法:
    void setAccessible(boolean flag):设置此对象的 accessible 标志为指定的布尔值(值为 true 时,指示反射的对象在使用时取消访问权限检查

  • 先检查要调用的构造器或方法是否有 public 修饰,再检查是否有 private 修饰

# 创建对象

  • 方式 1:
    使用 Class 对象的 T newInstance()方法来创建该 Class 对象对应类的实例,要求该 Class对象的对应类有无参数构造器

  • 方式 2:
    先使用 Class 对象获取指定的构造器 Constructor 对象,再调用 Constructor 对象的T newInstance(Object... initargs)方法来创建该 Class 对象对应类的实例

# 调用方法

  • 先使用 Class 对象获取指定的方法 Method 对象,再调用 Method 对象的 invoke() 方法
    Object invoke(Object obj, Object...args):该方法中的 obj 是执行该方法的对象,后面的 args 是执行该方法时传入该方法的实参

  • 如果调用的是类方法,第一个参数设置为 null

  • 如果调用是数组型参数(形参个数可变)的方法,把实际参数作为 Object[] 的元素再传递,即: Object invoke(执行该方法的对象, new Object[]{ 所有实参 })

  • 返回值的类型是 Object ,可能需要强转

# 访问成员变量值

  • Field 提供了如下两组方法来读取或设置成员变量值:
    Object get(Object obj):获取 obj 对象上此 Field 表示的字段的值
    xxx getXxx(Object obj):获取 obj 对象的该成员变量的值(此处的 Xxx 对应 8 种基本类型,如果该成员变量的类型是引用类型,则取消 get 后面的 Xxx)
    void setXxx(Object obj, Xxx val):将 obj 对象的该成员变量设置成 val 值(此处的 Xxx 对应 8 种基本类型,如果该成员变量的类型是引用类型,则取消 set 后面的 Xxx)

# 操作数组

  • 在 java. lang. reflect 包下的 Array 类中的类方法:
    Object newInstance(Class<?>componentType,int...length):创建一个具有指定的元素类型、指定维度的新数组
    int getLength(Object array):以 int 形式返回指定数组对象的长度
    xxx getXxx(Object array, int index):返回 array 数组中第 index 个元素
    void setXxx(Object array, int index, xxx val):将 array 数组中第 index 个元素的值设为 val

  • 其中 xxx 是各种基本数据类型,如果数组元素是引用类型,则方法变成
    get(ObjeCt array, int index)
    set(Object array, int index, Object val)

# 使用反射加载资源文件

  1. Class 类中的 InputStream getResourceAsStream(String name)
    name 不以 '/' 开头时默认是从当前类的字节码所在的路径去加载资源,以 '/' 开头则是从 classpath 的根路径去加载资源
  2. ClassLoader 类中的 InputStream getResourceAsStream(String name)
    classpath 的根路径去加载资源,返回所读取资源的输入流,name 不能以 '/' 开头
  • 获取 ClassLoader 对象的方法
    1. Class 类中的 ClassLoader getClassLoader():返回该类的类加载器
    2. Thread 类中的 ClassLoader getContextClassLoader():返回该线程的上下文 ClassLoader

# 使用反射生成 JDK 动态代理

  • 通过使用 Proxy 类和 InvocationHandler 接口可以生成 JDK 动态代理类或动态代理对象,即在程序中为一个或多个接口动态地生成实现类或创建实例
  • 特点:
    1. 代理的对象必须有实现的接口
    2. 需要为每个对象创建代理对象
    3. 动态代理的最小单位是类(所有类中的方法都会被处理)

# Proxy 类

  • 构造器:protected Proxy(InvocationHandler h)

  • 类方法

    • Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces):创建一个动态代理类所对应的 Class 对象,该代理类将实现 interfaces 所指定的多个接口(第一个 ClassLoader 参数指定生成动态代理类的类加载器)
    • Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h):使用指定的 InvocationHandler 创建一个动态代理对象,该代理对象的实现类实现了 interfaces 指定的系列接口,执行代理对象的每个方法时都会被替换执行 InvocationHandler 对象的 invoke 方法

# InvocationHandler 接口

  • 执行动态代理对象的所有方法时,都会被替换成执行 invoke 方法 Object invoke(Object proxy, Method method, Object[] args)
    • proxy:代表动态代理对象
    • method:代表正在执行的方法
    • args:代表调用目标方法时传入的实参
public class MyInvocationHandler implements InvocationHandler {
    private Object target; // 需要被代理的对象

    // 在构造函数中初始化 target 对象
    public MyInvocationHandler(Object target) {
        this.target = target;
    }
    
    // 执行动态代理对象的所有方法时,都会被替换成执行如下的 invoke 方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
        ... // 执行其它方法
        // 以 target 作为主调来执行 method 方法
        Object result = method.invoke(target, args);
        ... // 执行其它方法
        return result;
    }
}

// 获取 JDK 动态代理对象的方法
public <T> T getProxyObject(T target) {
    MyInvocationHandler handler = new MyInvocationHandler(target);
    // 注意:动态创建的代理对象只能赋给 target 所实现的接口类型变量
    return (T)Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);
}

// 将 JDK 动态代理生成的 class 字节码输出到本地文件系统
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# cglib 动态代理

  • 一个基于 ASM 的、强大的、高性能、高质量的字节码生成库
  • 原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对 final 修饰的类进行代理
// 自定义的 MethodInterceptor 需实现 org.springframework.cglib.proxy.MethodInterceptor

// 获取 cglib 动态代理对象的方法
public <T> T getProxyObject(T target) {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(target.getClass()); // 设置代理对象需要继承的类
    // 设置代理对象需要回调的方法
    enhancer.setCallback(new MethodInterceptor() {
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
                throws Throwable {
            ... // 执行其它方法
            // 这里 obj 是增强后的代理对象,所以不能调用 proxy.invoke(obj, args) 或 method.invoke(obj, args),否则会再次进入拦截器
            Object result = proxy.invokeSuper(obj, args);
            ... // 执行其它方法
            return result;
        }
    });
    return (T)enhancer.create(); // 创建一个代理对象
}

// 将 cglib 动态代理生成的 class 字节码输出到本地文件系统
// System.setProperty("cglib.debugLocation", "d:/");
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "d:/");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# ASM

  • 一个通用的 Java 字节码操纵和分析框架,可用于修改现有类或直接以二进制形式动态生成类

# Javassist

  • 一个开源的分析、编辑和创建 Java 字节码的类库
Updated at: 2024-04-22 15:04:27