51

使用 Cglib 实现多重代理

 5 years ago
source link: http://thinkinjava.cn/2018/10/使用-Cglib-实现多重代理/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

前言

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

Mj2AvqM.png!web

fqaQJ36.png!web

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

3quUF3b.png!web

代码如下:

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

2qeeUfV.png!web

符合预期。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK