1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Spring IOC 原理深層解析

        共 10504字,需瀏覽 22分鐘

         ·

        2020-08-16 08:35

        點擊上方藍色字體,選擇“標星公眾號”

        優(yōu)質(zhì)文章,第一時間送達

        ? 作者?|?CryFace

        來源 |?urlify.cn/vmyIVv


        一、Spring IOC概念認識

        區(qū)別IOC與DI

        首先我們要知道IOC(Inverse of Control:控制反轉(zhuǎn))是一種設(shè)計思想,就是?將原本在程序中手動創(chuàng)建對象的控制權(quán),交由Spring框架來管理。這并非Spring特有,在其他語言里面也有體現(xiàn)。IOC容器是Spring用來實現(xiàn)IOC的載體,?IOC容器實際上就是個Map(key,value),Map 中存放的是各種對象。

        或許是IOC不夠開門見山,Martin Fowler提出了DI(dependency injection)來替代IOC,即讓調(diào)用類對某一接口實現(xiàn)類的依賴關(guān)系由第三方(容器或協(xié)作類)注入,以移除調(diào)用類對某一接口實現(xiàn)類的依賴。

        所以我們要區(qū)別IOC與DI,簡單來說IOC的主要實現(xiàn)方式有兩種:

        • 依賴查找

        • 依賴注入

        我們DI就是依賴注入,也就是IOC的一種可取的實現(xiàn)方式!對兩個概念總結(jié)以下:

        • IOC (Inversion of control ) 控制反轉(zhuǎn)/反轉(zhuǎn)控制。是站在對象的角度,對象實例化以及管理的權(quán)限(反轉(zhuǎn))交給了容器。

        • DI (Dependancy Injection)依賴注入。是站在容器的角度,容器會把對象依賴的其他對象注入(送進去)。例如:對象A 實例化過程中因為聲明了一個B類型的屬性,那么就需要容器把B對象注入到A中。

        通過使用IOC容器可以對我們的對象注入依賴(DI),實現(xiàn)控制反轉(zhuǎn)!

        IOC解決的問題

        通過上面的介紹,我們大概理解了IOC的概念,也知道它的作用。那么也會有疑惑,為什么需要依賴反轉(zhuǎn)呢,有什么好處,解決了什么問題?

        簡單來說,IOC 容器就像是一個工廠一樣,當我們需要創(chuàng)建一個對象的時候,只需要配置好配置文件/注解即可,完全不用考慮對象是如何被創(chuàng)建出來的。?在實際項目中一個 Service 類可能有幾百甚至上千個類作為它的底層,假如我們需要實例化這個 Service,你可能要每次都要搞清這個 Service 所有底層類的構(gòu)造函數(shù),這可能會把人逼瘋。如果利用 IOC 的話,你只需要配置好,然后在需要的地方引用就行了,這大大增加了項目的可維護性且降低了開發(fā)難度。

        舉個例子:現(xiàn)有一個針對User的操作,利用 Service 和 Dao 兩層結(jié)構(gòu)進行開發(fā)!

        在沒有使用IOC思想的情況下,Service 層想要使用 Dao層的具體實現(xiàn)的話,需要通過new關(guān)鍵字在UserServiceImpl 中手動 new出 IUserDao 的具體實現(xiàn)類 UserDaoImpl(不能直接new接口類)。

        這種方式可以實現(xiàn),但是如果開發(fā)過程中接到新需求,針對IUserDao 接口開發(fā)出另一個具體實現(xiàn)類。因為Server層依賴了IUserDao的具體實現(xiàn),所以我們需要修改UserServiceImpl中new的對象。如果只有一個類引用了IUserDao的具體實現(xiàn),可能覺得還好,修改起來也不是很費力氣,但是如果有許許多多的地方都引用了IUserDao的具體實現(xiàn)的話,一旦需要更換IUserDao的實現(xiàn)方式,那修改起來將會非常的頭疼。

        但是如果使用IOC容器的話,我們就不需要操心這些事,只需要用的時候往IOC容器里面“要”就完事。

        二、Spring IOC容器實現(xiàn)

        在IOC容器的設(shè)計中,有兩個主要的容器系列,一個是實現(xiàn)BeanFactory接口的簡單容器系列,這系列容器只實現(xiàn)了容器的最基本功能;另一個是ApplicationContext應(yīng)用上下文,它作文容器的高級形態(tài)而存在。后面作為容器的高級形態(tài),在簡單容器的基礎(chǔ)上面增加了許多的面向框架的特性,同時對應(yīng)用環(huán)境作了許多適配。

        BeanFactory

        BeanFactory,從名字上也很好理解,生產(chǎn) bean 的工廠,它負責生產(chǎn)和管理各個 bean 實例。

        我們先來看一下BeanFactory的繼承體系

        先介紹一下里面比較重要的一些接口和類

        1. ApplicationContext 繼承了 ListableBeanFactory,這個 Listable 的意思就是,通過這個接口,我們可以獲取多個 Bean,大家看源碼會發(fā)現(xiàn),最頂層 BeanFactory 接口的方法都是獲取單個 Bean 的。

        2. ApplicationContext 繼承了 HierarchicalBeanFactory,Hierarchical 單詞本身已經(jīng)能說明問題了,意思是分層,也就是說我們可以在應(yīng)用中起多個 BeanFactory,然后可以將各個 BeanFactory 設(shè)置為父子關(guān)系。

        3. AutowireCapableBeanFactory 這個名字中的 Autowire 大家都非常熟悉,它就是用來自動裝配 Bean 用的(如按名字匹配,按類型匹配等),但是仔細看上圖,ApplicationContext 并沒有繼承它,不過不用擔心,不使用繼承,不代表不可以使用組合,如果你看到 ApplicationContext 接口定義中的最后一個方法 getAutowireCapableBeanFactory() 就知道了。

        4. ConfigurableListableBeanFactory 也是一個特殊的接口,看圖,特殊之處在于它繼承了第二層所有的三個接口,而 ApplicationContext 沒有。用于擴展IOC容器的定制性!

        ApplicationContext

        ApplicationContext下面有著我們通過配置文件來構(gòu)建,也是我們的子實現(xiàn)類。先來看一下繼承體系

        我們重點了解一下比較主要的實現(xiàn)類:

        • ClassPathXmlApplicationContext從名字可以看出一二,就是在ClassPath中尋找xml配置文件,根據(jù)xml文件內(nèi)容來構(gòu)件ApplicationContext容器。

        • FileSystemXmlApplicationContext?的構(gòu)造函數(shù)需要一個 xml 配置文件在系統(tǒng)中的路徑,其他和 ClassPathXmlApplicationContext 基本上一樣。

        • AnnotationConfigApplicationContext?是基于注解來使用的,它不需要配置文件,采用 Java 配置類和各種注解來配置,是比較簡單的方式,也是大勢所趨。

        • ConfigurableApplicationContext?擴展于 ApplicationContext,它新增加了兩個主要的方法:refresh()和 close(),讓 ApplicationContext 具有啟動、刷新和關(guān)閉應(yīng)用上下文的能力。在應(yīng)用上下文關(guān)閉的情況下調(diào)用 refresh()即可啟動應(yīng)用上下文,在已經(jīng)啟動的狀態(tài)下,調(diào)用 refresh()則清除緩存并重新裝載配置信息,而調(diào)用close()則可關(guān)閉應(yīng)用上下文。

        此外,ApplicationContext還通過其他接口擴展了BeanFactory的功能,如下圖

        • ApplicationEventPublisher:讓容器擁有發(fā)布應(yīng)用上下文事件的功能,包括容器啟動事件、關(guān)閉事件等。實現(xiàn)了 ApplicationListener 事件監(jiān)聽接口的 Bean 可以接收到容器事件 , 并對事件進行響應(yīng)處理 。在 ApplicationContext 抽象實現(xiàn)類AbstractApplicationContext 中,我們可以發(fā)現(xiàn)存在一個 ApplicationEventMulticaster,它負責保存所有監(jiān)聽器,以便在容器產(chǎn)生上下文事件時通知這些事件監(jiān)聽者。

        • MessageSource:為應(yīng)用提供 i18n 國際化消息訪問的功能。

        • ResourcePatternResolver :ApplicationContext 實現(xiàn)類都實現(xiàn)了類似于PathMatchingResourcePatternResolver 的功能,可以通過帶前綴的 Ant 風格的資源文件路徑裝載 Spring 的配置文件。

        WebApplicationContext

        在ApplicationContext下面還有一個實現(xiàn)類是WebApplicationContext,是專門為 Web 應(yīng)用準備的容器,它允許從相對于 Web 根目錄的路徑中裝載配置文件完成初始化工作。

        從WebApplicationContext 中可以獲得 ServletContext 的引用,整個 Web 應(yīng)用上下文對象將作為屬性放置到 ServletContext 中,以便 Web 應(yīng)用環(huán)境可以訪問 Spring 應(yīng)用上下文。WebApplicationContext 定義了一個常量ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,在上下文啟動時, WebApplicationContext 實例即以此為鍵放置在 ServletContext 的屬性列表中,因此我們可以直接通過以下語句從 Web 容器中獲取WebApplicationContext:

        WebApplicationContext wac = (WebApplicationContext)servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);

        整合圖如下,其他不過多介紹:

        三、SpringIOC的啟動流程

        Spring IOC的啟動時會讀取應(yīng)用程序提供的Bean的配置信息,并在Spring容器中生成一份相應(yīng)的Bean配置注冊表,然后根據(jù)注冊表加載、實例化bean、建立bean與bean之間的依賴關(guān)系。然后將這些準備就緒的bean放到bean緩存池中,等待應(yīng)用程序調(diào)用。

        總結(jié)一下,我們可以把IOC的啟動流程分為一下兩個重要的階段:

        1. 容器的啟動階段

        2. Bean的實例化階段

        這里補充一下,在 Spring 中,最基礎(chǔ)的容器接口方法是由 BeanFactory 定義的,而 BeanFactory 的實現(xiàn)類采用的是?延遲加載,也就是說,容器啟動時,只會進行第一個階段的操作, 當需要某個類的實例時,才會進行第二個階段的操作。而 ApplicationContext(另一個容器的實現(xiàn)類)在啟動容器時就完成了所有初始化,這就需要更多的系統(tǒng)資源,我們需要根據(jù)不同的場景選擇不同的容器實現(xiàn)類。我們下面介紹更多是以ApplicationContext為主來介紹!

        IOC容器的啟動階段

        在容器啟動階段,我們的Spring經(jīng)歷了很多事情,具體的話可以分為以下幾個步驟:

        1. 加載配置信息

        2. 解析配置信息

        3. 裝配BeanDefinition

        4. 后處理

        加載配置信息

        這里我們要先回顧一下之前的beanfactory了,我們說這是一個最基礎(chǔ)的bean工廠接口,那么就需要我們的實現(xiàn)類,我們上面雖然說到了ApplicationContext,但是我們再仔細看一下那張圖,然后站高處來看。ApplicationContext 繼承自 BeanFactory,但是它不應(yīng)該被理解為 BeanFactory 的實現(xiàn)類,而是說其內(nèi)部持有一個實例化的 BeanFactory(DefaultListableBeanFactory)。以后所有的 BeanFactory 相關(guān)的操作其實是委托給這個實例來處理的。

        我們?yōu)槭裁催x擇了DefaultListableBeanFactory,可以看到它繼承的兩個父類,然后繼續(xù)延伸上去齊全了所有的功能??梢哉fDefaultListableBeanFactory 基本上是最牛的 BeanFactory 了,這也是為什么這邊會使用這個類來實例化的原因。

        好了,我們繼續(xù)回到加載配置文件信息這個話題。我們Spring加載配置文件最開始圖里面也介紹了,有ClassPathXmlApplicationContext?類路徑加載和FileSystemXmlApplicationContext?文件系統(tǒng)加載。

        然后就是我們IOC 容器讀取配置文件的接口為?BeanDefinitionReader,它會根據(jù)配置文件格式的不同給出不同的實現(xiàn)類,將配置文件中的內(nèi)容讀取并映射到?BeanDefinition?中。比如xml文件就會用XmlBeanDefinitionReader。

        解析配置信息

        我們解析配置信息就是要將我們讀取的配置信息里面的信息轉(zhuǎn)換成一個dom樹,然后解析里面的配置信息裝配到我們的BeanDefinition。我們在processBeanDefinition中先將解析后的信息封裝到一個BeanDefinitionHolder,一個BeanDefinitionHolder其實就是一個 BeanDefinition 的實例和它的 beanName、aliases (別名)這三個信息。

        processBeanDefinition過程可以解析很多的標簽,如factory-beanfactory-method、、、、,當然最顯目的就是,例如以下的屬性:

        Property
        class類的全限定名
        name可指定 id、name(用逗號、分號、空格分隔)
        scope作用域
        constructor arguments指定構(gòu)造參數(shù)
        properties設(shè)置屬性的值
        autowiring modeno(默認值)、byName、byType、 constructor
        lazy-initialization mode是否懶加載(如果被非懶加載的bean依賴了那么其實也就不能懶加載了)
        initialization methodbean 屬性設(shè)置完成后,會調(diào)用這個方法
        destruction methodbean 銷毀后的回調(diào)方法

        在具體的xml配置文件中可以是這樣子的:

        <bean id="exampleBean" name="name1, name2, name3" class="com.javadoop.ExampleBean"
        scope="singleton" lazy-init="true" init-method="init" destroy-method="cleanup">


        <constructor-arg type="int" value="7500000"/>
        <constructor-arg name="years" value="7500000"/>
        <constructor-arg index="0" value="7500000"/>


        <property name="beanOne">
        <ref bean="anotherExampleBean"/>
        property>
        <property name="beanTwo" ref="yetAnotherBean"/>
        <property name="integerProperty" value="1"/>
        bean>

        裝配BeanDefinition

        在上面我們將信息解析后,就會裝配到一個BeanDefinitionHolder,里面就包含了我們的BeanDefinition。然后裝配BeanDefinition,就是將這些BeanDefinition注冊到BeanDefinitionRegistry(說到底核心是一個 beanName-> beanDefinition 的 map)中。我們在獲取的BeanDefinition的時候需要通過key(beanName)獲取別名,然后通過別名再一次重定向獲取我們的BeanDefinition。

        Spring容器的后續(xù)操作直接從BeanDefinitionRegistry中讀取配置信息。具體注冊實現(xiàn)就是在我們上面介紹到的DefaultListableBeanFactory實現(xiàn)類里。

        后處理

        在我們的后續(xù)操作,容器掃描BeanDefinitionRegistry中的BeanDefinition,使用Java的反射機制自動識別出Bean工廠后處理后器(實現(xiàn)BeanFactoryPostProcessor接口)的Bean,然后調(diào)用這些Bean工廠后處理器對BeanDefinitionRegistry中的BeanDefinition進行加工處理。主要完成以下兩項工作:

        • 對使用到占位符的元素標簽進行解析,得到最終的配置值,這意味對一些半成品式的BeanDefinition對象進行加工處理并得到成品的BeanDefinition對象;

        • BeanDefinitionRegistry中的BeanDefinition進行掃描,通過Java反射機制找出所有屬性編輯器的Bean(實現(xiàn)java.beans.PropertyEditor接口的Bean),并自動將它們注冊到Spring容器的屬性編輯器注冊表中(PropertyEditorRegistry);

        Spring容器從BeanDefinitionRegistry中取出加工后的BeanDefinition,并調(diào)用InstantiationStrategy著手進行Bean實例化的工作;在實例化Bean時,Spring容器使用BeanWrapper對Bean進行封裝,BeanWrapper提供了很多以Java反射機制操作Bean的方法,它將結(jié)合該Bean的BeanDefinition以及容器中屬性編輯器,完成Bean屬性的設(shè)置工作。

        在我們裝配好Bean容器后,還要通過方法prepareBeanFactory準備Bean容器,在準備階段會注冊一些特殊的Bean,這里不做深究。在準備容器后我們可能會對bean進行一些加工,就需要用到beanPostProcessor來進行一些后處理。我們利用容器中注冊的Bean后處理器(實現(xiàn)BeanPostProcessor接口的Bean)對已經(jīng)完成屬性設(shè)置工作的Bean進行后續(xù)加工,直接裝配出一個準備就緒的Bean。這個在下面實例化階段后再介紹到!

        這里可能會對BeanPostProcessorBeanFactoryPostProcessor產(chǎn)生混亂,理解不清??偨Y(jié)一下兩者的區(qū)別:

        • BeanPostProcessor?對容器中的Bean進行后處理,對Bean進行額外的加強,加工。使用點是在我們單例Bean實例化過程中穿插執(zhí)行的。

        • BeanFactoryPostProcessor?對Spring容器本身進行后處理,增強容器的功能。是在我們單例實例化之前執(zhí)行的。

        更加具體的可以參考這一篇博文。點擊跳轉(zhuǎn)

        總結(jié)

        這里關(guān)于容器的啟動過程很多細節(jié)并不是很詳細,因為很多東西都需要配著源碼才能分析。關(guān)于源碼解析推薦這一篇,更加深入(Spring IOC 容器源碼分析)

        Bean的實例化階段

        然后就是我們Bean的預(yù)先實例化階段。在ApplicationContext中,所有的BeanDefinition的Scope默認是Singleton,針對Singleton我們Spring容器采用是預(yù)先實例化的策略。這樣我們在獲取實例的時候就會直接從緩存里面拉取出來,提升了運行效率。

        但是如果我們設(shè)置了懶加載的話,那么就不會預(yù)先實例化。而是在我們第一次getBean的時候才會去實例化。不過我們大部分時候都不會去使用懶加載,除非這個bean比較特殊,例如非常耗費資源,在應(yīng)用程序的生命周期里的使用概率比較小。在這種情況下我們可以將它設(shè)置為懶加載!

        實例化過程

        針對我們的Bean的實例化,具體一點的話可以分為以下階段:

        1. Spring對bean進行實例化,默認bean是單例;

        2. Spring對bean進行依賴注入,比如有沒有配置當前depends-on的依賴,有的話就去實例依賴的bean;

        3. 如果bean實現(xiàn)了BeanNameAware接口,spring將bean的id傳給setBeanName()方法;

        4. 如果bean實現(xiàn)了BeanFactoryAware接口,spring將調(diào)用setBeanFactory方法,將BeanFactory實例傳進來;

        5. 如果bean實現(xiàn)了ApplicationContextAware接口,它的setApplicationContext()方法將被調(diào)用,將應(yīng)用上下文的引用傳入到bean中;

        6. 如果bean實現(xiàn)了BeanPostProcessor接口,它的postProcessBeforeInitialization方法將被調(diào)用;

        7. 如果bean實現(xiàn)了InitializingBean接口,spring將調(diào)用它的afterPropertiesSet接口方法,類似的如果bean使用了init-method屬性聲明了初始化方法,則再調(diào)用該方法;

        8. 如果bean實現(xiàn)了BeanPostProcessor接口,它的postProcessAfterInitialization接口方法將被調(diào)用;

        9. 此時bean已經(jīng)準備就緒,可以被應(yīng)用程序使用了,他們將一直駐留在應(yīng)用上下文中,直到該應(yīng)用上下文被銷毀;

        10. 若bean實現(xiàn)了DisposableBean接口,spring將調(diào)用它的distroy()接口方法。如果bean使用了destroy-method屬性聲明了銷毀方法,則再調(diào)用該方法;

        上面提及到的方法有點多,但是我們可以按照分類去記憶

        分類類型所包含方法
        Bean自身的方法配置文件中的init-method和destroy-method配置的方法、Bean對象自己調(diào)用的方法
        Bean級生命周期接口方法BeanNameAware、BeanFactoryAware、InitializingBean、DiposableBean等接口中的方法
        容器級生命周期接口方法InstantiationAwareBeanPostProcessor、BeanPostProcessor等后置處理器實現(xiàn)類中重寫的方法

        循環(huán)依賴問題

        關(guān)于實例化過程其實是一塊比較復(fù)雜的東西,如果不去看源碼的話,講個上面的流程也差不多。畢竟完全講的話,哪能記住那么多。在這里還有一個主要講的就是在實例化過程中一個比較復(fù)雜的問題,就是“循環(huán)依賴問題”。這里花點篇幅講解一下。

        循環(huán)依賴問題,舉個例子引入一下。比如我們有A,B兩個類,A的 構(gòu)造方法有一個參數(shù)是B,B的構(gòu)造方法有一個參數(shù)是A,這種A依賴于B,B依賴于A的問題就是依賴問題。

        @Service
        public class A {
        public A(B b) { }
        }

        @Service
        public class B {
        public B(A a) {
        }
        }

        或者說A依賴于B,B依賴于C,C依賴于A也是。

        我們的循環(huán)依賴可以分類成三種:

        • 原型循環(huán)依賴

        • 單例構(gòu)造器循環(huán)依賴

        • 單例setter注入循環(huán)依賴

        我們的Spring是無法解決構(gòu)造器的循環(huán)依賴的,但是可以解決setter的循環(huán)依賴。關(guān)于這三者的區(qū)別,這里給出一篇比較詳細的博文可以參考。(循環(huán)依賴的三種方式)

        循環(huán)依賴解決

        構(gòu)造器依賴問題

        我們說過構(gòu)造器的循環(huán)依賴Spring是無法解決的,那引出另一個問題就是Spirng是如何判斷構(gòu)造器發(fā)生了循環(huán)依賴呢?

        簡單介紹一下,我們在上面介紹的例子,A依賴于B,B依賴于A。在我們A實例化的時候要去實例B,然后B又要去實例A,在我們過程中,我們這將beanName添加到一個set結(jié)構(gòu)中,當?shù)诙翁砑覣的時候,也就是B依賴于A,要去實例化A的時候,因為Set已經(jīng)存在A的beanName了,所以Spring就會判斷發(fā)生了循環(huán)依賴問題,拋出異常!

        原型依賴問題

        至于原型依賴的判斷條件其實和構(gòu)造器的判斷差不多,最主要的區(qū)別就是set的類型變成了ThreadLocal類型的。

        Setter是如何具體解決循環(huán)依賴問題呢?

        我們的Spring是通過三級緩存來解決的。

        三級緩存呢,其實就是有三個緩存:

        • singletonObjects(一級緩存)

        • earlySingletonObjects(二級緩存)

        • singletonFactories(三級緩存)

        我們以上面A依賴于B,B依賴于A的樣例來分析一下setter是如何通過三級緩存解決循環(huán)依賴問題。

        1. 首先我們在實例化A的時候,通過beanDifinition定義拿到A class的無參構(gòu)造方法,通過反射創(chuàng)建了這個實例對象。這個A的實例對象是一個尚未進行依賴注入和init-method方法調(diào)用等等邏輯處理的早期實例,是我們業(yè)務(wù)無法使用的。然后在進行后續(xù)的包裝處理前,我們會將它封裝成一個ObjectFactory對象然后存入到我們的三級緩存中(key是beanName,value是ObjectFactory對象),相當于一個早起工廠提前曝光。

        2. 然后呢我們的會繼續(xù)實例化A,在實例過程中因為A依賴于B,我們通過Setter注入依賴的時候,通過getBean(B)去獲取依賴對象B,但是這個B還沒有實例化,所以我們就需要去創(chuàng)建B的實例。

        3. 然后我們就開始創(chuàng)建B的實例,同上A的過程。在實例B的過程中,因為B依賴于A,所以也會調(diào)用getBean(A)去獲得A的實例,首先就會去一級緩存訪問,如果沒有就去二級緩存,再沒有就去三級緩存。然后在三級緩存中發(fā)現(xiàn)我們的早期實例A,不過也拿來用了。然后完成B的依賴,再完成后面B實例化過程的一系列階段,最后并且存放到Spring的一級緩存中。并將二三級緩存清理掉。

        4. 完成B的實例后,我們就會回到A的實例階段,我們的A在有了B的依賴后,也繼續(xù)完成了后續(xù)的實例化過程,把一個早期的對象變成一個完整的對象。并將A存進到一級緩存中,清除二三級緩存。

        為什么要有三級緩存?二級緩存不夠用嗎?

        我們在上面分析的過程中呢,可能會感覺二級緩存的存在感不是特別強。為什么不去掉第二級的緩存然后變成一個二級緩存呢。

        這里呢,解釋一下。我們的B在拿到A的早期實例后就會進行緩存升級,將A從從三級緩存移到二級緩存中。之所以需要有三級緩存呢,是因為在這一步,我們的bean可能還需要一些其他的操作,可能會被bean后置處理器進行一些增強之類的啥,或者做一些AOP的判斷。如果只有二級緩存的話,那么返回的就是早期實例而不是我們增強的后的實例!

        四、總結(jié)

        對啟動流程這一塊,看了網(wǎng)上的很多資料,也照著看了一下源碼。雖然自己總結(jié)了,但總感覺哪里會有紕漏。如果哪里有錯誤的話,還請看官們幫忙指出,或者給我指明一下哪里需要修改的地方,大家一起進步學習!



        粉絲福利:108本java從入門到大神精選電子書領(lǐng)取

        ???

        ?長按上方二維碼?2 秒
        回復(fù)「1234」即可獲取資料以及
        可以進入java1234官方微信群



        感謝點贊支持下哈?

        瀏覽 96
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            爱福利视频 | 欧美伊人大香蕉 | 97se亚洲综合自在线尤物 | 超碰人人人操 | 啊轻点灬太粗嗯太深了用力音频 | 国产精品久久久久久久妇 | 翔田千里無碼破解 | 人人摸人人搞 | 欧美做爱视频网站视频 | 丁香五月婷婷色爱 |