1. 类型信息概述
1.1 类型信息的定义
- 自然界中的每个物体都有描述信息(如性别、年龄),而计算机中对象的信息通常用字段值表示。
- 类型信息记录了类的名称、字段、方法、父类或接口等信息。
- JVM通过类型信息认识和识别对象,赋予程序动态性。
1.2 类型信息存储
1.2.1 Java文件与Class文件
.java文件:存储源代码。.class文件:由Java编译器生成,存储二进制字节码。- 每个类或接口对应一个
.class文件。 - 内部类以
外部类+$+内部类名命名。 - 并行类会生成多个
.class文件。
- 每个类或接口对应一个
1.2.2 Class文件结构
- 包括魔数、访问标志、常量池、字段表、方法表、属性表等。
- 常量池占整个类大小的60%左右。
1.2.3 JVM加载过程
-
编译阶段:
- 翻译类文件(文本 -> 二进制)。
- 动态添加一个公有的静态常量属性
.class,其为Class类实例。
-
加载机制:
- 预先加载:启动时加载所有类。
- 按需加载:使用时动态加载。
-
类加载顺序:
- 加载基础类(Bootstrap Loader)。
- 加载包含
main()函数的类(AppClassLoader)。 - 按需加载其他类。
-
JVM三种预定义类型类加载器:
- 引导类(Bootstrap)加载器:用原生代码实现,负责加载
<Java_Runtime_Home>/lib下的类库。 - 扩展类(Extension)加载器:由
Sun的ExtClassLoader实现,负责加载<Java_Runtime_Home>/lib/ext或者由系统变量java.ext.dir指定位置中的类库。 - 系统类(System)加载器:由
Sun的AppClassLoader实现,负责加载系统类路径(CLASSPATH)中指定的类库。
- 引导类(Bootstrap)加载器:用原生代码实现,负责加载
-
父类和子类的加载顺序:当一个类具有继承关系时,加载顺序是从顶级类开始,依次加载直至加载到这个类本身。
-
引用类的加载:
- 未初始化的静态引用:不会触发类的加载。
public class Course { static { System.out.println("Course prepare!"); } } public class Teacher { static { System.out.println("Teacher prepare!"); } public static Course course; } - 初始化的静态引用:会触发类的加载。
public static Course course = new Course();
- 未初始化的静态引用:不会触发类的加载。
2. 核心类
2.1 Class类
Class对象是类型信息的核心,用于描述类的整体信息。- 获取方式:
.class属性:直接通过类名获取。getClass()方法:通过对象实例获取。Class.forName(String className)方法:通过类的全限定名获取。
2.2 Constructor类
- 描述构造方法,可通过以下方法获取:
getConstructor(Class... parameterTypes):获取指定参数类型的公有构造方法。getConstructors():获取所有公有构造方法。getDeclaredConstructor(Class... parameterTypes):获取任意访问权限的构造方法。getDeclaredConstructors():获取所有构造方法。
2.3 Method类
- 描述方法,可通过以下方法获取:
-
getMethod(String name, Class... parameterTypes):获取指定名称和参数类型的公有方法。 -
getMethods():获取所有公有方法。不仅包括本身类定义的方法描述对象,还包含继承自父类或接口的方法描述对。象 -
getDeclaredMethod(String name, Class... parameterTypes):获取任意访问权限的方法。 -
getDeclaredMethods():获取所有方法。
-
2.4 Field类
- 描述字段,可通过以下方法获取:
getField(String name):获取指定公有字段。getFields():获取所有公有字段。getDeclaredField(String name):获取任意访问权限的字段。getDeclaredFields():获取所有字段。
3. 类型信息应用——运行时类型识别
3.1 RTTI简介
- 运行时类型识别(RTTI):在程序运行时动态识别对象和类的信息。
运行时类型识别(RTTI)是Java语言中的一个重要概念,它允许程序在运行时检查对象和类的信息。这为编写更加灵活和动态的代码提供了可能。
3.2 实现方式
RTTI主要包括以下几种方式:
- 使用
instanceof关键字。 - 利用
Class类提供的方法,比如isInstance()、getClass()等。
instanceof关键字
instanceof用于判断一个对象是否是指定类型的实例或其子类的实例。如果表达式返回true,则表示该对象可以安全地转换为指定类型;如果返回false,则不能进行这种转换。
示例:
if (x instanceof Dog) {
((Dog)x).bark();
}
在这个例子中,首先使用instanceof检查变量x是否是Dog类的一个实例。如果是,则将x强制转换为Dog类型,并调用bark()方法。
Class.isInstance()方法
Class.isInstance()方法提供了一种更灵活的方式来检查对象是否属于某个特定类型。此方法实际上是对instanceof运算符的动态等价形式。它的参数是要检查的对象,如果对象是指定类或其子类的一个实例,则返回true。
示例解释:
boolean isDog = Dog.class.isInstance(x);
这段代码的作用与instanceof关键字类似,用来判断对象x是否为Dog类的实例或者其子类的实例。如果x确实是Dog类或其派生类的一个实例,那么isDog变量将会被设置为true;否则,isDog会被设置为false。
这种机制可以实现在不知道具体类名的情况下比较对象类型,增加了代码的灵活性和可重用性。对于需要处理多种类型对象的情况非常有用,特别是在设计模式如工厂模式、策略模式等中经常会被用到。此外,Class.isInstance()还可以用于反射操作中,以动态地确定对象的类型。
4. 类型信息应用——反射
4.1 反射机制
- 在运行时动态获取类的所有属性和方法,并调用对象的成员。
- 主要步骤:
- 获取操作类的
Class对象。 - 调用
getDeclaredMethods()等方法获取类定义信息。 - 使用反射API操作这些信息。
- 获取操作类的
4.2 反射的主要功能
- 加载类:
- 使用
Class.forName(String className)加载类。
- 使用
- 实例化类:
Class.newInstance()(已废弃)。clazz.getDeclaredConstructor().newInstance()。
- 执行方法:
Method.invoke(Object obj, Object... args)。
- 修改属性:
Field.set(Object obj, Object value)。
- 修改访问权限:
AccessibleObject.setAccessible(boolean flag)。
5. 代理
5.1 什么是代理模式?
代理模式(Proxy Pattern)
定义:代理模式是一种结构型设计模式,它为其他对象提供一种代理以控制对这个对象的访问。
代理通常有以下几个用途:
- 延迟初始化(Lazy Initialization):在需要时才创建的对象。
- 存取控制(Access Control):限制对原始对象的访问权限。
- 日志记录(Logging):在方法调用前后记录日志。
- 缓存(Caching):为结果添加缓存功能以提高性能。
5.2 静态代理
静态代理
静态代理意味着代理类是在编译时就已经确定好的,并且每个业务类都需要一个对应的代理类。这种方式虽然简单直接,但扩展性和维护性较差。
组成部分:
- 接口:定义了代理和目标对象共同实现的方法。
- 目标对象:实现了接口并提供了实际的业务逻辑。
- 代理对象:同样实现了接口,但在调用接口方法时,可以添加额外的操作。
示例:
public interface Speakable {
void speak(String message);
}
public class Person implements Speakable {
@Override
public void speak(String message) {
System.out.println("Speak: " + message);
}
}
public class PersonProxy implements Speakable {
private final Person person;
public PersonProxy(Person person) {
this.person = person;
}
@Override
public void speak(Stringmessage) {
System.out.println("Before speaking");
person.speak(message);
System.out.println("Time: " + System.currentTimeMillis());
}
}
5.3 动态代理
5.3.1 动态代理概述
动态代理:在程序运行期间,JVM 动态生成一个代理类来代理目标对象,实现对方法调用的拦截与增强。
它常用于实现 日志记录、事务管理、权限控制等横切关注点,而无需修改原始业务逻辑代码。
5.3.2 Java 动态代理的核心组件
| 组件名称 | 说明 |
|---|---|
Proxy | 核心类,负责创建所有代理类。生成的代理类是它的子类,并实现了目标接口。 |
InvocationHandler | 调用处理器接口,定义了 invoke() 方法,用于处理代理对象的方法调用。 |
ProxyHandler(如 LogHandler) | 实现 InvocationHandler 接口,封装了增强逻辑(如日志、时间统计)。 |
Proxied(委托类) | 真实的目标对象,被代理的对象,通常通过 ProxyHandler 持有其引用。 |
5.3.3 动态代理使用示例
5.3.3.1. 定义接口(DataService)
public interface DataService {
String getDataById(String id);
}
5.3.3.2 实现类(委托类 - DataServiceImpl)
import java.util.HashMap;
import java.util.Map;
public class DataServiceImpl implements DataService {
private Map<String, String> dataMap = new HashMap<>();
public DataServiceImpl() {
// 初始化一些数据
dataMap.put("1", "Data for ID 1");
dataMap.put("2", "Data for ID 2");
dataMap.put("3", "Data for ID 3");
}
@Override
public String getDataById(String id) {
System.out.println("从数据源获取数据...");
return dataMap.get(id);
}
}
5.3.3.3 调用处理器(CacheInvocationHandler)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class CacheInvocationHandler implements InvocationHandler {
private final Object target;
private final Map<String, Object> cache = new HashMap<>();
public CacheInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String key = method.getName() + Arrays.toString(args);
// 检查缓存中是否存在结果
if (cache.containsKey(key)) {
System.out.println("从缓存中读取数据...");
return cache.get(key);
} else {
// 如果缓存中不存在,则调用目标对象的方法
Object result = method.invoke(target, args);
// 将结果放入缓存
cache.put(key, result);
return result;
}
}
}
5.3.3.4 创建代理并调用(主程序)
import java.lang.reflect.Proxy;
public class Bootstrap {
public static void main(String[] args) {
// 创建目标对象
DataService dataService = new DataServiceImpl();
// 创建代理对象
DataService proxy = (DataService) Proxy.newProxyInstance(
DataService.class.getClassLoader(),
new Class[]{DataService.class},
new CacheInvocationHandler(dataService)
);
// 使用代理对象调用方法
System.out.println(proxy.getDataById("1"));
System.out.println(proxy.getDataById("1")); // 第二次调用,应该从缓存中获取
System.out.println(proxy.getDataById("2"));
System.out.println(proxy.getDataById("2")); // 同样,第二次调用应该从缓存中获取
}
}
输出示例
当你运行上述代码时,输出可能如下:
从数据源获取数据...
Data for ID 1
从缓存中读取数据...
Data for ID 1
从数据源获取数据...
Data for ID 2
从缓存中读取数据...
Data for ID 2
5.3.4 关键 API 和方法解析
✅ InvocationHandler 接口
-
作用:
- 封装通用逻辑(如日志、权限检查等)。
- 提供统一入口处理代理对象的方法调用。
-
方法签名:
Object invoke(Object proxy, Method method, Object[] args)
| 参数 | 含义 |
|---|---|
proxy | 生成的代理对象 |
method | 被调用的方法对象 |
args | 方法参数数组 |
✅ Proxy.newProxyInstance() 方法
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
| 参数 | 含义 |
|---|---|
loader | 类加载器,用于加载生成的代理类 |
interfaces | 代理类需要实现的接口列表 |
h | 调用处理器,代理方法执行时会回调它的 invoke 方法 |
5.3.5 代理类的构造过程(底层原理)
- 参数校验:确保 InvocationHandler 不为空;
- 访问权限判断:如果是非公共类需特殊处理;
- 获取或生成代理类:
- 先查缓存是否存在;
- 若无,则动态生成字节码并加载进 JVM;
- 创建代理实例:
- 调用代理类的构造函数;
- 构造出最终的代理对象;
⚠️ 注意:生成的代理类继承自
Proxy,并实现你传入的接口。
5.3.6 注意事项
| 项目 | 说明 |
|---|---|
| 必须实现接口 | JDK 动态代理只能代理实现了接口的类 |
| 性能开销 | 使用反射机制,性能略低 |
| 适用于 AOP 场景 | 如日志记录、事务控制、安全验证等 |
| 不能代理 final 类/方法 | 因为 CGLIB 基于继承,final 类无法被继承 |
5.3.7 JDK 动态代理 vs CGLIB
| 对比项 | JDK 动态代理 | CGLIB |
|---|---|---|
| 是否需要接口 | ✅ 是 | ❌ 否 |
| 实现代理方式 | 接口实现 | 子类继承 |
| 支持 final 类/方法 | ❌ 不支持 | ❌ 不支持(无法继承) |
| 性能 | 相对较低(反射) | 较高(直接调用) |
| 应用场景 | Spring AOP 默认使用 JDK 代理(基于接口) | 无接口时使用 CGLIB,Spring Boot 自动切换 |
6 动态代理loader参数
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 是 JDK 动态代理的核心方法,用于动态生成代理对象。其中,loader 参数的作用非常重要。
1. ClassLoader loader 参数的作用
loader 指定了用于加载代理类的类加载器(ClassLoader)。它决定了代理类会被哪个类加载器加载到 JVM 中。
什么是类加载器?
在 Java 中,类加载器(ClassLoader)负责将 .class 文件加载到 JVM 中,并将其转换为 Class 对象。Java 的类加载机制是分层的,常见的类加载器包括:
- Bootstrap ClassLoader:加载核心类库(如
java.lang.*)。 - Extension ClassLoader:加载扩展类库。
- Application ClassLoader:加载应用程序类路径下的类。
- 自定义类加载器:用户可以定义自己的类加载器。
每个类加载器都有其特定的职责范围,并且它们之间遵循“双亲委派模型”(Parent Delegation Model),即优先委托父类加载器加载类。
2. 为什么需要指定 ClassLoader?
在动态代理中,Proxy.newProxyInstance() 方法会动态生成一个代理类。这个代理类需要被加载到 JVM 中才能被使用。因此,必须明确指定一个类加载器来完成这一任务。
具体原因
-
动态生成的代理类需要被加载:
- 动态代理会在运行时生成一个新的代理类(例如
$Proxy0),这个类实现了目标类的所有接口。 - 为了使这个代理类能够被 JVM 使用,必须通过类加载器将其加载到内存中。
- 动态代理会在运行时生成一个新的代理类(例如
-
类加载器决定了代理类的可见性:
- 在 Java 中,类的可见性和作用域是由类加载器决定的。如果代理类和目标类不在同一个类加载器下,可能会导致类加载冲突或无法访问的问题。
- 因此,通常会使用目标类的类加载器来加载代理类,以确保代理类和目标类在同一个类加载器范围内。
-
支持模块化和隔离:
- 在复杂的应用程序中(如 Web 容器、插件系统),不同模块可能使用不同的类加载器。通过指定类加载器,可以确保代理类被正确加载到目标模块中。
3. loader 参数的实际意义
示例代码
Speakable proxy = (Speakable) Proxy.newProxyInstance(
person.getClass().getClassLoader(), // 指定类加载器
person.getClass().getInterfaces(), // 目标类实现的接口
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method: " + method.getName());
Object result = method.invoke(person, args);
System.out.println("After method: " + method.getName());
return result;
}
}
);
在这个例子中,person.getClass().getClassLoader() 获取了目标类 Person 的类加载器,并将其传递给 Proxy.newProxyInstance() 方法。
作用解释
-
person.getClass().getClassLoader():- 获取目标类
Person的类加载器。 - 确保代理类和目标类由同一个类加载器加载,避免类加载冲突。
- 获取目标类
-
为什么要用目标类的类加载器?
- 如果代理类和目标类不在同一个类加载器范围内,可能会导致以下问题:
- 类型不匹配:代理类和目标类被视为不同的类型。
- 类加载冲突:目标类的接口可能无法被代理类加载器找到。
- 因此,使用目标类的类加载器是最安全的选择。
- 如果代理类和目标类不在同一个类加载器范围内,可能会导致以下问题:
4. 如果不指定类加载器会发生什么?
如果不显式指定类加载器,或者指定错误的类加载器,可能会导致:
-
类加载失败:
- 如果代理类的类加载器无法找到目标类实现的接口,就会抛出
ClassNotFoundException或NoClassDefFoundError。
- 如果代理类的类加载器无法找到目标类实现的接口,就会抛出
-
类型不匹配:
- 即使代理类成功加载,但如果代理类和目标类由不同的类加载器加载,可能会导致类型不匹配问题。例如:
这里的强制类型转换可能会失败,抛出Speakable proxy = (Speakable) Proxy.newProxyInstance(...);ClassCastException。
- 即使代理类成功加载,但如果代理类和目标类由不同的类加载器加载,可能会导致类型不匹配问题。例如:
-
模块隔离问题:
- 在复杂的模块化系统中,不同模块可能使用不同的类加载器。如果代理类被加载到错误的模块中,可能会导致类不可见或功能异常。
1267

被折叠的 条评论
为什么被折叠?



