Spring核心——設計模式與IoC


“Spring”——每一個Javaer開發者都繞不開的字眼,從21世紀第一個十年國內異常活躍的SSH框架,到現在以Spring Boot作為入口粘合了各種應用。Spring現在已經完成了從web入口到微服務架構再到數據處理整個生態,看着現在https://spring.io/projects上長長的項目清單,一臉懵逼的自問到這些到底是啥?可以干嘛?

一切都從IoC開始

早期的Spring並沒有這么多亮瞎眼的項目,僅僅是圍繞着core、context、beans以及MVC提供了一個簡單好用搭建網站級應用的工具。那個時候完全是一個與J2EE的繁雜多樣對抗簡單便捷的小清新。Srping之父Rod的一本《J2EE Development without EJB》宣告J2EE那么名堂完全沒多大用處。經過這么多年的發展,事實也證明除了Servlet、JDBC以及JSP似乎其他東西可有可無。后來Vertx、WebFlux等Reactive機制框架的出現,以及前后端分離開發的盛行,似乎Servlet也可有可無了、jsp也快消失了。所以現在Oracle干脆把J2EE這個燙手山芋直接丟給開源社區了。

Rod的輪子理論造就了Spring的2大核心概念——IoC(Inversion of Control)和beans。Spring IoC和Beans的概念度娘、谷哥一搜一大把,在此就不重復介紹了。個人認為IoC和Beans最基本的實現思想來自於設計模式的幾大原則,它之所以這么好用並且深入人心就是體現了設計模式的精髓。

依賴倒轉原則:Spring的介紹Framework文檔的開篇就提到反向依賴注入(DI——dependency injection ),其目標是讓調用者不要主動去使用被調用者,而是讓被調用者向調用者提供服務。IoC和beans的配合完美實現了這個過程,一個@component注解添加一個bean到Ioc容器,一個@autowired注解Ioc容器會找到對應的類注入進來。

接口隔離原則:Ioc不僅僅根據class類型注入bean,他還會根據接口類型自動裝配注入一個bean。

里氏代換原則:在接口隔離的原則的基礎上我們可以利用XML配置文件來制定裝配的服務。例如javax.sql.DataSource是Java里提供數據庫鏈接服務的接口,世面上有各種各樣開源或閉源的工具實現了DataSource接口,例如c3p0和druid。我們想要切換他們僅僅需要像下面這樣添加或刪除一個bean(當然先要引入Jar包):

<!-- c3p0 --> <bean id="ds" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/c3p0jdbctemplate"/> <property name="user" value="admin"/> <property name="password" value="123456"/> </bean> <!-- druid --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="url" value="jdbc:mysql://localhost:3306/c3p0jdbctemplate" /> <property name="username" value="admin"/> <property name="password" value="123456"/> </bean> 

聚合復用原則:SpringFramework號稱非侵入式框架,我們在使用的過程中也很少有繼承的情況,基本上所有的特性都是通過注解(Annotation)來實現,需要某一項服務也是將其注入后使用。雖然我們在開發的過程中為了實現一些高級功能會繼承重寫某些方法后,然后再將我們的新類添加到Ioc中,但是Spring本身並不太鼓勵這樣去實現。

除了前面4項原則,迪米特法則和開閉原則並沒有太直觀的體現。對於迪米特法則來說Ioc機制本身就實現了調用者與被調用者之間不會直接發生依賴關系(new創建)。而開閉原則,Spring框架本身那么多構建類都是按照這個原則開發的——新功能用新的類實現,而非增加原有方法。

Beans

配置

現在我們知道Spring的2大核心是IoC和Beans。IoC字面翻譯叫“控制反轉”,這個“反轉”過程實現的思想其實蠻簡單的:就是先有一個容器(container),我們把實現各種功能的bean(一個類的實例)一股腦向容器里面扔,至於最后這些bean被誰用了通過配置和注解來確定。

上面提到了配置,在2.5版本之前配置只能通過XML文件實現,之后引入了annotation配置的方式,然后3.x版本之后可以完全使用Java代碼來實現配置而無需XML文件。配置文件的格式和作用其實也不復雜,就是告訴容器我要扔進去什么bean。扔進去的bean當然需要初始化一些數據了,丟一個光禿禿沒有任何數據的實例到容器中貌似也沒多大用處,所以XML文件中就提供了一些標簽來標記如何初始化數據:

<?xml version="1.0" encoding="UTF-8"?> <!-- 省略xmlns --> <beans> <bean id="otherBean" class="myProject.OtherBean" /> <bean id="myBean" class="myProject.MyClass"> <!-- 通過setOtherBean方法設置OtherBean的實例 --> <property name="otherBean" ref="otherBean"/> <!-- 通過setValue方法設置數值 --> <property name="value" value="myValue"/> </bean> </beans>

參數

下面是Bean相關的參數,它們可以用XML<bean>標簽來配置,也可以用@bean傳遞一個參數來設定:

class 標記當前Bean加載的類
name Bean的別名和名稱。
scope Bean的范圍,默認是單例。
constructor 構造函數注入<constructor-arg />
properties 屬性注入<property>
autowiring auto注入模式
lazy 懶加載模式
initialization 制定初始化類時執行的方法
destruction 制定類銷毀時要執行的方法

Spring Framework的官網用了一個小節專門介紹bean的命名方式,既可以用id來標識,又可以用name來標識,第一次看還挺暈乎的。

<bean id="myBeanId" name=“myAlias1,myAlias2” />

其實注意一下四點即可:

  1. id和name均可以標識一個bean,但是id必須是全局一對一的,而一個bean可以用多個name,用,號分割。
  2. 如果不給bean制定id,那么容器會為他自動生成一個唯一的序列號。
  3. name可以配合<alias>標簽使用來轉換別名。

個人感覺使用spring到現在name出現場景並不多,也很少看到哪個開源項目通過name的方式向外暴露服務。

創建模式與Scope

Bean只是一個和IoC容器相對應的概念:IoC容器存放並管理bean,bean是IoC機制的最小工作單元。往后的AOP等功能都是建立在Bean的基礎上拓展開來的——要使用Spring這些功能首先得是一個Ioc容器中的Bean。Bean實際上就是一個Java類的實例,只不過實例化工作交給了Ioc容器而已。

Bean的實例化有3種方式——構造方法創建、靜態工廠、動態工廠。每一個Bean對應的Scope實際上就2個參數——singleton與prototype(實際上還有其他參數可以使用,這里說只有2個具體原因見后面Scope的說明)。

單例構造創建

90%的Bean都是直接通過這種方法方法來創建的。這也是我們最常見的配置方式:

<bean id="myBean" class="myProject.MyClass" />

當以上面這樣的方式配置一個bean時,Ioc容器會直接調用構造方法來創建一個類實例(當然在定義類時必須提供一個公開的構造方法)。由於默認情況下bean的scope參數是singleton,所以創建出來bean在不指定scope的狀態下都是一個單例。

某些時候我們會在類當中再用static 來設定一個嵌入類:

package myProject; class MyClass { static class MyNestClass{ public MyNestClass(){} } }

可以通過“$”符號關聯的方式創建這個Bean:

<bean id="myBean" class="myProject.MyClass$MyNestClass" />

靜態工廠創建

靜態工廠創建bean和靜態工廠模式的概念一樣,就是指定一個工廠類,然后通過一個靜態方法返回一個新的bean。

XML配置:

<bean id="myFactory" class="myProject.MyFactory" factory-method="createInstance"/>

工廠類:

class MyFactory { static class MyClass{}; private static MyClass myClass = new MyClass(); private MyFactory() {} public static MyClass createInstance() { return myClass; } }

動態工廠創建

動態工廠在設計模式上叫“抽象工廠”,spring官網將其自稱為實例工廠(instance factory)。這里叫“動態工廠”是想對他們加以區分。雖然“實例工廠”並不是教科書似的抽象工廠,但是目的就是實現工廠動態創建。動態工廠與靜態工廠最大的區別就是會先將工廠本身設置成一個bean(實例化),然后再通過這個工廠bean來創建“產品bean”。看下面的例子:

<bean id="myLocator" class="myProject.MyLocator"> <!-- 自身就是一個實例化的bean,可以設定任何bean的配置 --> </bean> <!-- 綁定bean與一個動態工廠 --> <bean id="instanceFactory" factory-bean="myLocator" factory-method="createInstance"/>
class MyFactory { static class MyClass{}; public MyClass createInstance() { return new MyClass(); } }

一個工廠可以同時用於創建多個bean方法:

<bean id="myLocator" class="myProject.MyFactory" /> <bean id="serverOne" factory-bean="myLocator" factory-method="createClassOne"/> <bean id="serverTwo" factory-bean="myLocator" factory-method="createClassTwo"/>
class MyFactory { static class MyServerOne{}; static class MyServerTwo{}; public MyServerOne createClassOne() { return new MyServerOne(); } public MyServerTwo createClassTwo() { return new MyServerTwo(); } }

為什么需要實例化方法

可能你會想,Spring實例化提供一個簡單的bean創建實例就好了,干嘛還要整靜態工廠、抽象工廠之類的東西?

實際上我個人認為Spring的架構大神們是想通過一套簡單的機制幫你實現設計模式中的所有創建模式——靜態工廠、抽象工廠、單例模式、建造者模式和原型模式。因為IoC的最大任務之一就是代替我們創建各種Bean(類實例),而類實例的創建無非就是這幾種創建模式。

這里僅僅介紹了2種工廠模式,下面將結合Bean的Scope屬性介紹其他模式的思路。

Scope

scope直譯過來叫范圍、界限、廣度。不過按照字面意思理解Bean的Scopd屬性肯定要跑偏的。Scope數據涉及2個層面的含義。

首先在實現層面,對於設計模式來說,Scope就只有2種模式——singleton模式和prototype模式。

其次在應用層面,除了上面2個,Scope還提供了request、session、application、websocket。從字面上看就知道實際上這些Scope參數僅僅是指定了一個bean的適用范圍。

以request為例,要啟用他需要保證應用的“上下文”是web模式,例如XmlWebApplicationContext,其他情況下會拋出異常。然后"scope=request"的工作方式就是外部發起一個請求后,web層(servlet)啟用一個線程來響應這個請求。到了業務層面我們需要指定一些bean來處理這個請求,當這些bean設定為request時,那么它僅僅用於這一次請求就拋棄。下一次請求出現時會創建一個新的實例。

所以不管是request、session、application還是websocket,實際上都是通過prototype模式創建的實例,也就是設計模式中的原型模式,雖然並不一定是教科書般的標准,但是在整個容器中他實現了原型的特性。

此外singleton模式和 Gang of Four (GoF)中定義的通過ClassLoad實現的單例模式也有很大的區別,但是對於Ioc容器而言,任何bean在一個容器中絕對是一個單例,現在所有的資源都通過容器來管理依賴關系,那么最終的效果也是一個單例。

建造者模式

到目前為止,還有一個創建模式未出場——建造者模式。建造者模式實際上就是通過一個標准的方法組裝一個復雜的對象。

標准的建造者模式先得有一個Director提供外部訪問接口,外部調用者要創建一個復雜對象時向接口傳遞指定參數,然后Director根據參數調用Builder提供的各種方法,這些方法再用concrete去構建最終的Product。

實際上把復雜對象創建的過程看成各個bean依賴構造的過程即可實現模式,例如:

<!-- cpu部件 --> <bean id="amdCpu" class="myProject.cpu.Amd"/> <bean id="intelCpu" class="myProject.cpu.Intel"/> <!-- 顯卡部件 --> <bean id="amdGraphics" class="myProject.graphics.Amd"/> <bean id="nvdiaGraphics" class="myProject.graphics.Nvdia"/> <!-- 組裝電腦1 --> <bean id="myComputer" class="myProject.computer.MyComputer"> <property name="cpu" ref="amdCpu"/> <property name="graphics" ref="nvdiaGraphics"/> </bean> <!-- 組裝電腦2 --> <bean id="yourComputer" class="myProject.computer.YourComputer"> <property name="cpu" ref="intelCpu"/> <property name="graphics" ref="amdGraphics"/> </bean>

 

原文連接:https://my.oschina.net/chkui/blog/1835837


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM