一次動(dòng)態(tài)代理的填坑之旅

占小狼,轉(zhuǎn)載請(qǐng)注明原創(chuàng)出處,謝謝!

背景

想在現(xiàn)有的接口加上熔斷降級(jí)或者限流的功能,比較好的方式是通過(guò)注解的方式,并基于動(dòng)態(tài)代理進(jìn)行實(shí)現(xiàn),下面代碼是Rhino的實(shí)現(xiàn)

@Rhino
public class ServiceImpl {

    @Degrade(rhinoKey = "syncMethod-0",  fallBackMethod = "fallbackMethod")
    public void method() throws Exception {
        int i = 1 / 0;
    }

    private String fallbackMethod() throws Exception {
        return "fallback";
    }
}

通過(guò)在方法添加@Degrade注解,很方便的賦予了method方法熔斷降級(jí)功能,在該方法的失敗率達(dá)到閾值時(shí),就自動(dòng)熔斷,并調(diào)用降級(jí)方法。

這里的動(dòng)態(tài)代理并沒(méi)有使用Spring的AOP,而是自己實(shí)現(xiàn)了BeanPostProcessorBeanFactoryPostProcessor接口,另外也實(shí)現(xiàn)了PriorityOrdered接口。

在生成動(dòng)態(tài)代理對(duì)象的時(shí)候,根據(jù)類是否有實(shí)現(xiàn)接口,選擇使用JDK的Proxy還是使用Cglib。

/**
 * @param clazz
 * @param origin
 * @return
 */
private static Object createProxyService(Class clazz, Object origin) {
    Object proxy;
    if (clazz.getInterfaces().length > 0) {
        proxy = Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), new RhinoInvocationHandler(origin, clazz));
    } else {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(new RhinoInvocationHandler(origin, clazz));
        proxy = enhancer.create();
    }
    return proxy;
}

這樣的實(shí)現(xiàn),在99%的情況下,是沒(méi)有問(wèn)題的,直到有一天,有兩個(gè)業(yè)務(wù)同時(shí)反饋了一個(gè)問(wèn)題,使用了熔斷降級(jí)的注解之后,發(fā)現(xiàn)自身的Spring AOP注解失效了或者是直接啟動(dòng)異常,WTF,這個(gè)問(wèn)題一直都沒(méi)考慮過(guò)好嗎...

為什么會(huì)這樣?Spring AOP到底干了什么,或者是自己的注解到底有什么問(wèn)題?

復(fù)現(xiàn)問(wèn)題

為了快速定位問(wèn)題,并解決問(wèn)題,最好的辦法就是復(fù)現(xiàn)該問(wèn)題,寫了一個(gè)簡(jiǎn)單的Spring AOP的例子。

@Component  //加入到IoC容器
@Aspect  //指定當(dāng)前類為切面類
public class Aop {

    @Pointcut("execution(* com.dianping.rhino.aop.*.*(..))")
    public void pointCut(){
    }

    @Before("pointCut()")
    public void begin(){
        System.out.println("begin");
    }

    @After("pointCut()")
    public void close(){
        System.out.println("close");
    }

    @Around(value = "@annotation(MethodLog)")
    public void around() {
        System.out.println(" MethodLog ");
    }
}

這里簡(jiǎn)短的解釋下Spring AOP各個(gè)注解的作用
@Aspect
切面,標(biāo)識(shí)該類是一個(gè)切面類
@Pointcut
切入點(diǎn),用來(lái)標(biāo)識(shí)哪些方法是需要被添加切面的
@Before
在切入點(diǎn),執(zhí)行方法之前進(jìn)行增強(qiáng)
@After
在切入點(diǎn),執(zhí)行方法之后進(jìn)行增強(qiáng)
@Around
在該例子中,只有添加了@MethodLog注解的方法才會(huì)被增強(qiáng)

一切準(zhǔn)備就緒,開啟DEBUG之旅,Spring的內(nèi)部邏輯有點(diǎn)復(fù)雜,整個(gè)過(guò)程需要一點(diǎn)耐心。

通過(guò)Debug發(fā)現(xiàn),Rhino的代理對(duì)象Processor排在Spring AOP的Processor,意味著Rhino生成的代理對(duì)象,會(huì)傳給Spring AOP的Processor再做一層代理,在Spring AOP生成代理對(duì)象的內(nèi)部邏輯中,有這么一段判斷邏輯。

在createProxy方法中,會(huì)根據(jù)傳入的beanClass,即上一個(gè)Processor處理過(guò)的對(duì)象,判斷是否有實(shí)現(xiàn)接口。

回到Rhino的實(shí)現(xiàn),因?yàn)镾erviceImpl類沒(méi)有實(shí)現(xiàn)接口,所以內(nèi)部會(huì)采用CGLIB的方式創(chuàng)建代理對(duì)象,我們來(lái)看下這個(gè)對(duì)象的接口。

好家伙,默默的給加了一個(gè)Factory接口,這樣在Spring AOP的處理中就當(dāng)做有接口的情況進(jìn)行實(shí)現(xiàn)了。這里最大的問(wèn)題是,最終生成的代理對(duì)象是Factory類型的對(duì)象,在賦值給ServiceImpl變量時(shí)就會(huì)拋異常了。

解決問(wèn)題

終于找到了問(wèn)題的所在,那么改如何解決呢?
無(wú)法修改Spring AOP的邏輯,但是可以控制Rhino的邏輯,只需要把Rhino的Processor移到Spring AOP的Processor之后,這樣就可以在Rhino的Proccessor中處理經(jīng)過(guò)Spring AOP的代理過(guò)的beanClass對(duì)象了,有效的避免這個(gè)問(wèn)題。

如何有效調(diào)整Processor的處理順序?
通過(guò)分析發(fā)現(xiàn)Spring AOP的內(nèi)部實(shí)現(xiàn)基于Ordered接口,而Rhino的實(shí)現(xiàn)是基于PriorityOrdered接口,而且處理器在初始化完成后會(huì)進(jìn)行排序,實(shí)現(xiàn)PriorityOrdered接口的會(huì)放到前面,相同類型的再根據(jù)設(shè)置的order進(jìn)行排序。很顯然,Rhino的Processor被放在前面,找到了問(wèn)題,解決方法也很簡(jiǎn)單,Rhino也換成Order接口,并且order設(shè)置成LOWEST_PRECEDENCE,即排在最后面。

結(jié)論

經(jīng)過(guò)上面的調(diào)整之前,這個(gè)問(wèn)題確實(shí)被有效的解決了,以前遇到Spring的異常,都是一臉懵逼。所以,遇到類似的問(wèn)題,最好通過(guò)DEBUG源碼去發(fā)現(xiàn)問(wèn)題,并解決問(wèn)題,這樣可以有效的防止后續(xù)的繼續(xù)挖坑。

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

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