JAVA基础:动态代理cglib原理(二)Fastclass 机制分析

提示:动态代理


前言

类型机制回调方式适用场景效率
JDK动态代理委托机制。代理类和目标类都实现了同样的接口。InvocationHandler持有目标类。代理类委托InvocationHandler去调用目标类原始方法反射目标类实现接口反射调用稍慢。
CGLIB动态代理继承机制。代理类继承了目标类并重写了目标方法,通过回调函数MethodInterceptor调用父类方法执行原始逻辑通过FastClass方法索引调用非final类,非final方法第一次调用因为要生成多个Class对象较]DK慢,但是调用时方法索引较反射方式快

本篇讲解通过FastClass方法索引调用中FastClass机制原理。


一、FastClass机制

JDK动态代理的拦截对象是通过反射的机制来调用被拦截方法的,反射的效率比较低,所以CGLIB采用了FastClass的机制来实现对被拦截方法的调用。

CGLIB共生成3个类,分别为代理类,代理类的FastClass,目标类的FastClass,FastClass机制就是对一个类的方法建立索引,通过索引来直接调用相应的方法。
在这里插入图片描述
FastClass并不是跟代理类一块生成的,而是在第一次执行MethodProxy invoke/invokeSuper时生成的并放在了缓存中。
在这里插入图片描述
在这里插入图片描述
下面用一个小例子来说明:

package com.kkk.demo1;

/**
 * @author Kkk
 * @date 2023/2/21
 * @Describe: 演示 FastClass机制
 */
public class Test {

    public static void main(String[] args){
        Target target = new Target();
        TargetFastClass fastClass = new TargetFastClass();
        int index = fastClass.getIndex("targetMethod1()V");
        fastClass.invoke(index, target, null);
    }
}

/**
 * 模拟代理类 如:UserService$$EnhancerByCGLIB$$1552dfb0
 */
class Target{
    public void targetMethod1(){
        System.out.println("...targetMethod1...");
    }
    public void targetMethod2(){
        System.out.println("...targetMethod2...");
    }
}

/**
 * 模拟代理类FastClass 如:UserService$$EnhancerByCGLIB$$1552dfb0$$FastClassByCGLIB$$8d34995d
 */
class TargetFastClass{

    public Object invoke(int index, Object o, Object[] arg){
        Target t = (Target) o;
        switch(index){
        case 1:
            t.targetMethod1();
            return null;
        case 2:
            t.targetMethod2();
            return null;
        }
        return null;
    }
    
    public int getIndex(String signature){
        String var = signature.toString();
        switch(var.hashCode()){
        case -1709726090:
            return 1;
        case -1709696299:
            return 2;
        }
        return -1;
    }
}

上例中,TargetFastClass是Target的Fastclass,在TargetFastClass中有两个方法getIndex和invoke。在getIndex方法中对Target的每个方法建立索引,并根据入参(方法名+方法的描述符)来返回相应的索引。Invoke根据指定的索引,以arg为入参调用对象O的方法。这样就避免了反射调用,提高了效率。


总结

拙技蒙斧正,不胜雀跃。

<think>好的,用户现在问的是静态代理和动态代理的区别,以及动态代理的两种方式的差异。我需要先理清楚这些概念,然后以通俗易懂的方式解释,尤其是考虑到用户是初学者,可能对AOP已经有了一些基础了解,但需要更深入的代理机制的解释。 首先,用户之前已经了解过AOP的核心概念和应用,现在转向代理的实现机制,这是AOP背后的重要技术。所以需要从代理的基本概念入手,对比静态和动态代理的不同。 静态代理和动态代理的主要区别在于代理类的创建时机和方式。静态代理是手动编写代理类,每个目标类都需要一个对应的代理类,这样在目标类多的时候会非常麻烦,代码冗余。而动态代理是在运行时动态生成代理类,不需要为每个目标类单独编写代理,提高了灵活性和可维护性。 接下来,动态代理的两种方式:JDK动态代理CGLIB动态代理。需要解释它们的实现原理、优缺点以及适用场景。JDK动态代理基于接口,通过反射机制生成代理类,要求目标类必须实现接口。而CGLIB通过继承目标类,使用字节码技术生成子类作为代理,因此可以代理没有接口的类。但需要注意final方法的问题,因为CGLIB无法代理final方法。 用户作为初学者,可能需要更具体的示例和对比表格来帮助理解。因此,我应该给出代码示例,说明静态代理和两种动态代理的实现方式,并通过表格总结它们的差异。同时,要指出Spring AOP是如何选择使用哪种动态代理的,这样用户能结合实际框架应用来理解。 还要注意用户可能存在的疑问点,比如为什么JDK动态代理必须基于接口,CGLIB如何生成字节码,以及性能上的差异。虽然现在CGLIB和JDK动态代理在性能上差距不大,但了解历史背景也很重要。 最后,确保回答结构清晰,分点明确,使用生活化的类比帮助理解。例如,静态代理就像手工制作每个产品的模具,而动态代理则是3D打印,按需生成。这样的类比有助于用户形成直观印象。</think>### 一、静态代理 vs 动态代理(快递代收点类比📦) #### 1. **静态代理** → **手工定制快递柜** - **特点**: - 需要**手动编写代理类** - 每个被代理类都要有对应的代理类 - 编译时确定代理关系 - **代码示例**: ```java // 接口:快递服务 interface ExpressService { void deliver(String packageId); } // 目标类:顺丰快递 class SFExpress implements ExpressService { public void deliver(String packageId) { System.out.println("顺丰正在派送包裹:" + packageId); } } // 静态代理类:快递代收点 class StaticProxy implements ExpressService { private SFExpress target = new SFExpress(); public void deliver(String packageId) { System.out.println("[代收点] 接收包裹:" + packageId); // 增强逻辑 target.deliver(packageId); // 调用原始方法 System.out.println("[代收点] 签收完成"); // 增强逻辑 } } // 使用 ExpressService proxy = new StaticProxy(); proxy.deliver("PKG-001"); ``` - **缺点**: - 每新增一个快递公司(如京东物流),就需要新建一个代理类 - 代理类和目标类**强耦合** --- #### 2. **动态代理** → **智能快递柜** - **特点**: - **运行时动态生成代理类** - 一个代理类可代理多个目标类 - 通过反射机制实现 - **核心优势**: ```text ┌─────────────┐ ┌───────────────┐ │ 客户端调用 │───▶ │ 动态代理类 │ └─────────────┘ └───────────────┘ │ 反射调用 ▼ ┌─────────────┐ │ 目标对象 │ └─────────────┘ ``` --- ### 动态代理的两种实现方式 #### 方式1:JDK动态代理(基于接口) - **实现原理**: - 通过`java.lang.reflect.Proxy`类生成代理对象 - 要求目标类**必须实现接口** - **代码示例**: ```java // 动态代理处理器 class JdkProxyHandler implements InvocationHandler { private Object target; public JdkProxyHandler(Object target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("[JDK代理] 开始处理"); Object result = method.invoke(target, args); // 反射调用目标方法 System.out.println("[JDK代理] 处理完成"); return result; } } // 使用 ExpressService target = new SFExpress(); ExpressService proxy = (ExpressService) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new JdkProxyHandler(target) ); proxy.deliver("PKG-002"); ``` - **关键特点**: - 生成的文件名格式:`$Proxy0` - 通过`ProxyGenerator`生成字节码 - **只能代理接口方法** --- #### 方式2:CGLIB动态代理(基于继承) - **实现原理**: - 通过**继承目标类**生成子类代理 - 使用ASM字节码操作框架 - 不要求目标类实现接口 - **代码示例**: ```java // 目标类(没有实现接口) class ZTOExpress { public void deliver(String packageId) { System.out.println("中通正在派送包裹:" + packageId); } } // CGLIB代理工厂 class CglibProxyFactory implements MethodInterceptor { public Object getProxy(Class<?> clazz) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(clazz); enhancer.setCallback(this); return enhancer.create(); } public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("[CGLIB代理] 开始处理"); Object result = proxy.invokeSuper(obj, args); // 调用父类方法 System.out.println("[CGLIB代理] 处理完成"); return result; } } // 使用 CglibProxyFactory factory = new CglibProxyFactory(); ZTOExpress proxy = (ZTOExpress) factory.getProxy(ZTOExpress.class); proxy.deliver("PKG-003"); ``` - **关键特点**: - 生成的文件名格式:`TargetClass$$EnhancerByCGLIB$$...` - 需要添加CGLIB依赖(Spring-core已包含) - **无法代理final类/方法** --- ### 三、核心差异对比表 | 对比维度 | JDK动态代理 | CGLIB动态代理 | |-------------------|-------------------------------------|----------------------------------------| | **实现基础** | 基于接口 | 基于继承 | | **目标类要求** | 必须实现接口 | 不需要实现接口 | | **生成方式** | 使用Proxy类生成 | 使用Enhancer生成 | | **方法调用速度** | 较慢(反射调用) | 较快(MethodProxy直接调用) | | **生成文件体积** | 较小 | 较大(生成子类包含更多信息) | | **适用场景** | 代理接口方法 | 代理普通类方法 | | **Spring默认策略** | 当Bean实现接口时使用 | 当Bean没有接口时使用 | --- ### 四、选择策略(Spring AOP的决策逻辑) ```text ┌──────────────┐ │ 目标类检查 │ └──────┬───────┘ ˅ ┌─────────────┴─────────────┐ │ 是否有实现接口? │ └─────────────┬─────────────┘ ˅ ┌──────────────┴──────────────┐ ▼ ▼ ┌─────────────┐ ┌───────────────┐ │ JDK动态代理 │ │ CGLIB动态代理 │ └─────────────┘ └───────────────┘ ``` --- ### 五、性能注意事项 1. **JDK 8+优化**:反射调用性能已大幅提升 2. **CGLIBFastClass机制**:通过索引直接调用方法,避免反射 3. **实际选择建议**: - 优先使用JDK动态代理(符合面向接口编程原则) - 需要代理非接口方法时再使用CGLIB --- ### 六、代码验证技巧 1. **查看代理类类型**: ```java System.out.println(proxy.getClass()); // 输出示例: // class com.sun.proxy.$Proxy0 → JDK代理 // class ZTOExpress$$EnhancerByCGLIB$$ → CGLIB代理 ``` 2. **保存生成的代理类**(调试用): ```bash # JDK代理 System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true"); # CGLIB代理 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "cglib_output"); ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值