“Spring”——每一個Javaer開發者都繞不開的字眼,從21世紀第一個十年國內異常活躍的SSH框架,到現在以Spring Boot作為入口粘合了各種應用。Spring現在已經完成了從web入口到微服務架構再到數據處理整個生態,看著現在https://spring.io/projects上長長的 ...
“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” />
其實註意一下四點即可:
- id和name均可以標識一個bean,但是id必須是全局一對一的,而一個bean可以用多個name,用,號分割。
- 如果不給bean制定id,那麼容器會為他自動生成一個唯一的序列號。
- 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