Cglib 如何实现多重代理?

简介: 由于 Cglib 本身的设计,无法实现在 Proxy 外面再包装一层 Proxy(JDK Proxy 可以),通常会报如下错误:

由于 Cglib 本身的设计,无法实现在 Proxy 外面再包装一层 Proxy(JDK Proxy 可以),通常会报如下错误:

Caused by: java.lang.ClassFormatError: Duplicate method name "newInstance" with signature "..........  
at java.lang.ClassLoader.defineClass1(Native Method)  
at java.lang.ClassLoader.defineClass(ClassLoader.java:763)  
... 10 more  

错误来源代码:

net.sf.cglib.proxy.Enhancer#generateClass(ClassVisitor v)

......省略代码  
// 以下部分的字节码,每次生成 Proxy 实例都会插入。JVM 验证字节码时则会报错。 
if (useFactory || currentData != null) {  
    int[] keys = getCallbackKeys();  
    emitNewInstanceCallbacks(e);  
    emitNewInstanceCallback(e);  
    emitNewInstanceMultiarg(e, constructorInfo);  
    emitGetCallback(e, keys);  
    emitSetCallback(e, keys);  
    emitGetCallbacks(e);  
    emitSetCallbacks(e);  
}  

通过 dump 出来的字节码查看则更为直观:


image.png



生成的字节码中,newInstance 方法是重复的。


dump 方法:System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./");


如何处理?


实现多重代理,有一种蹩脚的方法,例如 JDK 和 Cglib 组合使用。或者你直接使用 JDK 代理。但有时候,针对类的操作还行不通。


笔者参考 Spring 的做法,实现了一个简单的多重代理。


Spring 的场景是:一个目标方法被多个 AOP 拦截,此时就需要多重代理。


Spring 创建代理的代码位于 :org.springframework.aop.framework.CglibAopProxy#getProxy


Spring AOP 拦截器类:org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor


该类的 intercept 方法是实现多重代理的核心。


每次调用目标方法,都会根据目标方法,和目标方法的多个拦截点生成一个调用对象。

// 生成调用对象  
CglibMethodInvocation c = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy);  
// 调用   
c.proceed();  

然后调用父类 proceed 方法,其实就是一个过滤器模式:

public Object proceed() throws Throwable {  
    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {  
        return invokeJoinpoint();  
    }  
    Object interceptorOrInterceptionAdvice =  
            this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);  
    if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {  
        InterceptorAndDynamicMethodMatcher dm =  
                (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;  
        if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {  
            return dm.interceptor.invoke(this);  
        }  
        else {  
            // Skip this interceptor and invoke the next in the chain. 递归.  
            return proceed();  
        }  
    }  
    else {  
        return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);  
    }  
}  

注意最后一行,这里就是调用拦截点的 invoke 方法,这个拦截点的具体实现类:AspectJAroundAdvice。

看下他的 invoke 方法:

public Object invoke(MethodInvocation mi) throws Throwable {  
    ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;  
   // AOP 里熟悉的 ProceedingJoinPoint 参数!!!!  
    ProceedingJoinPoint pjp = lazyGetProceedingJoinPoint(pmi);  
    JoinPointMatch jpm = getJoinPointMatch(pmi);  
    return invokeAdviceMethod(pjp, jpm, null, null);  
}  

通常,我们在业务中编写 AOP 拦截代码时,都会接触到这个 ProceedingJoinPoint 参数,然后调用他的 proceed 方法调用目标方法。


这个 ProceedingJoinPoint 类的 proceed 方法最终会回调 DynamicAdvisedInterceptor 对的 proceed 方法。直到所有的拦截点全部执行完毕。最终执行目标类的方法。


所以,你设置的每个被拦截的方法,如果这个方法会被拦截多次,那么就会有多个 MethodInterceptor(不是 cglib 的)实例形成调用链。然后通过 ProceedingJoinPoint 传递给你拦截使用。


铺垫了这么多,我们自己来实现一个简单的,不能像 Spring 这么复杂!!!!


简单实现 Cglib 多重代理


先说一下思路:事实上很简单,只需要再拦截器里放一个过滤器链即可,用户在过滤器里拦截多重调用。这些拦截器,就像你加 @Around 注解的方法,只不过我们这里没有 Spring 那么方便而已。


画个 UML 图 :


image.png


代码如下:


Test.java & SayHello.java

public class Test {  
    public static void main(String[] args) {  
        Object proxy = ProxyFactory.create().getProxy(new SayHello());  
        proxy.toString();  
    }  
    static class SayHello {  
        @Override  
        public String toString() {  
            return "hello cglib !";  
        }  
    }  
}  

ProxyFactory.java & Interceptor.java

public class ProxyFactory {  
    private ProxyFactory() {}  
    public static ProxyFactory create() {  
        return new ProxyFactory();  
    }  
    public Object getProxy(Object origin) {  
        final Enhancer en = new Enhancer();  
        en.setSuperclass(origin.getClass());  
        List<Chain.Point> list = new ArrayList<>();  
        list.add(new Point1());  
        list.add(new Point2());  
        en.setCallback(new Interceptor(new Chain(list, origin)));  
        return en.create();  
    }  
    private class Interceptor  
        implements MethodInterceptor {  
        Chain chain;  
        public Interceptor(Chain chain) {  
            this.chain = chain;  
        }  
        @Override  
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)  
            throws Throwable {  
            return chain.proceed();  
        }  
    }  
}  

Chain.java & Point.java

public class Chain {  
    private List<Point> list;  
    private int index = -1;  
    private Object target;  
    public Chain(List<Point> list, Object target) {  
        this.list = list;  
        this.target = target;  
    }  
    public Object proceed() {  
        Object result;  
        if (++index == list.size()) {  
            result = (target.toString());  
            System.err.println("Target Method invoke result : " + result);  
        } else {  
            Point point = list.get(index);  
            result = point.proceed(this);  
        }  
        return result;  
    }  
    interface Point {  
        Object proceed(Chain chain);  
    }  
}  

Point1.java & Point2.java

public class Point1 implements Chain.Point {  
    @Override  
    public Object proceed(Chain chain) {  
        System.out.println("point 1 before");  
        Sleep.sleep(20);  
        Object result = chain.proceed();  
        Sleep.sleep(20);  
        System.out.println("point 1 after");  
        return result;  
    }  
}  
public class Point2 implements Chain.Point {  
    @Override  
    public Object proceed(Chain chain) {  
        System.out.println("point 2 before");  
        Sleep.sleep(20);  
        Object result = chain.proceed();  
        Sleep.sleep(20);  
        System.out.println("point 2 after");  
        return result;  
    }  
}  

运行 Test main 结果:

image.png

相关文章
|
敏捷开发 测试技术 数据库
PowerMock(一):PowerMock的基本使用
您好,我是码农飞哥,感谢您阅读本文!本文主要介绍PowerMock的基本使用。
2224 0
|
23天前
|
Linux 程序员 数据格式
【2026最新】Notepad++下载、安装和使用一篇搞定(附中文版安装包)
Notepad++ 是一款免费开源、轻量高效的 Windows 文本编辑器,支持 C/Python/HTML 等 80+ 语言语法高亮、代码折叠、正则替换、编码转换及插件扩展,专为程序员与文本处理用户打造,完美替代系统记事本。(239字)
|
8天前
|
人工智能 缓存 安全
Claude Code 封号真实原因曝光,这次彻底不装了,直接针对国内开发者的账号下手?
Claude Code 封号潮背后:逆向扒出客户端隐写区域标记,Anthropic 政策收紧叠加 DeepSeek 7 月涨价,国产替代更紧迫。
|
13天前
|
人工智能 JSON 自然语言处理
让教学更智慧:用阿里云百炼工作流,自动生成中小学教材内容#小有可为#有温度的AI
通过可视化工作流编排,将大模型推理能力转化为标准化的教学内容生成引擎。教师只需输入教材标题和适用学段,即可自动获得结构完整、符合课程标准的章节内容,大幅降低备课门槛,助力教育资源均衡化。
500 127
|
17天前
|
存储 人工智能 监控
QoderWork完全指南:从入门到精通,把“AI实习生”变成你的全能工作搭档
阿里云2026年推出的桌面端AI工作助手QoderWork,不止聊天,更可动手干活:本地运行、安全可控,支持文件整理、数据分析、PPT生成、网页开发等;内置专家套件、多Agent协作与自定义Skills,让AI真正成为你身边的“AI实习生”。
|
9天前
|
人工智能 安全 程序员
终于,Claude Code 封号的原因被曝光了!竟然针对中国用户,植入隐形代码?!
通俗易懂地揭秘 Claude Code 封号的手段,分享一些自己对 AI 编程困境的思考,Codex、Cursor、DeepSeek、智谱 GLM、甚至是豆包,都有所行动了
477 1
|
9天前
|
人工智能 安全 Cloud Native
Higress 新发布:AI Gateway 能力增强,Gateway API 及其推理扩展持续打磨
增强 AI 网关能力,持续打磨 Gateway API 及其推理扩展。
397 125
|
7天前
|
人工智能 编解码 物联网
2026 最新Stable Diffusion 本地部署教程 下载安装使用详细图解(含官网安装包)
Stable Diffusion(SD)是2022年发布的开源文生图模型,由Stability AI等联合开发。支持文生图、图生图、局部重绘等,依托VAE降低算力需求,可在消费级显卡运行。本文提供秋葉aaaki制作的Windows整合包(含图形界面与插件),开箱即用,零配置启动。