Spring容器深入(li),spring容器深入li
spring中最常用的控制反轉和面向切面編程。
一、IOC
IoC(Inversion of Control,控制倒轉)。對於spring架構來說,就是由spring來負責控制對象的生命週期和對象間的關係。在一個對象中,如果要使用另外的對象,就必須得到它(自己new一個,或者從JNDI中查詢一個),使用完之後還要將對象銷毀(比如Connection等),對象始終會和其他的介面或類藕合起來。
所有的類都會在spring容器中登記,告訴spring你是個什麼東西,你需要什麼東西,然後spring會在系統運行到適當的時候,把你要的東西主動給你,同時也把你交給其他需要你的東西。所有的類的建立、銷毀都由 spring來控制,也就是說控制對象生存周期的不再是引用它的對象,而是spring。對於某個具體的對象而言,以前是它控制其他對象,現在是所有對象都被spring控制,所以這叫控制反轉。
IoC的一個重點是在系統運行中,動態向某個對象提供它所需要的其他對象。這一點是通過DI(Dependency Injection,依賴注入)來實現的。比如對象A需要操作資料庫,本來需要在A中自己編寫代碼來獲得一個Connection對象,有了spring我們就只需要告訴spring,A中需要一個Connection,至於這個Connection怎麼構造,何時構造,A不需要知道。在系統運行時,spring會在適當的時候製造一個Connection,然後像打針一樣,注射到A當中,這樣就完成了對各個對象之間關係的控制。A需要依賴Connection才能正常運行,而這個Connection是由spring注入到A中的,依賴注入的名字就這麼來的。那麼DI是如何?的呢? Java 1.3之後一個重要特徵是反射(reflection),它允許程式在啟動並執行時候動態產生對象、執行對象的方法、改變對象的屬性,spring就是通過反射來實現注入的。
在沒有使用Spring的時候,每個對象在需要使用他的合作對象時,自己均要使用像new object() 這樣的文法來將合作對象建立出來,這個合作對象是由自己主動建立出來的,建立合作對象的主動權在自己手上,自己需要哪個合作對象,就主動去建立,建立合作對象的主動權和建立時機是由自己把控的,而這樣就會使得對象間的耦合度高了,A對象需要使用合作對象B來共同完成一件事,A要使用B,那麼A就對B產生了依賴,也就是A和B之間存在一種耦合關係,並且是緊密耦合在一起,而使用了Spring之後就不一樣了,建立合作對象B的工作是由Spring來做的,Spring建立好B對象,然後儲存到一個容器裡面,當A對象需要使用B對象時,Spring就從存放對象的那個容器裡面取出A要使用的那個B對象,然後交給A對象使用,至於Spring是如何建立那個對象,以及什麼時候建立好對象的,A對象不需要關心這些細節問題(你是什麼時候生的,怎麼生出來的我可不關心,能幫我幹活就行),A得到Spring給我們的對象之後,兩個人一起協作完成要完成的工作即可。
所以控制反轉IoC(Inversion of Control)是說建立對象的控制權進行轉移,以前建立對象的主動權和建立時機是由自己把控的,而現在這種權力轉移到第三方,比如轉移交給了IoC容器,它就是一個專門用來建立對象的工廠,你要什麼對象,它就給你什麼對象,有了IoC容器,依賴關係就變了,原先的依賴關係就沒了,它們都依賴IoC容器了,通過IoC容器來建立它們之間的關係。
二、IOC的好處
可維護性比較好,非常便於進行單元測試,便於偵錯工具和診斷故障。代碼中的每一個Class都可以單獨測試,彼此之間互不影響,只要保證自身的功能無誤即可,這就是組件之間低耦合或者無耦合帶來的好處。
每個Team Dev的成員都只需要關心實現自身的商務邏輯,完全不用去關心其它的人工作進展,因為你的任務跟別人沒有任何關係,你的任務可以單獨測試,你的任務也不用依賴於別人的組件,再也不用扯不清責任了。
可複用性好,我們可以把具有普遍性的常用組件獨立出來,反覆利用到項目中的其它部分,或者是其它項目,當然這也是物件導向的基本特徵。
IOC產生對象的方式轉為外置方式,也就是把對象產生放在設定檔裡進行定義,這樣,當我們更換一個實現子類將會變得很簡單,只要修改設定檔就可以了,完全具有熱插撥的特性。
三、IOC常見的注入方式
介面注入(Spring不支援),介面注入模式因為曆史較為悠久,在很多容器中都已經得到應用。但由於其在靈活性、易用性上不如其他兩種注入模式,因而在 IOC 的專題世界內並不被看好。
setter注入,對於習慣了傳統 javabean 開發的程式員,通過 setter 方法設定依賴關係更加直觀。 如果依賴關係較為複雜,那麼構造子注入模式的建構函式也會相當龐大,而此時設值注入模式則更為簡潔。如果用到了第三方類庫,可能要求我們的組件提供一個預設的建構函式,此時構造子注入模式也不適用。
構造器注入,在構造期間完成一個完整的、合法的對象。 所有依賴關係在建構函式中集中呈現。 依賴關係在構造時由容器一次性設定,組件被建立之後一直處於相對“不變”的穩定點。 只有組件的建立者關心其內部依賴關係,對調用者而言,該依賴關係處於“黑盒”之中。
四、AOP
AOP(Aspect-OrientedProgramming,面向切面編程)。例如日誌功能。日誌代碼往往水平地散布在所有對象層次中,而與它所散布到的對象的核心功能毫無關係。對於其他類型的代碼,如安全性、異常處理和透明的持久性也是如此。這種散布在各處的無關的代碼被稱為橫切(cross-cutting)代碼,在OOP設計中,它導致了大量代碼的重複,而不利於各個模組的重用。
而AOP技術則恰恰相反,它利用一種稱為“橫切”的技術,剖解開封裝的對象內部,並將那些影響了多個類的公用行為封裝到一個可重用模組,並將其名為“Aspect”,即切面。所謂“切面”,簡單地說,就是將那些與業務無關,卻為業務模組所共同調用的邏輯或責任封裝起來,便於減少系統的重複代碼,降低模組間的耦合度,並有利於未來的可操作性和可維護性。AOP代表的是一個橫向的關係。
使用“橫切”技術,AOP把軟體系統分為兩個部分:核心關注點和橫切關注點。業務處理的主要流程是核心關注點,與之關係不大的部分是橫切關注點。橫切關注點的一個特點是,他們經常發生在核心關注點的多處,而各處都基本相似。比如許可權認證、日誌、交易處理。Aop 的作用在於分離系統中的各種關注點,將核心關注點和橫切關注點分離開來。
實現AOP的技術,主要分為兩大類:一是採用動態代理技術,利用截取訊息的方式,對該訊息進行裝飾,以取代原有對象行為的執行;二是採用靜態織入的方式,引入特定的文法建立“方面”,從而使得編譯器可以在編譯期間織入有關“方面”的代碼。
五、AOP概念
切面(Aspect):一個關注點的模組化,這個關注點實現可能另外橫切多個對象。交易管理是J2EE應用中一個很好的橫切關注點例子。切面用spring的 Advisor或攔截器實現。
連接點(Joinpoint): 程式執行過程中明確的點,如方法的調用或特定的異常被拋出。
通知(Advice): 在特定的連接點,AOP架構執行的動作。各種類型的通知包括“around”、“before”和“throws”通知。通知類型將在下面討論。許多AOP架構套件括Spring都是以攔截器做通知模型,維護一個“圍繞”連接點的攔截器鏈。Spring中定義了四個advice: BeforeAdvice, AfterAdvice, ThrowAdvice和DynamicIntroductionAdvice。
切入點(Pointcut): 指定一個通知將被引發的一系列連接點的集合。AOP架構必須允許開發人員指定切入點:例如,使用Regex。 Spring定義了Pointcut介面,用來組合MethodMatcher和ClassFilter,可以通過名字很清楚的理解, MethodMatcher是用來檢查目標類的方法是否可以被應用此通知,而ClassFilter是用來檢查Pointcut是否應該應用到目標類上。
引入(Introduction): 添加方法或欄位到被通知的類。 Spring允許引入新的介面到任何被通知的對象。例如,你可以使用一個引入使任何對象實現 IsModified介面,來簡化緩衝。Spring中要使用Introduction, 可有通過DelegatingIntroductionInterceptor來實現通知,通過DefaultIntroductionAdvisor來配置Advice和代理類要實現的介面。
目標對象(Target Object): 包含連接點的對象。也被稱作被通知或被代理對象。POJO
AOP代理(AOP Proxy): AOP架構建立的對象,包含通知。 在Spring中,AOP代理可以是JDK動態代理或者CGLIB代理。
織入(Weaving): 組裝方面來建立一個被通知對象。這可以在編譯時間完成(例如使用AspectJ編譯器),也可以在運行時完成。Spring和其他純Java AOP架構一樣,在運行時完成織入
六、AOP通知類型
前置通知(before advice):在切入點之前執行。
後置通知(after returning advice):在切入點執行完成後,執行通知。
環繞通知(around advice):包圍切入點,調用方法前後完成自訂行為。
異常通知(after throwing advice):在切入點拋出異常後,執行通知。
七、Spring啟動過程
通過對ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");這條語句的跟蹤,最重要的是調用了AbstractApplicationContext類的refresh()方法。refresh()方法有以下幾個步驟:
1.第一個方法是prepareRefresh(),prepareRefresh()只包含了兩個方法,這兩個方法載入了伺服器的一些資訊和判斷必填項是否都完整。第二個方法:ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();refreshBeanFactory()這個方法,如果容器現在已經啟動,而我們要新啟動一個容器,那麼首要的就是銷毀以前的容器,清空容器和容器中註冊了的bean。建立容器物件DefaultListableBeanFactory beanFactory = createBeanFactory();建立一個DefaultListableBeanFactory.注意在這裡面有一個方法getInternalParentBeanFactory(),這個方法獲得並設定了父容器。當然在spring啟動的過程中這個父容器是空的。
2.判斷bean是否允許覆蓋,bean是否允許循環參考,把java註解和spring標準註解都放到了容器裡面來。
3.要載入bean loadBeanDefinitions(beanFactory)。實現了該方法的類一共有三個1、XmlWebApplicationContext 2、AbstractXmlApplicationContext 3、AnnotationConfigWebApplicationContext.在項目啟動的過程中XmlWebApplicationContext真正的被執行。接下來的bean載入過程主要涉及到的就是設定檔解析。設定檔解析大體步驟1、根據命名空間將標籤名稱和解析類放置到map中2、讀取設定檔的過程中遇到標籤就將找到解析類去解析。
八、如何感知到Spring容器啟動成功這件事情?
spring提供了事件監聽器的處理機制,spring提供了內建的幾類的事件:
ContextClosedEvent 當使用ConfigurableApplicationContext介面的close()方法關閉ApplicationContext容器時觸發該事件。
ContextRefreshedEvent ApplicationContext容器初始化或者重新整理時觸發該事件。
ContextStartedEvent 當使用ConfigurableApplicationContext介面的start()方法啟動ApplicationContext容器時觸發該事件。
ContextStoppedEvent 當使用ConfigurableApplicationContext介面的stop()方法停止ApplicationContext容器時觸發該事件。
RequestHandleEvent。
在spring容器啟動完成後會觸發ContextRefreshedEvent事件,在spring容器啟動過程中調用AbstractApplicationContext的refresh()方法,其中調用了finishRefresh()用來發布這個事件。
ApplicationEventMulticaster在接收到ApplicationEvent事件之後,通過multicastEvent方法,通知所有的觀察者ApplicationListener。
比如hsf中通過建立一個ContextRefreshedEvent,ContextClosedEvent事件監聽器,在spring容器啟動完成後和容器關閉時,做一些處理動作。
九、FactoryBean與BeanFactory
Spring中有兩種類型的Bean,一種是普通Bean,另一種是工廠Bean,即FactoryBean,這兩種Bean都被容器管理,但工廠Bean跟普通Bean不同,其返回的對象不是指定類的一個執行個體,其返回的是該FactoryBean的getObject方法所返回的對象。
BeanFactory是IoC容器的核心介面。它的職責包括:執行個體化、定位、配置應用程式中的對象及建立這些對象間的依賴。從本質上講,BeanFactory僅僅只是一個維護bean定義以及相互依賴關係的進階工廠介面。通過BeanFactory我們可以訪問bean定義。
十、如何在Bean初始化前後做一些事情
首先看下AbstractAutowireCapableBeanFactory的createBean方法:(刪去些佔地方的try catch)。
protected Object createBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) throws BeanCreationException { resolveBeanClass(mbd, beanName); /1解析Bean的class mbd.prepareMethodOverrides(); //2 方法注入準備 Object bean = resolveBeforeInstantiation(beanName, mbd); //3 第一個BeanPostProcessor擴充點 if (bean != null) { //4 如果3處的擴充點返回的bean不為空白,直接返回該bean,後續流程不需要執行 return bean; } Object beanInstance = doCreateBean(beanName, mbd, args); //5 執行spring的建立bean執行個體的流程啦 return beanInstance;}
接著:
protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) { Object bean = null; if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) { // Make sure bean class is actually resolved at this point. if (mbd.hasBeanClass() && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { //3.1、執行InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation回調方法 bean = applyBeanPostProcessorsBeforeInstantiation(mbd.getBeanClass(), beanName); if (bean != null) { //3.2、執行InstantiationAwareBeanPostProcessor的postProcessAfterInitialization回調方法 bean = applyBeanPostProcessorsAfterInitialization(bean, beanName); } } mbd.beforeInstantiationResolved = (bean != null); } return bean;}
接著:
protected Object applyBeanPostProcessorsBeforeInstantiation(Class beanClass, String beanName) throws BeansException { for (Iterator it = getBeanPostProcessors().iterator(); it.hasNext();) { BeanPostProcessor beanProcessor = (BeanPostProcessor) it.next(); if (beanProcessor instanceof InstantiationAwareBeanPostProcessor) { InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) beanProcessor; Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName); if (result != null) { return result; } } } return null; }
接著:
public interface BeanPostProcessor { Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException; Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;}
通過代碼我們可以看到BeanPostProcessor,可以在spring容器執行個體化bean之後,在執行bean的初始化方法前後,添加一些自己的處理邏輯。
十、如何在bean銷毀的時候做一些事情?
有兩種方法:A.利用destroy-method配置 B.實現DisposableBean介面。實現DisposableBean介面,在destroy()方法裡做一些操作。或者對設定檔加入destroy-method屬性指定方法;如果兩者同時出現,先執行DisposableBean介面的destroy()方法,然後再執行destroy-method屬性指定方法。
十一、Bean的生命週期
什麼時候初始化Bean?當scope=singleton,即預設情況,會在容器初始化時執行個體化。但我們可以指定Bean節點的lazy-init=”true”來延遲初始化bean,這時候,只有第一次擷取bean才會初始化bean,即第一次請求該bean時才初始化。如下配置所示:
<bean id=”xxx” class=”examples.test.OrderServiceBean” lazy-init=”true” />
如果想對所有bean都應用延遲初始化,可以在根節點beans設定default-lazy-init=”true”,如下所示:
<beans default-lazy-init=”true” …>
當scope=prototype時,也會延遲初始化bean,即第一次請求該bean時才初始化(如調用getBean()方法時)。
Bean的生命週期,構造器、init方法、擷取bean後的操作、destroy方法(ctx.close時執行)。注意:如果bean的scope設為prototype時,當ctx.close時,destroy方法不會被調用。原因:對於prototype範圍的bean,有一點非常重要,那就是Spring不能對一個prototype bean的整個生命週期負責:容器在初始化、配置、裝飾或者是裝配完一個prototype執行個體後,將它交給用戶端,隨後就對該prototype執行個體不聞不問了。不管何種範圍,容器都會調用所有對象的初始化生命週期回調方法。但對prototype而言,任何配置好的析構生命週期回調方法都將不會 被調用。清除prototype範圍的對象並釋放任何prototype bean所持有的昂貴資源,都是用戶端代碼的職責。(讓Spring容器釋放被prototype範圍bean佔用資源的一種可行方式是,通過使用bean的後置處理器,該處理器持有要被清除的bean的引用。)
在spring容器啟動時,refresh方法中,調用invokeBeanFactoryPostProcessors(beanFactory);
在 invokeBeanFactoryPostProcessors 這個方法裡,擷取實現 BeanFactoryPostProcessor 介面的 bean ,將其排序後,依次調用invokeBeanFactoryPostProcessors。
最終調用了postProcessor.postProcessBeanFactory(beanFactory)方法,該方法會對bean做初始的處理,具體處理方式與子類具體實現有關。
。