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

使用 Cglib 实现多重代理

使用 Cglib 实现多重代理

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

使用 Cglib 实现多重代理

代码如下:

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

使用 Cglib 实现多重代理

符合预期。

原文 

http://thinkinjava.cn/2018/10/使用-Cglib-实现多重代理/

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » 使用 Cglib 实现多重代理

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址