反射与动态代理——Java语言动态性的核心

前言

在上一篇文章《泛型深度解析》中,我们学习了类型擦除和通配符的奥秘。但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:如何提升反射性能?

  • 缓存ClassMethodField对象(避免重复查找)
  • 设置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异常体系的底层原理。


如果你觉得本文有帮助,欢迎点赞、评论、转发!

相关推荐
LL_break1 小时前
从零上手Redis:string编码原理、常用命令与设计逻辑详解
java·数据库·redis·缓存·java-ee
Rsun045512 小时前
13、Java 策略模式从入门到实战
java·bash·策略模式
历程里程碑2 小时前
Linux 50 IP协议深度解析:从报头结构到子网划分与NAT
java·linux·开发语言·网络·c++·python·智能路由器
绿豆人2 小时前
go语言的Reflect包
java·开发语言·数据结构
liuyao_xianhui2 小时前
map和set_C++
java·开发语言·数据结构·c++·算法·宽度优先
一叶飘零_sweeeet2 小时前
Spring AI 核心架构、抽象模型与四大核心组件设计精髓
spring·spring ai
LXMXHJ2 小时前
spring+
spring
清心歌2 小时前
ArrayList 深入解析
java
Java成神之路-2 小时前
零基础入门:动态代理与 Spring AOP 核心知识点总结
spring·代理模式