使用 Cglib 實現多重代理

來源:互聯網
上載者:User

標籤:eof   method   proc   factor   inter   debug   針對   jdk   deb   

前言

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

符合預期。

使用 Cglib 實現多重代理

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.