Cglib 如何實現(xiàn)多重代理?

由于 Cglib 本身的設(shè)計,無法實現(xiàn)在 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  

image.gif

錯誤來源代碼:

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

......省略代碼  
// 以下部分的字節(jié)碼,每次生成 Proxy 實例都會插入。JVM 驗證字節(jié)碼時則會報錯。 
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 出來的字節(jié)碼查看則更為直觀:

image
image.gif

?

image
image.gif

?

生成的字節(jié)碼中,newInstance 方法是重復(fù)的。如何查看字節(jié)碼,我在公眾號Java技術(shù)棧有發(fā)布過三種方法,關(guān)注后搜索閱讀吧。

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

如何處理?

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

筆者參考 Spring 的做法,實現(xiàn)了一個簡單的多重代理。

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

Spring 創(chuàng)建代理的代碼位于 :org.springframework.aop.framework.CglibAopProxy#getProxy

Spring AOP 攔截器類:org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor

該類的 intercept 方法是實現(xiàn)多重代理的核心。

每次調(diào)用目標方法,都會根據(jù)目標方法,和目標方法的多個攔截點生成一個調(diào)用對象。

// 生成調(diào)用對象  
CglibMethodInvocation c = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy);  

// 調(diào)用   
c.proceed();  

image.gif

然后調(diào)用父類 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);  
    }  
}  

image.gif

注意最后一行,這里就是調(diào)用攔截點的 invoke 方法,這個攔截點的具體實現(xiàn)類:AspectJAroundAdvice。

看下他的 invoke 方法:

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

image.gif

通常,我們在業(yè)務(wù)中編寫 AOP 攔截代碼時,都會接觸到這個 ProceedingJoinPoint 參數(shù),然后調(diào)用他的 proceed 方法調(diào)用目標方法。

這個 ProceedingJoinPoint 類的 proceed 方法最終會回調(diào) DynamicAdvisedInterceptor 對的 proceed 方法。直到所有的攔截點全部執(zhí)行完畢。最終執(zhí)行目標類的方法。

所以,你設(shè)置的每個被攔截的方法,如果這個方法會被攔截多次,那么就會有多個 MethodInterceptor(不是 cglib 的)實例形成調(diào)用鏈。然后通過 ProceedingJoinPoint 傳遞給你攔截使用。

鋪墊了這么多,我們自己來實現(xiàn)一個簡單的,不能像 Spring 這么復(fù)雜?。。?!

簡單實現(xiàn) Cglib 多重代理

先說一下思路:事實上很簡單,只需要再攔截器里放一個過濾器鏈即可,用戶在過濾器里攔截多重調(diào)用。這些攔截器,就像你加 @Around 注解的方法,只不過我們這里沒有 Spring 那么方便而已。

畫個 UML 圖 :

image
image.gif

?

代碼如下:

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 !";  
        }  
    }  
}  

image.gif

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();  
        }  
    }  
}  

image.gif

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);  
    }  
}  

image.gif

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;  
    }  
}  

image.gif

運行 Test main 結(jié)果:

image
image.gif

?

符合預(yù)期。


無二維碼.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容