zoukankan      html  css  js  c++  java
  • Cglib 如何实现多重代理?

    由于 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 出来的字节码查看则更为直观:

    生成的字节码中,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 图 :

    代码如下:

    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 结果:

    符合预期。

    作者:莫那·鲁道
    来源:https://www.cnblogs.com/stateis0/p/9744123.html
    近期热文推荐:

    1.Java 15 正式发布, 14 个新特性,刷新你的认知!!

    2.终于靠开源项目弄到 IntelliJ IDEA 激活码了,真香!

    3.我用 Java 8 写了一段逻辑,同事直呼看不懂,你试试看。。

    4.吊打 Tomcat ,Undertow 性能很炸!!

    5.《Java开发手册(嵩山版)》最新发布,速速下载!

    觉得不错,别忘了随手点赞+转发哦!

  • 相关阅读:
    Fiddler的使用
    vue后台管理系统搭建
    有效的山脉数组
    从中序与后序遍历序列构造二叉树
    从前序与中序遍历序列构造二叉树
    最大二叉树
    填充每个节点的下一个右侧节点指针
    二叉树展开为链表
    翻转二叉树
    Java判断字符串是否为数字
  • 原文地址:https://www.cnblogs.com/javastack/p/13929870.html
Copyright © 2011-2022 走看看