深入瞭解Spring的IOC(DI)、AOP和SpringBoot自動配置原理

1、Spring的IOC和DI
(1)IOC
IOC全稱是Inverse of Control,控制反轉的意思,就是把對象的創建和生命週期的管理交給spring,這裏提到對象的創建,那spring創建對象的方法有幾種?
四種:
第一種是針對只有無參構造的類而言,直接利用反射原理創建對象,這種方式創建的對象如何放入spring容器中?在xml中寫一個bean標籤對,裏面寫id和class屬性;
第二種是針對有參構造的類或者抽象類而言(當然也可以用於只有無參構造的類),此時需要自己創建一個靜態工廠類,裏面定義一個getInstance()靜態方法,那這種方式創建的對象又如何放入spring容器中?在xml中寫一個bean標籤對,裏面除了id和class屬性,還要寫一個factory屬性,這個屬性寫的是靜態工廠方法(注意xml裏寫兩個bean,因爲除了工廠類,還需要原材料類)(靜態工廠方法在xml中的確只寫一個!):
第三種和第二種使用場景一樣,也需要創建一個工廠類和裏面的工廠方法,但只是這裏不用靜態方法而是實例方法,從形式上就是少一個static,其他一樣,但這種方式創建的對象放到spring容器中的xml裏寫法不一樣,需要注意的是靜態工廠方法只有一個bean,實例工廠卻是兩個,第一個bean是寫工廠類的id和class,第二個bean是寫原材料類的id、factory-bean(其實就是第一個bean的id值,這個取代class)和factory-method。
第四種是採用Spring內置的工廠接口完成。
隨着需求的不斷增加,bean標籤對裏的屬性只有上面的id、class、factory-method、factory-bean肯定不夠,所以出現了scope、lazy-init、init-method和destroy-method:
1)scope:定義單例還是多例
如果不寫,默認是單例,即對象只創建一次後就放在spring容器中,然後每次getbean都是從容器中拿這個對象,不會取一次創建一次,而如果加了這個屬性並且值設置爲prototype,則說明開啓多例模式,那就是每取一次創建一次。
2)lazy-init:定義是否開啓懶加載機制
如果項目需要在xml中要寫很多的bean而且是在單例模式下,這樣解析xml後就會對裏面所有bean進行創建對象,這樣會耗費很多時間和內存,如果都創建了很多還用不到,那就更是大大浪費了,所以引申出一個屬性:懶加載,即一開始解析xml的時候有這個屬性的bean就先不創建,只有主程序中第一次用到了才創建,當然這個屬性也可以設置爲全局,即所有bean都滿足,如果全局和局部設置不同而且都有哪個優先?局部優先。
注:爲什麼這裏強調“而且是在單例模式下”?多例模式下不需要懶加載機制,相同點都是用的時候再創建對象,但不同點是單例懶加載機制還是隻有第一次才創建對象。
3)init-method和destroy-method
這兩個屬性體現了spring對對象的生命週期管理,這個類比前面學過的servlet的生命週期,即spring根據id+class或id+class+factory-method或id+class+id+factory-bean+factory-method這三種方法創建對象後(可以設置多例模式、懶加載機制),先調用init方法進行初始化操作,再調用對象裏的普通方法,最後在對象銷燬之前才調用destroy方法進行銷燬操作。
:使用spring不一定是web工程還可以是普通工程;還有就是一定要導spring的包,這樣才能完成spring自動解析xml文件等功能;最後要注意寫一個xml模板,配置文件和註解的模板形式不一樣。
上面提到如果xml中bean太多,那Spring解析這個xml就要創建這麼多bean對象很浪費時間和空間,所以爲單例模式弄了一個懶加載機制,這是爲Spring考慮,那我們能不能爲自己考慮下,畢竟寫這麼多bean太累,而且看起來太麻煩了,爲了解決這個需求,出現了IOC的註解@Component:
這個註解只要寫在我們要創建對象的類的上面即可,然後再在xml文件中寫一個包掃描的標籤對就可以自動找到這個類,並對其創建對象然後放到Spring的map容器中以id爲鍵、class爲值保存。
問題是這個包掃描的標籤對怎麼寫?
包掃描標籤對的名字是context:component-scan,怎麼記這個名字?context是上下文、環境的意思,component就是IOC的註解名,scan是掃描,即掃描有Component這個註解名的環境,其實就是掃描包,這個標籤對裏的屬性也是包名即base-package,主程序啓動以後解析xml文件,xml文件就會去指定的包名裏掃描所有帶有Component註解的類進行創建對象並放入內置map容器中。
:雖然註解方便,但因爲是封裝的,所以效率其實沒有配置文件高,類比java效率沒有c高一樣,因爲c更偏底層。
IOC的註解名其實和bean標籤對中的屬性名息息相關,只是Component這個註解和屬性名無關,但多例模式的註解名還是Scope,即@Scope(value=’prototype’);如果是開啓懶加載機制,則爲@Lazy;初始化方法和銷燬方法分別是@PostConstruct和@PreDestroy,除了這兩個註解寫在方法上,其他都在類上。
還需要注意的是@Component這個註解是在所有類通用的,但爲了更好地在Web層、Service層和Dao層區分,所以在Web層(包)的類用@Controller表示,Service層(包)的類用@Service表示,Dao層(包)的類用@Repository表示。
使用@Repository的時候要注意一種情況:如果Dao層寫了一個接口userDao,並有兩個個實現類實現這個接口,並且這些實現類上面都有@Repository註解,然後此時在Service層裏的userService的實現類調用了userDao接口,而且用@Autowire自動注入userDao接口的實現類,但此時注入的是userDao的哪個實現類?所以必須在@Repository的後面寫(“userDao”),這樣@Autowire的就會自動找到這裏,如下所示:
在這裏插入圖片描述
在這裏插入圖片描述
除了上面這種方法還可以在@Autowired下面再加一個註解爲@Qualifier(“MySqlUserDao”)。
綜上所述,Spring的IOC實現原理:
當我們在啓動類裏初始化一個Spring容器的時候,Spring會自動去解析xml文件,當解析到xml裏的bean標籤對的時候,會根據標籤中的class屬性即類的全路徑名,然後根據反射方法創建給類的實例對象(具體是Class.forName(全路徑名)這種反射方法,還有兩種是對象名.getClass()和類名.class,然後是再調用newInstance()方法即可創建對象,這是默認的無參構造創建對象的方式),創建好的對象會放到Spring容器內置的Map中,鍵是Bean的id,值是該對象,然後我們需要的時候用getBean()方法就可以拿到了。

(2)DI
DI全稱是Dependency Injection,依賴注入的意思,其實就是在Spring創建的對象過程中給對象的屬性傳入屬性值,首先回顧下前面說的因爲java面向對象的封裝特性,所以javaBean裏的屬性一般私有化,那怎麼給私有化屬性傳入屬性值?兩種方法,一種是寫set方法,對應也有get方法;另一種是寫有參構造方法。
那Spring怎麼實現這兩種方法?
1)set方法:對應properties標籤對
這個標籤對寫在bean標籤對裏,如果把bean標籤對類比爲html中的form標籤對,那properties標籤對就是input標籤對(表、行和列標籤先不管),但因爲屬性值有不同的數據類型,所以需要具體問題具體分析,這裏大致可以分爲Spring內置的和非內置的:
A、Spring內置的
Spring內置的數據類型如果是int、String則比較簡單,直接寫name和vale屬性即可,但如果是List 、Set、Map、Properties等,則會比較麻煩,首先還是寫properties標籤對,雖然properties標籤對裏的屬性還是name和value,但value已經獨立,可能因爲它太多管不住了,以List爲例,需要寫多個value標籤對,屬性值寫在value標籤對裏面,還需要注意的是這麼多value標籤對前後必須寫上list標籤對包裹起來,如下所示:

         <property name="jobs">
                        <list>
                                <value>上單</value>
                                <value>打野</value>
                                <value>輔助</value>
                                <value>中單</value>
                        </list>
        </property>

如果是Set類型,只需要把上面的List改成Set即可,Map和Properties在value標籤對這個部分會有點不同,這裏不聊,遇到可以查看資料。
B、Spring非內置的
Spring非內置的數據類型其實就是這個類中用到的其他類的對象(這些都是自定義的),比如Web層的Controller類的方法調用Service層的對象,那就要把Service層的對象作爲屬性值注入到Controller類中,怎麼在xml中寫?還是寫一個properties標籤對,裏面的屬性是name和ref bean(bean可以省略),ref的值對應下面新寫的bean的id屬性,這個新寫的bean的id和class對應上面提到的其他類。
Spring非內置的數據類型除了寫properties標籤對和name、ref屬性之外還可以直接在bean中用autowire屬性,這個屬性是自動裝配的意思,屬性值可以寫成byName表示根據其他bean的id注入,和懶加載一樣,這個也可以全局裝配,而且全局和局部都存在的情況下優先局部,但我測試了下如果除了非內置數據類型外還有內置數據類型,則非內置數據類型不能用自動裝配這種方法。
2)有參構造方法:對應constructor-arg標籤對。
注意這個標籤對是和properties標籤對同級別(DI級別,name屬性是DI的),而不是bean標籤對(IOC級別,id屬性纔是IOC的),bean標籤對是共有了,除非改成註解形式,所以和properties標籤對相比,如果是Spring非內置的數據類型中的int和String則還是name+value屬性;如果是Spring內置的數據類型則還是用name+ref屬性,ref和後面bean的id對應,但需要注意的是這裏還要加上index,這個值從0開始算,分別對應有參構造方法裏參數的位置順序。
上面聊了IOC的註解形式,這裏也聊下DI的註解形式:@Value和@Autowire(註解的首字母大寫),前面提到Spring內置的數據類型是在properties標籤對裏寫name+value,如果改成註解形式properties標籤對肯定不要了,因爲前面IOC的註解裏也把bean標籤對去掉了,但也沒保留id或者class作爲註解名稱,而是用Component作爲註解名,而DI不一樣,Spring內置的數據類型用原來的value屬性作爲註解名即@Value;如果是Spring非內置的則用自動裝配裏的Autowire屬性名作爲註解名。
這裏需要特別指出的是Spring內置的數據類型有簡單和複雜的,簡單的是非集合類型,比如int和String,這兩種數據類型的賦值是直接在@Value後面跟上()並填上相應數據即可,,但都需要加上雙引號;如果是複雜的集合類型則比較麻煩,還是需要在配置文件中寫很多。
:如果在很多類中的屬性上面一個個賦予具體的值,則比較麻煩,所以考慮用properties配置文件,如果用這個配置文件,則首先寫一個這樣的文件,再在xml文件中寫context:property-placeholder標籤對來引入這個文件,最後再在不同類的一個個屬性上寫上@Value(”${屬性名}”)即可,不需要具體的值,注意要加上雙引號。

(1)Spring Bean的生命週期
首先是Spring容器實例化Bean對象(4種方法:默認反射、靜態工廠、實例工廠和Spring工廠),再設置Bean屬性(兩種方法:set方法和有參構造),如果接下來想注入Bean對容器基礎設施層面的依賴,可以通過各種Aware接口聲明其依賴關係,然後調用init方法執行初始化操作,接着就可以調用Bean中的所有方法,最後Bean銷燬之前,Spring容器會調用destory方法來執行銷燬操作。
(2)Spring Bean的作用域
Spring Bean的作用域一共有五種:
A、Singleton:這個是Spring默認的作用域,指的是Spring Bean是單例的;
B、prototype:指的是Spring Bean是多例的,即每次getBean()方法,就會在Spring容器中創建一次Bean的實例對象;
C、request:指的是同一個request範圍內使用同一個Bean實例對象;
D、session:指的是同一個Session範圍內使用同一個Bean實例對象;
E、GlobalSession:指的是所有Session使用同一個Bean實例對象。

2、Spring的AOP
AOP全稱是Aspect Oriented Programming,意思是方面 面向 編程,組合起來就是面向切面編程。
在講面向切面編程之前先學習它的基礎代理設計模式,首先要知道爲什麼引出代理設計模式?這就不得不說前面講的IOC和DI的作用了,Spring的IOC和DI讓我們不再自己new對象了,而是從Spring的內置map容器中拿,而且類與類之間對象的調用也不需要new,而是自動裝配,此時再結合接口,就可以實現軟件設計中低耦合的目標,低耦合就是讓類與類之間的關聯性儘量低,或者理解爲是否非它莫屬,如果不是就是低耦合。
軟件設計的另一個目標:高內聚,這個是IOC和DI無法實現的,所謂高內聚,應該是減少方法中無關的功能,比如每個方法開始前開啓事務,結束後關閉事務,顯然開啓事務和關閉事務這兩個和當前類無關,所以可以把開啓事務和關閉事務這兩個功能交給代理者幫忙完成,代理有靜態代理和動態代理這兩種,應該選哪個?選擇動態代理,因爲靜態代理解決了相關性但沒有解決重複性,這也就是AOP裏用動態代理的原因。
那Spring如何實現動態代理?
兩種方式實現,一種是JDK自帶的Proxy類實現;另一種是採用第三方包cglib。
Proxy類如何實現動態代理?調用Proxy類的newProxyInstance()方法就可以創建一個代理者對象,這個方法的方法參數有三個:被代理者的類加載器、被代理者實現的所有接口和InvocationHandler接口的一個實現類並在類中重寫invoke()方法實現動態調用被代理者的方法,可以在被代理者的方法調用前後進行一些其他操作,比如事務、日誌等。
Cglib如何實現動態代理?首先創建一個增強器,再把被代理者當做父類繼承,至於父類實現的接口可以選擇性實現,然後設置回調方法對被代理者的方法進行增強。
這兩種動態代理方法的區別在哪?Proxy類實現動態代理的前提是實現被代理者實現的所有接口,而Cglib不一定要實現接口,只需要繼承被代理者即可,所有Cglib的應用場景更廣泛,Spring的AOP默認先用Proxy類實現,如果不能實現再去用Cglib,當然我們可以自己設置爲Cglib使其強制都用Cglib。
SpringAOP還有很多專業術語
(1)切面:就是處理類;
(2)通知:就是處理類的方法,具體分爲前置通知、環繞通知、後置通知、異常通知和最
終通知這五種。
(3)連接點:目標層的方法;
(4)切入點:根據within(具體到類名)或者Executor(具體到方法名)這些切入點規則選
出來的連接點;
(5)織入:把切面裏的通知應用到目標對象從而創建新的代理對象過程;
所以SpringAOP的具體原理是是什麼?(盲點:讓動態代理和專業術語聯繫起來)
Spring會在用戶獲取對象時,生成目標對象的代理對象,然後根據切入點規則,匹配用戶連接點,得到切入點,當切入點被調用的時候,不會直接去找目標對象,而是通過代理對象攔截之後由切面類中指定的通知執行來對目標對象中的方法進行增強。
注:Spring事務:
A、隔離級別:
1)ISOLATION_DEFAULT:使用數據庫默認的事務隔離級別
2)ISOLATION_READ_UNCOMMITTED:最低級別,允許讀取到其他事務未提交的數據,會產生髒讀、 不可重複讀和幻讀;
3)ISOLATION_READ_COMMITTED:只能讀取到其他事務已提交的數據,可以防止髒讀,但不能防止可重複讀和幻讀;
4)ISOLATION_REPEATABLE_READ:可以防止髒讀和不可重複讀,但不能防止幻讀;
5)ISOLATION_SERIALIZABLE:最高級別,事務處理爲串行且阻塞的,所以能避免所有情況。
對於不同數據庫,默認事務隔離級別是不一樣的,MySQL默認是ISOLATION_REPEATABLE_READ這種,而Oracle因爲沒有ISOLATION_REPEATABLE_READ這種,所以採用ISOLATION_READ_COMMITTED。
爲什麼不採用最高級別和最低級別?因爲二者分別是安全和效率的極端,所以最好是用平衡了效率和安全的ISOLATION_REPEATABLE_READ或者ISOLATION_READ_COMMITTED。
B、傳播機制:
1)支持當前事務,如果沒有當前事務,就新建一個事務,這是最常見的選擇;
2)支持當前事務,如果當前沒有事務,就以非事務方式執行;
3)支持當前事務,如果當前沒有事務,就拋出異常;
4)支持當前事務,如果當前事務存在,則執行一個嵌套事務,如果當前沒有事務,則新建一個事務;
5)新建事務,如果當前存在事務,則把當前事務掛起;
6)以非事務方式執行,如果當前存在事務,則把當前事務掛起;
7)以非事務方式執行,如果當前存在事務,則拋出異常。

3、SpringBoot自動配置原理
SpringBoot的優點有自動配置、內嵌Tomcat、簡化Maven依賴等,以前SSM框架需要寫很多的xml文件,而SpringBoot只需要寫application.properties或者yml即可,甚至不寫用默認0配置也可以實現SSM框架。那SpringBoot自動配置原理是什麼?
SpringBoot核心註解@SpringBootApplication是由
@SpringBootConfiguration+@ComponentScan+@EnableAutoConfiguration組合而成,第一個註解啓動Spring的xml文件;第二個註解是以前xml文件中的包掃描;第三個註解是關鍵,如果想深入理解這個註解,可以去看maven dependencies裏的源碼,點擊裏面的spring-boot-autoconfigure-版本號.jar這個jar包,然後找到裏面的EnableAutoConfiguration.class,裏面有個@import,導入select結尾的類,這個類裏面有個方法是去掃描該jar包裏META-INF中的spring.factories,這個文件裏聲明瞭哪些自動配置,自動配置是有條件的,一般是指定的類或者bean或者屬性是否存在纔會自動配置,所以這也是爲什麼pom.xml中添加依賴Spring就會自動配置的原因,關鍵就是條件註解的出現。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章