使用 Cglib 實現多重代理

前言

因爲 Cglib 自己的設計,沒法實如今 Proxy 外面再包裝一層 Proxy(JDK Proxy 能夠),一般會報以下錯誤:java

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

錯誤來源代碼:spring

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

......省略代碼

        // 如下部分的字節碼,每次生成 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 出來的字節碼查看則更爲直觀:this

生成的字節碼中,newInstance 方法是重複的。設計

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

如何處理?

實現多重代理,有一種蹩腳的方法,例如 JDK 和 Cglib 組合使用。或者你直接使用 JDK 代理。但有時候,針對類的操做還行不通。代理

筆者參考 Spring 的作法,實現了一個簡單的多重代理。code

Spring 的場景是:一個目標方法被多個 AOP 攔截,此時就須要多重代理。orm

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

符合預期。

相關文章
相關標籤/搜索