前言
在上一篇文章《泛型深度解析》中,我们学习了类型擦除和通配符的奥秘。但Java还有一种在运行时 操作类型的能力------反射。
Spring是如何创建Bean的?MyBatis是如何将接口映射为SQL的?AOP代理是如何实现的?
这些框架的底层都离不开反射 和动态代理。今天,我们就来彻底揭开反射与动态代理的神秘面纱。读完本文,你将能回答:
- 反射的本质是什么?
Method.invoke()是如何实现的?- 反射为什么慢?性能开销在哪里?
- JDK动态代理和CGLIB有什么区别?
- Spring AOP是如何选择代理方式的?
下一篇,我们将进入异常处理机制------Java中容易被忽视但至关重要的环节。
一、反射基础
1.1 什么是反射?
反射 是Java语言的一种特性,允许程序在运行时获取类的完整信息(构造方法、字段、方法、注解等),并动态创建对象、调用方法、访问字段。
java
// 传统方式:编译时确定
User user = new User();
user.setName("张三");
// 反射方式:运行时动态操作
Class<?> clazz = Class.forName("com.example.User");
Object user = clazz.getDeclaredConstructor().newInstance();
Method setName = clazz.getMethod("setName", String.class);
setName.invoke(user, "张三");
1.2 获取Class对象的四种方式
| 方式 | 示例 | 说明 |
|---|---|---|
| 类字面量 | Class<User> clazz = User.class; |
最安全、最简洁 |
| 对象.getClass() | Class<?> clazz = user.getClass(); |
已有对象实例 |
| Class.forName() | Class<?> clazz = Class.forName("com.example.User"); |
动态加载,会触发初始化 |
| 类加载器 | Class<?> clazz = loader.loadClass("com.example.User"); |
不触发初始化 |
java
// 四种方式示例
public class GetClassDemo {
public static void main(String[] args) throws Exception {
// 1. 类字面量
Class<User> clazz1 = User.class;
// 2. 对象.getClass()
User user = new User();
Class<?> clazz2 = user.getClass();
// 3. Class.forName()(会触发类初始化)
Class<?> clazz3 = Class.forName("com.example.User");
// 4. 类加载器(不会触发类初始化)
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Class<?> clazz4 = loader.loadClass("com.example.User");
System.out.println(clazz1 == clazz2); // true
System.out.println(clazz1 == clazz3); // true
System.out.println(clazz1 == clazz4); // true
}
}
1.3 反射的核心API
java
// 获取类的完整信息
public class ReflectionAPIDemo {
public static void main(String[] args) throws Exception {
Class<?> clazz = User.class;
// 获取构造方法
Constructor<?>[] constructors = clazz.getConstructors(); // public构造方法
Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors(); // 所有构造方法
// 获取方法
Method[] methods = clazz.getMethods(); // public方法(包括父类)
Method[] declaredMethods = clazz.getDeclaredMethods(); // 所有方法(不包括父类)
// 获取字段
Field[] fields = clazz.getFields(); // public字段
Field[] declaredFields = clazz.getDeclaredFields(); // 所有字段
// 获取注解
Annotation[] annotations = clazz.getAnnotations();
// 动态创建对象
User user = (User) clazz.getDeclaredConstructor().newInstance();
// 动态调用方法
Method setName = clazz.getMethod("setName", String.class);
setName.invoke(user, "张三");
// 动态访问字段
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true); // 突破private
nameField.set(user, "李四");
}
}
二、Method.invoke()底层原理
2.1 源码分析
java
// Method.java(JDK 8)
public Object invoke(Object obj, Object... args) throws ... {
// 1. 权限检查
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
checkAccess(obj, clazz, modifiers);
}
}
// 2. 获取MethodAccessor
MethodAccessor ma = methodAccessor;
if (ma == null) {
ma = acquireMethodAccessor();
}
// 3. 调用MethodAccessor的invoke方法
return ma.invoke(obj, args);
}
// 获取MethodAccessor
private MethodAccessor acquireMethodAccessor() {
MethodAccessor tmp = null;
if (root != null) tmp = root.getMethodAccessor();
if (tmp != null) {
methodAccessor = tmp;
} else {
// 创建MethodAccessor
tmp = reflectionFactory.newMethodAccessor(this);
setMethodAccessor(tmp);
}
return tmp;
}
2.2 MethodAccessor的实现
MethodAccessor有两个实现:
| 实现 | 说明 | 触发条件 |
|---|---|---|
| NativeMethodAccessorImpl | 本地方法实现 | 默认,前15次调用 |
| DelegatingMethodAccessorImpl | 委托,用于切换 | 中间层 |
| GeneratedMethodAccessor | 动态生成字节码 | 调用超过15次后 |
java
// NativeMethodAccessorImpl(简化)
class NativeMethodAccessorImpl extends MethodAccessor {
private final Method method;
private DelegatingMethodAccessorImpl parent;
private int numInvocations;
public Object invoke(Object obj, Object[] args) throws ... {
// 前15次使用本地调用
if (++numInvocations > ReflectionFactory.inflationThreshold()) {
// 超过阈值,生成字节码版本的Accessor
MethodAccessorImpl acc = (MethodAccessorImpl)
new MethodAccessorGenerator().generateMethod(...);
parent.setDelegate(acc);
}
// 调用本地方法
return invoke0(method, obj, args);
}
private native Object invoke0(Method method, Object obj, Object[] args);
}
2.3 反射的性能优化:Inflation机制
┌─────────────────────────────────────────────────────────────────────┐
│ Method.invoke() 性能优化流程 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 第1-15次调用 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ NativeMethodAccessorImpl.invoke() │ │
│ │ └─ native invoke0()(本地方法,开销较大) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ 超过阈值(默认15) │
│ ↓ │
│ 第16次及以后 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ GeneratedMethodAccessor.invoke() │ │
│ │ └─ 动态生成的字节码(直接调用,无native开销) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
为什么需要这个机制?
- 前15次:避免为每个方法都生成字节码(时间和内存开销)
- 15次后:高频调用的方法生成字节码版本,提升性能
2.4 反射为什么慢?
| 开销来源 | 说明 |
|---|---|
| 类型检查 | 每次调用都要检查参数类型、返回值类型 |
| 权限检查 | 检查访问权限(private、protected等) |
| 数组包装 | 可变参数需要包装为Object数组 |
| 自动装箱 | 基本类型参数需要装箱 |
| JIT优化受限 | 反射调用难以内联和优化 |
| Native调用 | 前15次有本地方法调用开销 |
java
// 性能对比
public class ReflectionBenchmark {
public static void main(String[] args) throws Exception {
User user = new User();
Method setName = User.class.getMethod("setName", String.class);
// 直接调用
long start = System.nanoTime();
for (int i = 0; i < 1000000; i++) {
user.setName("张三");
}
long time1 = System.nanoTime() - start;
// 反射调用
start = System.nanoTime();
for (int i = 0; i < 1000000; i++) {
setName.invoke(user, "张三");
}
long time2 = System.nanoTime() - start;
System.out.println("Direct: " + time1 / 1000000 + "ms");
System.out.println("Reflection: " + time2 / 1000000 + "ms");
}
}
// 典型输出:
// Direct: 5ms
// Reflection: 80ms (约16倍差距)
三、JDK动态代理
3.1 什么是动态代理?
动态代理 是在运行时动态生成代理类的技术,无需手动编写代理类。JDK动态代理只能代理实现了接口的类。
java
// 1. 定义接口
public interface UserService {
void addUser(String name);
String getUser(int id);
}
// 2. 实现类
public class UserServiceImpl implements UserService {
@Override
public void addUser(String name) {
System.out.println("添加用户: " + name);
}
@Override
public String getUser(int id) {
return "用户" + id;
}
}
// 3. 实现InvocationHandler
public class LogInvocationHandler implements InvocationHandler {
private final Object target;
public LogInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("[日志] 调用方法: " + method.getName());
long start = System.nanoTime();
Object result = method.invoke(target, args);
long end = System.nanoTime();
System.out.println("[日志] 耗时: " + (end - start) + "ns");
return result;
}
}
// 4. 使用代理
public class ProxyDemo {
public static void main(String[] args) {
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new LogInvocationHandler(target)
);
proxy.addUser("张三");
// 输出:
// [日志] 调用方法: addUser
// 添加用户: 张三
// [日志] 耗时: 12345ns
}
}
3.2 Proxy.newProxyInstance()源码分析
java
// Proxy.java
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
// 1. 克隆接口数组
final Class<?>[] intfs = interfaces.clone();
// 2. 查找或生成代理类
Class<?> cl = getProxyClass0(loader, intfs);
// 3. 获取构造方法
final Constructor<?> cons = cl.getConstructor(constructorParams);
// 4. 创建代理实例
return cons.newInstance(new Object[]{h});
}
// 代理类生成
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
// 缓存机制
return proxyClassCache.get(loader, interfaces);
}
3.3 动态生成的代理类长什么样?
通过设置系统属性可以保存生成的代理类:
java
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
java
// 动态生成的代理类(简化版)
public final class $Proxy0 extends Proxy implements UserService {
private static Method m0, m1, m2, m3, m4;
static {
try {
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m1 = Class.forName("java.lang.Object").getMethod("equals", Object.class);
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.example.UserService").getMethod("addUser", String.class);
m4 = Class.forName("com.example.UserService").getMethod("getUser", int.class);
} catch (NoSuchMethodException e) { ... }
}
public $Proxy0(InvocationHandler h) {
super(h);
}
@Override
public void addUser(String name) {
try {
h.invoke(this, m3, new Object[]{name});
} catch (Throwable e) { ... }
}
@Override
public String getUser(int id) {
try {
return (String) h.invoke(this, m4, new Object[]{id});
} catch (Throwable e) { ... }
}
@Override
public String toString() {
try {
return (String) h.invoke(this, m2, null);
} catch (Throwable e) { ... }
}
}
四、CGLIB动态代理
4.1 什么是CGLIB?
CGLIB(Code Generation Library) 是一个第三方代码生成库,可以代理没有实现接口的类,通过生成子类的方式实现代理。
java
// 1. 目标类(没有实现接口)
public class UserService {
public void addUser(String name) {
System.out.println("添加用户: " + name);
}
public String getUser(int id) {
return "用户" + id;
}
}
// 2. 实现MethodInterceptor
public class LogMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("[日志] 调用方法: " + method.getName());
long start = System.nanoTime();
Object result = proxy.invokeSuper(obj, args);
long end = System.nanoTime();
System.out.println("[日志] 耗时: " + (end - start) + "ns");
return result;
}
}
// 3. 使用代理
public class CglibDemo {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback(new LogMethodInterceptor());
UserService proxy = (UserService) enhancer.create();
proxy.addUser("张三");
}
}
4.2 CGLIB生成的代理类结构
java
// CGLIB生成的代理类(简化版)
public class UserService$$EnhancerByCGLIB$$xxx extends UserService {
private MethodInterceptor callback;
// 拦截器设置
public void setCallback(MethodInterceptor callback) {
this.callback = callback;
}
// 代理方法
public void addUser(String name) {
MethodInterceptor interceptor = this.callback;
if (interceptor != null) {
// 调用拦截器
interceptor.intercept(this,
CglibReflector.addUserMethod,
new Object[]{name},
CglibReflector.addUserProxy);
} else {
// 直接调用父类
super.addUser(name);
}
}
}
4.3 MethodProxy的优化
CGLIB比JDK动态代理快的一个重要原因是MethodProxy避免了反射调用:
java
// MethodProxy.invokeSuper() 使用快速路径
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
// 使用FastClass机制,直接调用父类方法
return fastClassInfo.f2.invoke(fastClassInfo.i2, obj, args);
}
// FastClass通过方法索引直接调用,比反射快
五、JDK动态代理 vs CGLIB
| 对比维度 | JDK动态代理 | CGLIB |
|---|---|---|
| 代理目标 | 必须实现接口 | 普通类(不能是final类) |
| 代理方式 | 实现接口 | 生成子类 |
| 方法拦截 | InvocationHandler.invoke() |
MethodInterceptor.intercept() |
| 调用性能 | 较慢(反射) | 较快(FastClass) |
| 创建性能 | 较快 | 较慢(生成字节码) |
| final方法 | 无影响 | 无法代理 |
| 依赖 | JDK内置 | 需要CGLIB库 |
java
// 性能对比
public class ProxyBenchmark {
public static void main(String[] args) {
// JDK动态代理
UserService jdkProxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class[]{UserService.class},
(proxy, method, args1) -> method.invoke(target, args1)
);
// CGLIB代理
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args1, proxy) ->
proxy.invokeSuper(obj, args1));
UserService cglibProxy = (UserService) enhancer.create();
// 性能测试...
}
}
// 典型结果:
// JDK动态代理调用耗时: ~80ns(反射)
// CGLIB调用耗时: ~30ns(FastClass)
六、Spring AOP中的代理选择
6.1 Spring的代理策略
Spring AOP根据目标类是否实现接口,自动选择代理方式:
java
// Spring AOP代理选择逻辑(简化)
public class DefaultAopProxyFactory {
public AopProxy createAopProxy(AdvisedSupport config) {
// 1. 如果设置了optimize=true,使用CGLIB
if (config.isOptimize() || config.isProxyTargetClass()) {
return new CglibAopProxy(config);
}
// 2. 如果目标类实现了接口,使用JDK动态代理
if (hasUserSuppliedInterfaces(config)) {
return new JdkDynamicAopProxy(config);
}
// 3. 否则使用CGLIB
return new CglibAopProxy(config);
}
}
6.2 强制使用CGLIB
java
// XML配置
<aop:aspectj-autoproxy proxy-target-class="true"/>
// Java配置
@EnableAspectJAutoProxy(proxyTargetClass = true)
// Spring Boot配置
spring.aop.proxy-target-class=true
6.3 选择建议
| 场景 | 推荐 | 原因 |
|---|---|---|
| 有接口且无需代理final方法 | JDK动态代理 | JDK内置,无需额外依赖 |
| 无接口或有final方法 | CGLIB | JDK动态代理无法代理 |
| 性能敏感 | CGLIB | 调用性能更高 |
| 需要代理toString等Object方法 | CGLIB | JDK动态代理无法代理 |
七、反射在框架中的应用
7.1 Spring IoC
java
// Spring创建Bean的简化流程
public class SimpleIoC {
public <T> T createBean(Class<T> clazz) throws Exception {
// 1. 通过反射创建实例
T instance = clazz.getDeclaredConstructor().newInstance();
// 2. 通过反射注入依赖
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(Autowired.class)) {
field.setAccessible(true);
Object dependency = getBean(field.getType());
field.set(instance, dependency);
}
}
// 3. 通过反射调用初始化方法
Method initMethod = clazz.getMethod("init");
if (initMethod != null) {
initMethod.invoke(instance);
}
return instance;
}
}
7.2 MyBatis Mapper
java
// MyBatis Mapper代理生成(简化)
public class MapperProxy<T> implements InvocationHandler {
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1. 获取方法上的注解(@Select、@Insert等)
Select select = method.getAnnotation(Select.class);
if (select != null) {
String sql = select.value()[0];
// 2. 执行SQL
return sqlSession.selectOne(sql, args);
}
return method.invoke(this, args);
}
}
// 使用
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.selectById(1); // 实际调用的是代理对象
7.3 JUnit
java
// JUnit测试执行(简化)
public class JUnitRunner {
public void runTest(Class<?> testClass) throws Exception {
Object testInstance = testClass.getDeclaredConstructor().newInstance();
// 执行@Before方法
for (Method method : testClass.getDeclaredMethods()) {
if (method.isAnnotationPresent(Before.class)) {
method.invoke(testInstance);
}
}
// 执行@Test方法
for (Method method : testClass.getDeclaredMethods()) {
if (method.isAnnotationPresent(Test.class)) {
try {
method.invoke(testInstance);
} catch (Exception e) {
// 记录失败
}
}
}
// 执行@After方法
for (Method method : testClass.getDeclaredMethods()) {
if (method.isAnnotationPresent(After.class)) {
method.invoke(testInstance);
}
}
}
}
八、常见面试题
Q1:反射的本质是什么?
答 :反射的本质是JVM在运行时维护了类的完整元数据(Class对象),通过这个元数据可以获取类的构造方法、字段、方法等信息,并动态调用。Method.invoke()底层通过MethodAccessor实现,前15次使用本地方法调用,超过阈值后动态生成字节码版本以提升性能。
Q2:反射为什么慢?
答:反射慢的原因包括:类型检查、权限检查、参数装箱、数组包装、JIT优化受限等。但JDK 8+经过优化后,高频调用的反射性能已接近直接调用(通过Inflation机制动态生成字节码)。
Q3:JDK动态代理和CGLIB有什么区别?
答:
- JDK动态代理 :只能代理实现了接口的类,通过
InvocationHandler拦截方法,使用反射调用,JDK内置 - CGLIB :可以代理普通类(通过生成子类),通过
MethodInterceptor拦截方法,使用FastClass直接调用,性能更高,但不能代理final类和方法
Q4:Spring AOP如何选择代理方式?
答 :Spring AOP默认规则:如果目标类实现了接口,使用JDK动态代理;否则使用CGLIB。可以通过proxy-target-class=true强制使用CGLIB。
Q5:如何提升反射性能?
答:
- 缓存
Class、Method、Field对象(避免重复查找) - 设置
setAccessible(true)跳过权限检查 - 高频调用会触发Inflation机制,自动生成字节码版本
- 使用
MethodHandle(JDK 7+)作为替代方案
java
// 缓存Method
public class ReflectionCache {
private static final Map<String, Method> methodCache = new ConcurrentHashMap<>();
public static Method getMethod(Class<?> clazz, String name, Class<?>... paramTypes) {
String key = clazz.getName() + "." + name;
return methodCache.computeIfAbsent(key, k -> {
try {
Method m = clazz.getDeclaredMethod(name, paramTypes);
m.setAccessible(true);
return m;
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
});
}
}
九、总结
9.1 核心要点
| 概念 | 一句话解释 |
|---|---|
| 反射 | 运行时获取类信息并动态操作的能力 |
| Method.invoke() | 通过MethodAccessor实现,有Inflation优化 |
| JDK动态代理 | 代理实现了接口的类,通过InvocationHandler |
| CGLIB | 通过生成子类代理普通类,通过MethodInterceptor |
| Inflation机制 | 前15次使用本地调用,超过阈值生成字节码 |
9.2 代理方式选择
┌─────────────────────────────────────────────────────────────────────┐
│ 代理方式选择决策树 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 目标类是否实现了接口? │
│ │ │
│ ├── 是 ──→ 是否强制使用CGLIB? │
│ │ ├── 是 ──→ CGLIB │
│ │ └── 否 ──→ JDK动态代理 │
│ │ │
│ └── 否 ──→ 目标类是否为final? │
│ ├── 是 ──→ 无法代理! │
│ └── 否 ──→ CGLIB │
│ │
└─────────────────────────────────────────────────────────────────────┘
9.3 面试金句
如果面试官问你"反射和动态代理的原理",你可以这样回答:
"反射是Java运行时获取类元数据的能力,核心是
Class对象。Method.invoke()底层通过MethodAccessor实现,采用Inflation优化:前15次使用本地方法调用,超过阈值后动态生成字节码版本,提升性能。JDK动态代理只能代理实现了接口的类,运行时生成实现同一接口的代理类,方法调用委托给
InvocationHandler.invoke()。CGLIB通过生成目标类的子类实现代理,使用
MethodInterceptor拦截方法,通过FastClass机制实现直接调用,性能比JDK动态代理高,但不能代理final类和方法。Spring AOP默认根据目标类是否实现接口自动选择代理方式,可通过
proxy-target-class=true强制使用CGLIB。"
下篇预告
理解了反射和动态代理,我们掌握了Java动态性的核心。但Java中还有一个容易被忽视但至关重要的机制------异常处理。
受检异常和非受检异常有什么区别?
try-with-resources是如何实现的?异常链是什么?下一篇《异常处理机制与最佳实践》将带你深入Java异常体系的底层原理。
如果你觉得本文有帮助,欢迎点赞、评论、转发!