逐行閱讀Spring5.X源碼(九)spring利用CGLIB實現(xiàn)動態(tài)代理原理剖析

前面花了大量篇幅講解spring如何完成掃描注冊的遍膜。注意碗硬,此時的注冊是將業(yè)務類class所對應的BeanDefinition,要想使用業(yè)務類的功能瓢颅,必須先實例化恩尾。spring肯定不會直接new一個業(yè)務對象來管理,spring是通過動態(tài)代理技術完成業(yè)務類的實例化挽懦。

什么是CGLIB

CGLIB(Code Generator Library)是一個強大的翰意、高性能的代碼生成庫。其被廣泛應用于AOP框架(Spring巾兆、dynaop)中猎物,用以提供方法攔截操作虎囚。CGLIB代理主要通過對字節(jié)碼的操作角塑,為對象引入間接級別,以控制對象的訪問淘讥。我們知道Java中有一個動態(tài)代理也是做這個事情的圃伶,那我們?yōu)槭裁床恢苯邮褂肑ava動態(tài)代理,而要使用CGLIB呢?答案是CGLIB相比于JDK動態(tài)代理更加強大,JDK動態(tài)代理雖然簡單易用,但是其有一個致命缺陷是粉怕,只能對接口進行代理巷嚣。如果要代理的類為一個普通類、沒有接口亭珍,那么Java動態(tài)代理就沒法使用了。


CGLIB組成結構

CGLIB底層使用了ASM(一個短小精悍的字節(jié)碼操作框架)來操作字節(jié)碼生成新的類。除了CGLIB庫外唧取,腳本語言(如Groovy何BeanShell)也使用ASM生成字節(jié)碼。ASM使用類似SAX的解析器來實現(xiàn)高性能划提。我們不鼓勵直接使用ASM枫弟,因為它需要對Java字節(jié)碼的格式足夠的了解

CGLIB的簡單使用

<dependency>
     <groupId>cglib</groupId>
     <artifactId>cglib</artifactId>
     <version>3.1</version>
</dependency>


import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class SampleClass {
    public void test(){
        System.out.println("hello world");
    }

    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(SampleClass.class);
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                System.out.println("before method run...");
                Object result = proxy.invokeSuper(obj, args);
                System.out.println("after method run...");
                return result;
            }
        });
        SampleClass sample = (SampleClass) enhancer.create();
        sample.test();
    }

}

在mian函數(shù)中,我們通過一個Enhancer和一個MethodInterceptor來實現(xiàn)對方法的攔截鹏往,運行程序后輸出為:

before method run...
hello world
after method run...

Process finished with exit code 0

上面就是CGLIB的簡單介紹及應用淡诗,CGLIB不是本文的重點,這里不再詳述伊履。Spring就是依靠CGLIB完成業(yè)務類的動態(tài)代理韩容。

拋磚引玉

老規(guī)矩,首先祭出我們的配置類

@ComponentScan("com.app")
@Configuration
public class Config {
}

啟動spring

public class SpringTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(Config.class);
        context.refresh();
        System.out.println(context.getBean(Config.class));
    }
}

spring啟動后唐瀑,我們獲取Config配置對象宙攻,打印結果如下:


com.config.Config$$EnhancerBySpringCGLIB$$efc56a4d@33e5ccce,不言而喻介褥,Conifg被CGLIB動態(tài)代理成了另一個增強型的對象座掘。

我們看,Config類上有個@Configuration注解柔滔,這個注解的作用前面文章講過好多了溢陪,表示這是一個配置類,spring掃描注冊的時候會解析這個類睛廊,但是如果把這個注解去掉形真,我們看一下打印結果:



此時,spring就沒有對它進行動態(tài)代理了超全。其實咆霜,Configuration的作用遠不止如此,我們繼續(xù)測試嘶朱。
首先生成兩個業(yè)務類E和F

public class E {
}
public class F {
}

通過@Bean方式注入

@ComponentScan("com.app")
@Configuration
public class Config {

    @Bean
    public E getE(){
        System.out.println("get class E");
        return new E();
    }
    @Bean
    public F getF(){
        getE();
        System.out.println("get class F");
        return new F();
    }
}

getE方法會生成E類的實例對象蛾坯,getF方法在生成F實例對象的同時,會再一次調用getE方法生成一個E實例對象疏遏,會嗎脉课?

public class SpringTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(Config.class);
        context.refresh();
        System.out.println(context.getBean(E.class));
        System.out.println(context.getBean(F.class));
    }
}

打印結果:



getE在整個過程中救军,只被調用了一次,換句話講倘零,getE()方法在getF()中并沒有起作用唱遭!好神奇!如果去掉@Configuration這個注解呈驶,情況就不一樣了拷泽,讀者可自行測試。

spring是怎么判斷Config是否需要代理的呢袖瞻?

context.refresh()完成了啟動過程跌穗,跟進代碼找到invokeBeanFactoryPostProcessors(beanFactory);,繼續(xù)跟進虏辫,第一行PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());蚌吸,invokeBeanFactoryPostProcessors這個方法就是調用各種后置處理器的,前面博文也講過太多了砌庄,這里不再詳述羹唠,如果讀者看到這里有點懵的話,建議按順序閱讀本專題娄昆。繼續(xù)佩微,ConfigurationClassPostProcessor完成了掃描注冊,完事找到方法里這么一行代碼

invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);

這行代碼跟進去萌焰,看下源碼

    private static void invokeBeanFactoryPostProcessors(
            Collection<? extends BeanFactoryPostProcessor> postProcessors, ConfigurableListableBeanFactory beanFactory) {

        for (BeanFactoryPostProcessor postProcessor : postProcessors) {
            postProcessor.postProcessBeanFactory(beanFactory);
        }
    }

假設我們程序員沒有提供后置處理器的話哺眯,這里的postProcessors只有一個,就是ConfigurationClassPostProcessor扒俯,怎么老是這個后置處理器奶卓,這個類實現(xiàn)了BeanDefinitionRegistryPostProcessor接口,而BeanDefinitionRegistryPostProcessor接口又繼承了BeanFactoryPostProcessor接口撼玄,ConfigurationClassPostProcessor通過BeanDefinitionRegistryPostProcessor接口的postProcessBeanDefinitionRegistry方法完成了掃描注冊夺姑,spring緊接著調用ConfigurationClassPostProcessor所實現(xiàn)BeanFactoryPostProcessor接口的postProcessBeanFactory方法完成后續(xù)處理,這個后續(xù)處理就是上面的源碼啦掌猛!

源碼很簡單盏浙,就是調用了后置處理器的postProcessBeanFactory方法,在這里就是ConfigurationClassPostProcessor的postProcessBeanFactory方法荔茬,我們看下它的源碼

    /**
     * Prepare the Configuration classes for servicing bean requests at runtime
     * by replacing them with CGLIB-enhanced subclasses.
     */
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        int factoryId = System.identityHashCode(beanFactory);
        if (this.factoriesPostProcessed.contains(factoryId)) {
            throw new IllegalStateException(
                    "postProcessBeanFactory already called on this post-processor against " + beanFactory);
        }
        this.factoriesPostProcessed.add(factoryId);
        if (!this.registriesPostProcessed.contains(factoryId)) {
            // BeanDefinitionRegistryPostProcessor hook apparently not supported...
            // Simply call processConfigurationClasses lazily at this point then.
            processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
        }

        enhanceConfigurationClasses(beanFactory);
        beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
    }

方法的注釋的意思是废膘,準備配置類以便在運行時為bean請求提供服務,方法是用cglib增強的子類替換它們慕蔚,也就是使用cglib的代理的方式增強beanDefinition丐黄。

前面是對當前beanDefinitionRegister做判斷,是否已經處理過和注冊過坊萝,不出意外的話就會進入enhanceConfigurationClasses(beanFactory);這個方法孵稽,顧名思義许起,就是增強配置類的十偶,這也解釋了為什么前面的Config加上@Configuration就會被動態(tài)代理菩鲜。那么下面的重點就是閱讀enhanceConfigurationClasses的源碼嘍,源碼首先找出所有的帶有@Configuration注解的配置類并存放到LinkedHashMap集合中惦积。在這里只有咱們的Config配置類符合接校,然后遍歷集合進行增強處理:
首先,根據(jù)BeanDefinition獲取對應的class對象
Class<?> configClass = beanDef.resolveBeanClass(this.beanClassLoader);
然后就是增強處理:
Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);

下一個分析enhance方法看如何增強的

    /**
     * Loads the specified class and generates a CGLIB subclass of it equipped with
     * container-aware callbacks capable of respecting scoping and other bean semantics.
     * @return the enhanced subclass
     */
    public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {
        if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {
            if (logger.isDebugEnabled()) {
                logger.debug(String.format("Ignoring request to enhance %s as it has " +
                        "already been enhanced. This usually indicates that more than one " +
                        "ConfigurationClassPostProcessor has been registered (e.g. via " +
                        "<context:annotation-config>). This is harmless, but you may " +
                        "want check your configuration and remove one CCPP if possible",
                        configClass.getName()));
            }
            return configClass;
        }
        Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
        if (logger.isTraceEnabled()) {
            logger.trace(String.format("Successfully enhanced %s; enhanced class name is: %s",
                    configClass.getName(), enhancedClass.getName()));
        }
        return enhancedClass;
    }

看這個方法的注釋狮崩,加載指定的類并生成一個CGLIB代理的子類蛛勉。源碼最重要的一行
Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
首先分析newEnhancer方法:

    /**
     * Creates a new CGLIB {@link Enhancer} instance.
     */
    private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
        Enhancer enhancer = new Enhancer();
        //把業(yè)務類,這里是Config睦柴,設置成代理類的父類
        enhancer.setSuperclass(configSuperClass);
        //代理類實現(xiàn)EnhancedConfiguration接口
        enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
        enhancer.setUseFactory(false);
        //設置代理類名稱的生成策略
        enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
        enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
        enhancer.setCallbackFilter(CALLBACK_FILTER);
        enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
        return enhancer;
    }

看注釋诽凌,生成一個CGLIB代理實例,里面用到了Enhancer 坦敌,不就是文章開頭講的那個小demo吧侣诵!
然后就是createClass方法:

    /**
     * Uses enhancer to generate a subclass of superclass,
     * ensuring that callbacks are registered for the new subclass.
     */
    private Class<?> createClass(Enhancer enhancer) {
        Class<?> subclass = enhancer.createClass();
        // Registering callbacks statically (as opposed to thread-local)
        // is critical for usage in an OSGi environment (SPR-5932)...
        Enhancer.registerStaticCallbacks(subclass, CALLBACKS);
        return subclass;
    }

通過Enhancer 生成一個所代理的類的子類。代理結束后返回狱窘,一直返回到enhanceConfigurationClasses方法的Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);杜顺,這時候的enhancedClass 就是代理類了,OK蘸炸,這行代碼執(zhí)行完后往下執(zhí)行了beanDef.setBeanClass(enhancedClass);躬络,意思很明朗,就是將Config的BeanDefinitino中的class替換成代理class搭儒。之后就會實例化代理類而不是Config類本身穷当。

現(xiàn)在解釋清楚了,為什么Config加上@Configuration注解后就會被spring動態(tài)代理淹禾。再解釋上文getE()方法在getF()中并沒有起作用膘滨!很明顯,spring既然代理了Config稀拐,那么執(zhí)行getF方法時不是真的執(zhí)行Config里的getF方法火邓,而是執(zhí)行代理類的getF方法,在哪里執(zhí)行的呢德撬?

newEnhancer方法中铲咨,有一個過濾器的設置enhancer.setCallbackFilter(CALLBACK_FILTER);,CALLBACK_FILTER是一個變量:

    private static final Callback[] CALLBACKS = new Callback[] {
            new BeanMethodInterceptor(),
            new BeanFactoryAwareMethodInterceptor(),
            NoOp.INSTANCE
    };

    private static final ConditionalCallbackFilter CALLBACK_FILTER = new ConditionalCallbackFilter(CALLBACKS);

文章一開始舉的CGLIB案例中,就是通過回調完成了方法的攔截對吧蜓洪? 這里有兩個回調類BeanMethodInterceptor和BeanFactoryAwareMethodInterceptor纤勒,他倆組成了一個回調鏈,依次調用而已隆檀。這兩個回調其實是在bean的聲明周期過程中調用的摇天,這是后續(xù)章節(jié)的內容粹湃,這里我們簡單講下,后面會詳細講泉坐。

在實例化過程中为鳄,我們主要關注BeanMethodInterceptor這個回調。我們在調用getF方法時腕让,會先執(zhí)行回調BeanMethodInterceptor中的intercept方法孤钦。intercept方法很復雜很復雜,大概意思是纯丸,在執(zhí)行getF中的getE方法時判斷getE返回的bean是否已經實例化了偏形,如果已經實例化了就不再調用該方法了。getF和getE調用的時候都是先調用回調函數(shù)的觉鼻,都會判斷是否已經實例化了俊扭。spring以此保證@Bean返回的實例是單例的。

本篇講的比較簡單比較淺坠陈,估計讀者也是明白個大概的原理萨惑,因為這里涉及到后續(xù)的知識,沒關系畅姊,后面會再詳細講解的咒钟。

這里大家主要了解

  1. CGLIB的使用
  2. Spring是如何利用到CGLIB的
  3. 如何使用內置的代理回調類BeanMethodInterceptor、BeanFactoryAwareMethodInterceptor來增強我們的目標類方法的即可若未。
  4. BeanDefinition將對應的class用代理類替換掉業(yè)務類朱嘴,后期實例化的是代理類
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市粗合,隨后出現(xiàn)的幾起案子萍嬉,更是在濱河造成了極大的恐慌,老刑警劉巖隙疚,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件壤追,死亡現(xiàn)場離奇詭異,居然都是意外死亡供屉,警方通過查閱死者的電腦和手機行冰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來伶丐,“玉大人悼做,你說我怎么就攤上這事』┗辏” “怎么了肛走?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長录别。 經常有香客問我朽色,道長邻吞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任葫男,我火速辦了婚禮抱冷,結果婚禮上,老公的妹妹穿的比我還像新娘腾誉。我一直安慰自己徘层,他們只是感情好峻呕,可當我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布利职。 她就那樣靜靜地躺著,像睡著了一般瘦癌。 火紅的嫁衣襯著肌膚如雪猪贪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天讯私,我揣著相機與錄音热押,去河邊找鬼。 笑死斤寇,一個胖子當著我的面吹牛桶癣,可吹牛的內容都是我干的。 我是一名探鬼主播娘锁,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼牙寞,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了莫秆?” 一聲冷哼從身側響起间雀,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎镊屎,沒想到半個月后惹挟,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡缝驳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年连锯,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片用狱。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡运怖,死狀恐怖,靈堂內的尸體忽然破棺而出齿拂,到底是詐尸還是另有隱情驳规,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布署海,位于F島的核電站吗购,受9級特大地震影響医男,放射性物質發(fā)生泄漏。R本人自食惡果不足惜捻勉,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一镀梭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧踱启,春花似錦报账、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至冠蒋,卻和暖如春羽圃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背抖剿。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工朽寞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人斩郎。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓脑融,卻偏偏與公主長得像,于是被迫代替她去往敵國和親缩宜。 傳聞我的和親對象是個殘疾皇子肘迎,可洞房花燭夜當晚...
    茶點故事閱讀 42,925評論 2 344