Spring核心——設計模式與IoC

来源:https://www.cnblogs.com/AIPAOJIAO/archive/2018/07/20/9343621.html
-Advertisement-
Play Games

“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” />

其實註意一下四點即可:

  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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 迪米特法則:也叫最少知識原則,如果兩個類不必彼此直接通信,那這兩個類就不應該發生直接的相互作用。如果其中一個類需要調用另一個類的某一個方法的話,可以通過第三者轉發這個調用。 迪米特法則首先強調的前提是在類的結構設計上,每一個類都儘量降低成員的訪問許可權,也就是說一個類包裝好自己的private狀態,不 ...
  • 首先我們要明白一點,我們為什麼要使用鏈路跟蹤? 當我們微服務之間調用的時候可能會出錯,但是我們不知道是哪個服務的問題,這時候就可以通過日誌鏈路跟蹤發現哪個服務出錯。 它還有一個好處:當我們在企業中,可能每個人都負責一個服務,我們可以通過日誌來檢查自己所負責的服務不會出錯,當調用其它服務時,這時候出現 ...
  • 列表與元組、字典 1.列表list:["ele1","ele2","ele3","ele0"] 列表是一組任意類型的值,按照一定順序組合而成的;通過偏移存取;可變長度、異構以及任意嵌套;可變的序列;對象引用的數組 2.元組tuple:("alex","韓順平","金雲龍") 只讀的列表,兩個方法in ...
  • 後臺添加管理員用戶使用SignupForm類實現 步驟一、複製一份前臺frontend/models/SignupForm.php 到後臺模型文件夾中 backend/models/SignupForm.php 步驟二、明確需要修改的文件為:新的SignupForm類,AdminuserContro... ...
  • 本文內容: 什麼是JavaBean JavaBean的使用 BeanUitls 利用DBUtils從資料庫中自動載入數據到javabean對象中 首發日期:2018-07-21 什麼是JavaBean JavaBean是一種遵循開發規範的一種類。在JavaWeb開發中,經常用來存儲實體信息,比如用戶... ...
  • 這裡我用了兩個生產者和兩個消費者進行演示,如下圖(畫的不好看,湊活看看): 這裡我就只講下怎麼註冊到dashbord和相關的配置,提供者和消費者等代碼可以去下載查看: 1.hystrix的配置: 這裡我將熔斷器(或者稱為斷路器配置到了消費者端): 啟動類: pom.xml: StuConsumerA ...
  • 打開eclipse,在菜單欄上找到Window,點擊Window >Perspective >Customize Perspective...,會看到 彈出來的一個視窗,然後點擊最後一個Shortcuts,然後將Shortcut Categories下麵的所有選項都去掉,然後點擊 左邊的每一項,右邊 ...
  • 1、 集合的嵌套: 集合的用法其實和數組的用法有很多共同之處,在使用數組的時候,二維數組就是數組的嵌套; 那麼在集合之中是否也可以這樣呢? 當然也是可以的,例如對於最複雜的的map集合; map<string, map<string,student>>;這樣map中就嵌套了一個map集合; 其中對於 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...