Java的类加载、反射

JVM和类

当调用iava命令运行某个Java程序时,该命令将会启动一个Java虚拟机进程,不管该Java程序有多么复杂,该程序启动了多少个线程,它们都处于该Java虚拟机进程里。正如前面介绍的,同一个M的所有线程、所有变量都处于同一个进程里,它们都使用该JVM进程的内存区。当系统出现以下几种情况时,IM进程将被终止。
程序运行到最后正常结束
程序运行到使用System.exit()或Runtime.getRuntime().exit()代码处结束程序。
程序执行过程中遇到未捕获的异常或错误而结束。
程序所在平台强制结束了IM进程。

类的加载

定义

  • 将类的 .class 字节码文件加载到内存中,并生成对应的 java.lang.Class 对象(存储在方法区)。

关键步骤

  • 定位字节码:通过类加载器(ClassLoader)查找 .class 文件(如文件系统、JAR 包、网络等)。
  • 读取字节码:将字节码转换为二进制数据流。
  • 创建 Class 对象:在方法区生成类的运行时数据结构(字段、方法、接口等元数据)。

类加载器

  • 类的加载由类加载器完成,类加载器通常由JVM提供,JVM提供的这些类加载器通常被称为系统类加载器。
  • 开发者可以通过继承ClassLoader基类来创建自己的类加载器。
  • 触发条件:首次主动使用类时触发(如 new、静态方法调用、反射等)。

连接(Linking)

连接阶段分为三个子阶段:验证(Verification)、准备(Preparation) 和 解析(Resolution)。

验证(Verification)

  • 目的:确保字节码符合 JVM 规范,防止恶意代码破坏虚拟机。
  • 检查内容:
    • 文件格式(魔数、版本号)。
    • 元数据(类继承关系、方法重写)。
    • 字节码指令(操作数类型、跳转指令合法性)。

准备(Preparation)

  • 目的:为类的静态变量分配内存并设置默认初始值。
  • 关键点:
    • 静态变量的内存分配在方法区。
    • 初始值为类型默认值(如 int 初始化为 0,引用类型初始化为 null)。
    • final static 常量在此阶段直接赋值(若为字面量)。

解析(Resolution)

  • 目的:将符号引用(符号化的类、方法、字段名称)转换为直接引用(内存地址)。
  • 解析对象:
    • 类或接口的符号引用。
    • 字段和方法的符号引用。
    • 延迟解析:某些解析可能在初始化后发生(如动态绑定)。

初始化(Initialization)

定义

执行类的 () 方法(由编译器自动生成),为静态变量赋实际值并执行静态代码块。

关键规则

  • 主动触发条件:类的首次主动使用(如创建实例、访问静态字段、调用静态方法)。
  • 父类优先:保证父类的 () 先于子类执行。
  • 线程安全:() 方法由 JVM 加锁同步,确保只执行一次。

注意

  • 被动引用不会触发初始化:
  • 通过子类引用父类的静态字段(仅初始化父类)。
  • 通过数组定义引用类(如 MyClass[] arr = new MyClass[10])。
  • 访问类的 final static 常量(已在准备阶段赋值)。

类初始化的时机

当Java程序首次通过下面6种方式来使用某个类或接口时,系统就会初始化该类或接口。

  • 创建类的实例。为某个类创建实例的方式包括:使用new操作符来创建实例,通过反射来创建实例,通过反序列化的方式来创建实例。
  • 调用某个类的类方法(静态方法)
  • 访问某个类或接口的类变量,或为该类变量赋值。
  • 使用反射方式来强制创建某个类或接口对应的iava.lang.Class对象。

类加载器

Java 类加载器(ClassLoader)是 JVM 的重要组成部分,负责将类的字节码文件(.class)加载到内存中,并生成对应的 java.lang.Class 对象。类加载器采用双亲委派模型,确保类的唯一性和安全性。

类加载器的分类

Java 类加载器分为以下四类:

启动类加载器(Bootstrap ClassLoader)
  • 职责:加载 JVM 核心类库(如 java.lang.、java.util.),位于 $JAVA_HOME/jre/lib 目录。
  • 实现:由 C/C++ 实现,是 JVM 的一部分。
  • 特点:
    • 是唯一没有父类加载器的加载器。
    • 无法通过 Java 代码直接访问。
扩展类加载器(Extension ClassLoader)
  • 职责:加载扩展类库(如 javax.*),位于 $JAVA_HOME/jre/lib/ext 目录。
  • 实现:由 sun.misc.Launcher$ExtClassLoader 实现。
  • 特点:
    • 父类加载器是 Bootstrap ClassLoader。
    • 可以通过 ClassLoader.getSystemClassLoader().getParent() 获取。
应用程序类加载器(Application ClassLoader)
  • 职责:加载用户类路径(classpath)下的类。
  • 实现:由 sun.misc.Launcher$AppClassLoader 实现。
  • 特点:
    • 父类加载器是 Extension ClassLoader。
    • 是默认的系统类加载器,可以通过 ClassLoader.getSystemClassLoader() 获取。
自定义类加载器(Custom ClassLoader)
  • 职责:用户自定义的类加载器,用于加载非标准路径的类(如网络、加密文件)。
  • 实现:继承 java.lang.ClassLoader,重写 findClass() 方法。
  • 特点:
    • 父类加载器是 Application ClassLoader。
    • 可以实现热部署、模块化加载等功能。

双亲委派模型(Parent Delegation Model)

双亲委派模型是类加载器的工作机制,确保类的唯一性和安全性。

工作原理
  • 委派:当一个类加载器收到加载请求时,首先将请求委派给父类加载器。
  • 加载:如果父类加载器无法加载(如找不到类),则由当前类加载器尝试加载。
  • 唯一性:确保同一个类只被加载一次,避免重复加载。
代码实现
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        // 1. 检查是否已加载
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            try {
                // 2. 委派给父类加载器
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    // 3. 父类加载器为 null,委派给 Bootstrap ClassLoader
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // 4. 父类加载器无法加载,由当前类加载器尝试加载
                c = findClass(name);
            }
        }
        // 5. 解析类(可选)
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}
优势
  • 避免重复加载:确保类的唯一性。
  • 安全性:防止核心类库被篡改(如自定义 java.lang.String 类)。
  • 隔离性:不同类加载器加载的类相互隔离。

自定义类加载器

自定义类加载器通常用于以下场景:

  • 动态加载类(如插件化、热部署)。
  • 加载加密或网络资源中的类。
  • 实现模块化加载。
实现步骤
  • 继承 ClassLoader。
  • 重写 findClass() 方法。
  • 在 findClass() 中读取字节码并调用 defineClass()。
示例代码
public class MyClassLoader extends ClassLoader {
    private String classPath;

    public MyClassLoader(String classPath) {
        this.classPath = classPath;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            // 1. 读取字节码文件
            byte[] data = loadClassData(name);
            // 2. 定义类
            return defineClass(name, data, 0, data.length);
        } catch (IOException e) {
            throw new ClassNotFoundException(name, e);
        }
    }

    private byte[] loadClassData(String name) throws IOException {
        String path = classPath + File.separatorChar + name.replace('.', File.separatorChar) + ".class";
        try (InputStream is = new FileInputStream(path);
             ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            byte[] buffer = new byte[1024];
            int len;
            while ((len = is.read(buffer)) != -1) {
                baos.write(buffer, 0, len);
            }
            return baos.toByteArray();
        }
    }
}
使用示例
public class Main {
    public static void main(String[] args) throws Exception {
        MyClassLoader loader = new MyClassLoader("/path/to/classes");
        Class<?> clazz = loader.loadClass("com.example.MyClass");
        Object obj = clazz.newInstance();
        System.out.println(obj.getClass().getClassLoader()); // 输出 MyClassLoader
    }
}

反射

反射(Reflection)是Java的一种机制,允许程序在运行时检查和操作类、方法、属性等元数据。通过反射,可以在运行时动态创建对象、调用方法、访问属性,而不需要在编译时知道这些类的具体信息。

反射的核心类

Java反射的核心类位于java.lang.reflect包中,主要包括:

  • Class:表示一个类或接口。
  • Field:表示类的成员变量。
  • Method:表示类的方法。
  • Constructor:表示类的构造方法。

获取Class对象

获取目标类的Class对象

获取Class对象的方式有三种:

  • 类名.class:Class<?> clazz = MyClass.class;
    • 调用某个类的class属性来获取该类对应的Class对象。
  • 对象.getClass():Class<?> clazz = myObject.getClass();
    • 该方法是iava.lang.0biet类中的一个方法,该方法将会返回该对象所属类对应的Class对象。
  • Class.forName():Class<?> clazz = Class.forName(“com.example.MyClass”);
    • 使用Class类的forName(String clazzName)静态方法。
    • 该方法需要传入字符串参数,该字符串参数的值是某个类的全限定类名(必须添加完整包名)。
Class类介绍
获取Class对应类所包含的构造器。
方法说明
ConnstrnuctorgetConstructor(Class<?>…parameterTypes)返回此Class对象对应类的、带指定形参列表的public构造器
Constructor<?>[lgetConstructors()返回此Class对象对应类的所有public构造器
ConstructorgetDeclaredConstructor (Class<?>…parameterTypes)返回此Class对象对应类的、带指定形参列表的构造器,与构造器的访问权限无关。
Constructor<?>getDeclaredConstructors返回此Class对象对应类的所有构造器,,与构造器的访问权限无关。
获取方法
方法说明
Method getMethod (String name, Class<?>…parameterTypes)返回此Class对象对应类的、带指定形参列表的public方法。
Method[lgetMethods()返回此Class对象所表示的类的所有public方法。
Method getDeclaredMethod(String name,Clas<?>…parameterTypes)返回此Class对象对应类的、带指定形参列表的方法,与方法的访问权限无关。
MethodgetDeclaredMethods()返回此Class对象对应类的全部方法,与方法的访问权限无关。
获取成员变量
方法说明
Field getField (String name)返回此Class对象对应类的、指定名称的public成员变量
Field[]getFields()返回此Class对象对应类的所有public成员变量
Field getDeclaredField(String name)返回此Class对象对应类的、指定名称的成员变量,与成员变量的访问权限无关。
Field[lgetDeclaredFields)返回此Class对象对应类的全部成员变量,与成员变量的访问权限无关。
获取Annotation
方法说明
< A extends Annotation>A getAnnotation (Class < A > annotationClass):Annotation尝试获取该Class对象对应类上存在的、指定类型的;如果该类型的注解不存在,则返回null。
< A extends Annotation>A getDeclaredAnnotation(Class< A>annotationClass)Java 8新增的方法,该方法尝试获取直接修饰该Class对象对应类的、指定类型的Annotation;如果该类型的注解不存在,则返回null。
Annotation[] getAnnotations)返回修饰该Class对象对应类上存在的所有Annotation
Annotation[] getDeclaredAnnotations()返回直接修饰该Class对应类的所有Annotation。
判断该类是否为接口、枚举、注解类型等
方法说明
boolean isAnnotation()返回此Class对象是否表示一个注解类型(由@interface定义)。
boolean isAnnotationPresent(Class <?extends Annotation>annotationClass)判断此Class对象是否使用了Annotation修饰,
boolean isAnonymousClass()返回此Class对象是否是一个名类。
boolean isArray()返回此Class对象是否表示一个数组类。
boolean isEnum返叫此Class对象是台表示一个枚举(由enum关键字定义)。
boolean isInterface()返回此Class对象是否表示一个接口(使用interface定义)。
boolean isInstance(Object obj)判断obi是否是此Class对象的实例,该方法可以完全代替imnstanceof操作符

创建对象

通过反射创建对象有两种方式:

  • 使用无参构造方法
Class<?> clazz = Class.forName("com.example.MyClass");
Object obj = clazz.newInstance();
  • 使用带参构造方法
Class<?> clazz = Class.forName("com.example.MyClass");
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Object obj = constructor.newInstance("example", 123);

访问字段

通过反射可以访问和修改类的字段

  • 获取字段
Field field = clazz.getDeclaredField("fieldName");
  • 访问私有字段
field.setAccessible(true);
  • 读取字段值
Object value = field.get(obj);
  • 设置字段值
field.set(obj, newValue);

调用方法

  • 获取方法
Method method = clazz.getDeclaredMethod("methodName", String.class, int.class);
  • 调用方法
Object result = method.invoke(obj, "example", 123);

访问私有方法

要访问私有方法,需要先设置访问权限:

method.setAccessible(true);
import java.lang.reflect.*;

public class ReflectionExample {
    public static void main(String[] args) throws Exception {
        // 获取Class对象
        Class<?> clazz = Class.forName("com.example.MyClass");

        // 创建对象
        Object obj = clazz.newInstance();

        // 访问字段
        Field field = clazz.getDeclaredField("fieldName");
        field.setAccessible(true);
        field.set(obj, "newValue");

        // 调用方法
        Method method = clazz.getDeclaredMethod("methodName", String.class, int.class);
        method.setAccessible(true);
        Object result = method.invoke(obj, "example", 123);

        System.out.println(result);
    }
}

JDK动态代理

JDK动态代理是Java提供的一种基于接口的代理机制,通过反射和java.lang.reflect.Proxy类来实现。它可以在运行时动态创建代理对象,并将方法调用转发到指定的InvocationHandler中。

JDK动态代理的核心类

  • java.lang.reflect.Proxy:
    • 用于创建动态代理类和代理对象。
    • 提供了静态方法newProxyInstance来生成代理对象。
  • java.lang.reflect.InvocationHandler:
    • 是一个接口,定义了invoke方法。
    • 代理对象的方法调用会被转发到InvocationHandler的invoke方法中。

实现步骤

  • 定义接口:代理对象需要实现的接口。
public interface UserService {
    void addUser(String name);
    void deleteUser(String name);
}
  • 实现接口:创建接口的具体实现类。
public class UserServiceImpl implements UserService {
    @Override
    public void addUser(String name) {
        System.out.println("添加用户: " + name);
    }

    @Override
    public void deleteUser(String name) {
        System.out.println("删除用户: " + name);
    }
}
  • 实现InvocationHandler:编写代理逻辑。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class UserServiceInvocationHandler implements InvocationHandler {
    // 目标对象(被代理的对象)
    private Object target;

    public UserServiceInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 前置增强
        System.out.println("方法调用前: " + method.getName());

        // 调用目标对象的方法
        Object result = method.invoke(target, args);

        // 后置增强
        System.out.println("方法调用后: " + method.getName());
        return result;
    }
}
  • 生成代理对象:使用Proxy.newProxyInstance创建代理对象。
import java.lang.reflect.Proxy;

public class DynamicProxyExample {
    public static void main(String[] args) {
        // 创建目标对象
        UserService userService = new UserServiceImpl();

        // 创建InvocationHandler
        InvocationHandler handler = new UserServiceInvocationHandler(userService);

        // 生成代理对象
        UserService proxy = (UserService) Proxy.newProxyInstance(
                userService.getClass().getClassLoader(), // 类加载器
                userService.getClass().getInterfaces(),  // 接口列表
                handler                                  // InvocationHandler
        );

        // 通过代理对象调用方法
        proxy.addUser("Alice");
        proxy.deleteUser("Bob");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值