「Spring」——每個Javaer開發者都繞不開的字眼,從21世紀第一個十年國內異常活躍的SSH框架,到如今以Spring Boot做爲入口粘合了各類應用。Spring如今已經完成了從web入口到微服務架構再到數據處理整個生態,看着如今https://spring.io/projects上長長的項目清單,一臉懵逼的自問到這些究竟是啥?能夠幹嗎?html
早期的Spring並無這麼多亮瞎眼的項目,僅僅是圍繞着core、context、beans以及MVC提供了一個簡單好用搭建網站級應用的工具。那個時候徹底是一個與J2EE的繁雜多樣對抗簡單便捷的小清新。Srping之父Rod的一本《J2EE Development without EJB》宣告J2EE那麼名堂徹底沒多大用處。通過這麼多年的發展,事實也證實除了Servlet、JDBC以及JSP彷佛其餘東西無關緊要。後來Vertx、WebFlux等Reactive機制框架的出現,以及先後端分離開發的盛行,彷佛Servlet也無關緊要了、jsp也快消失了。因此如今Oracle乾脆把J2EE這個燙手山芋直接丟給開源社區了。java
Rod的輪子理論造就了Spring的2大核心概念——IoC(Inversion of Control)和beans。Spring IoC和Beans的概念度娘、谷哥一搜一大把,在此就不重複介紹了。我的認爲IoC和Beans最基本的實現思想來自於設計模式的幾大原則,它之因此這麼好用而且深刻人心就是體現了設計模式的精髓。mysql
依賴倒轉原則:Spring的介紹Framework文檔的開篇就提到反向依賴注入(DI——dependency injection ),其目標是讓調用者不要主動去使用被調用者,而是讓被調用者向調用者提供服務。IoC和beans的配合完美實現了這個過程,一個@component註解添加一個bean到Ioc容器,一個@autowired註解Ioc容器會找到對應的類注入進來。web
接口隔離原則:Ioc不只僅根據class類型注入bean,他還會根據接口類型自動裝配注入一個bean。spring
里氏代換原則:在接口隔離的原則的基礎上咱們能夠利用XML配置文件來制定裝配的服務。例如javax.sql.DataSource是Java裏提供數據庫連接服務的接口,世面上有各類各樣開源或閉源的工具實現了DataSource接口,例如c3p0和druid。咱們想要切換他們僅僅須要像下面這樣添加或刪除一個bean(固然先要引入Jar包):sql
<!-- 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框架自己那麼多構建類都是按照這個原則開發的——新功能用新的類實現,而非增長原有方法。後端
如今咱們知道Spring的2大核心是IoC和Beans。IoC字面翻譯叫「控制反轉」,這個「反轉」過程實現的思想其實蠻簡單的:就是先有一個容器(container),咱們把實現各類功能的bean(一個類的實例)一股腦向容器裏面扔,至於最後這些bean被誰用了經過配置和註解來肯定。設計模式
上面提到了配置,在2.5版本以前配置只能經過XML文件實現,以後引入了annotation配置的方式,而後3.x版本以後能夠徹底使用Java代碼來實現配置而無需XML文件。配置文件的格式和做用其實也不復雜,就是告訴容器我要扔進去什麼bean。扔進去的bean固然須要初始化一些數據了,丟一個光禿禿沒有任何數據的實例到容器中貌似也沒多大用處,因此XML文件中就提供了一些標籤來標記如何初始化數據:websocket
<?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」 />
其實注意一下四點便可:
我的感受使用spring到如今name出現場景並很少,也不多看到哪一個開源項目經過name的方式向外暴露服務。
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直譯過來叫範圍、界限、廣度。不過按照字面意思理解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>