面試-軟件工程與設計模式相關,Spring簡介

面試-軟件工程與設計模式相關,Spring簡介

    • 1.編程思想
      • 1.1 面向過程編程
      • 1.2 面向對象編程
        • 1.2.1 面向對象編程三大特征
      • 1.3 面向切面編程
        • 1.3.1 原理
        • 1.3.2 大白話?
        • 1.3.3 名詞解釋
        • 1.3.4 實現
    • 2. 耦合與內聚
      • 2.1 耦合性
      • 2.2 內聚性
    • 3. 設計模式
      • 3.1 設計模型七大原則
        • 3.1.1 開放-封閉原則(開閉原則)
        • 3.1.2 單一職責原則
        • 3.1.3 依賴倒轉原則
        • 3.1.4 迪米特法則(最小知識原則)
        • 3.1.5 接口隔離原則
        • 3.1.6 合成/聚合復用原則
        • 3.1.7 里氏代換原則
      • 3.2 創建型模式
        • 3.2.1 工廠模式(Factory Pattern)
        • 3.2.2 抽象工廠模式(Abstract Factory Pattern)
        • 3.2.3 單例模式(Singleton Pattern)
        • 3.2.4 建造者模式(Builder Pattern)
        • 3.2.5 原型模式(Prototype Pattern)
      • 3.3 結構型模式
        • 3.3.1 適配器模式
        • 3.3.2 橋接模式
        • 3.3.3 過濾器模式
        • 3.3.4 組合模式
        • 3.3.5 裝飾器模式
        • 3.3.6 外觀模式
        • 3.3.7 享元模式
        • 3.3.8 代理模式
      • 3.4 行為性模式
        • 3.4.1 責任鏈模式
        • 3.4.2 命令模式
        • 3.4.3 解釋器模式
        • 3.4.4 迭代器模式
        • 3.4.5 中介者模式
        • 3.4.6 備忘錄模式
        • 3.4.7 觀察者模式
        • 3.4.8 狀態模式
        • 3.4.9 空對象模式
        • 3.4.10 策略模式
        • 3.4.11 模板模式
        • 3.4.12 訪問者模式
      • 3.5 J2EE模式
        • 3.5.1 MVC模式
        • 3.5.2 業務代碼模式
        • 3.5.3 組合實體模式
        • 3.5.4 數據訪問對象模式
        • 3.5.5 前端控制器模式
        • 3.5.6 攔截過濾器模式
    • 4. Spring介紹
    • 4.1 介紹
      • 4.2 常見注解
        • 4.2.1 Spring Bean注解
        • 4.2.2 自動裝配相關注解
        • 4.2.3 配置類注解
    • 4.3 原理
      • 4.3.1 IOC原理
      • 4.3.2 AOP原理
      • 4.3.3 MVC原理
      • 4.4 Spring啟動過程
        • 4.4.1 前置了解
        • 4.4.2 啟動過程
          • 1.初始化配置
          • 2. 組件掃描
          • 3.refresh
      • 4.5 Bean生命周期

1.編程思想

1.1 面向過程編程

面向過程編程(Procedural Programming)是一種以過程為中心的編程風格。在面向過程編程中,程序被分解為一系列的函數或過程,每個函數執行特定的任務。這種編程風格強調按照一定的順序執行一系列步驟來解決問題。函數可以接受輸入參數并返回輸出結果。數據通常是通過參數傳遞給函數,而函數之間的通信是通過函數調用來實現。

  • 直接、簡單,易于學習和理解,尤其適合初學者。
  • 可以提供較高的執行效率,因為它避免了面向對象編程中的一些額外開銷。
  • 更容易進行代碼調試和測試,因為函數之間的依賴關系相對簡單。
  • 性能相較于面向對象,較好。

  • 隨著項目規模的增大,代碼可能變得難以維護和擴展。函數之間的依賴關系可能變得復雜,代碼可讀性可能下降。
  • 全局變量的使用可能導致命名沖突和不可預測的副作用。
  • 面向過程編程缺乏一些面向對象編程的高級特性,如封裝、繼承和多態,這些特性可以提高代碼的組織性和重用性。

1.2 面向對象編程

面向對象(Object Oriented)是軟件開發方法,一種編程范式。對現實世界抽象,將屬性(數據)、行為(操作數據的函數或方法)封裝成類。

  • 提供了更好的組織性和可維護性,通過對象的封裝、繼承和多態等概念,使得程序更易于理解、擴展和重用。
  • 支持代碼的模塊化開發,不同對象之間的耦合性較低,使得團隊合作更加容易。
  • 可以通過繼承和多態實現代碼的靈活性和可擴展性。

  • 面向對象編程的學習曲線相對較陡峭,對初學者來說可能需要更多的時間和精力來理解和掌握相關概念。
  • 面向對象編程中的一些高級特性和設計模式可能會導致額外的開銷和復雜性。
  • 在某些情況下,面向對象編程可能會導致性能下降,尤其是當不恰當地使用繼承和多態時。
1.2.1 面向對象編程三大特征

封裝

通常認為封裝是把數據和操作數據的方法封裝起來,對數據的訪問只能通過已定義的接口。

繼承

繼承是從已有類得到繼承信息創建新類的過程。提供繼承信息的類被稱為父類(超類 /基類),得到繼承信息的被稱為子類(派生類)。

多態

分為編譯時多態(方法重載)和運行時多態(方法重寫)。要實現多態需要做兩件事:?是子類繼承父類并重寫父類中的方法,?是用父類型引用子類型對象,這樣同樣的引用調用同樣的方法就會根據子類對象的不同而表現出不同的行為。

方法重載 vs 方法重寫

方法重載(Overload)發生在編譯階段,表現為在同一類中編寫多個名稱相同但參數不同的方法。方法重寫(Override)發生在運行時,表現為父子類中同時編寫多個名稱、參數相同的方法。在運行時調用不同子類的不同的方法。

以JAVA語言為例,多態的實現利用運行時類型判定(RTTI)技術,它的作用是在我們不知道某個對象的確切的類型信息時(即某個對象是哪個類的實例),可以通過 RTTI 相關的機制幫助我們在編譯時獲取對象的類型信息。ps.此部分詳細解釋移步jvm.md。

面向過程編程 vs 面向對象編程

面向對象是?種基于面向過程的編程思想,是向現實世界模型的自然延伸,這是?種“ 萬物皆對象”的編程思想。由執行者變為指揮者,在現實?活中的任何物體都可以歸為?類事物,而每?個個體都是?類事物的實例。面向對象的編程是以對象為中心,以消息為驅動。

區別

  1. 組織方式:面向過程編程以函數為基本單位,按照一定的順序和步驟解決問題;而面向對象編程以對象為基本單位,通過對象的屬性和方法來解決問題,更強調數據和行為的封裝。

  2. 數據處理:面向過程編程通常以過程為中心,通過處理數據來達到目標;而面向對象編程則將數據和相關的操作封裝在對象中,通過對象之間的交互來處理數據。

  3. 代碼復用:面向過程編程側重于函數的重用,而面向對象編程側重于對象的重用,通過繼承、多態等機制實現代碼的復用和擴展。

  4. 抽象和封裝:面向對象編程具有更高的抽象能力,能夠將真實世界的概念映射到程序設計中,通過類和對象的封裝來表示問題領域的模型。

1.3 面向切面編程

面向切面編程(AOP)是一種軟件開發范式,旨在通過橫切關注點(cross-cutting concerns)的方式來解耦系統中的各個模塊。橫切關注點指的是那些不屬于業務邏輯本身,但是會影響多個模塊的代碼,比如日志記錄、事務管理、安全性等。

AOP的核心思想是將這些橫切關注點從業務邏輯中分離出來,形成獨立的切面(Aspect)。切面包含了橫切關注點的代碼,當程序執行到特定的點(切點)時,切面的代碼就會被執行,從而實現了與業務邏輯的解耦。

1.3.1 原理

AOP通過在程序執行的不同階段織入切面代碼來實現其功能。織入(weaving)是指將切面代碼與目標代碼合并的過程。

有兩種主要的織入方式:編譯時織入和運行時織入。編譯時織入是在程序編譯的時候將切面代碼織入目標代碼中,而運行時織入則是在程序運行的時候動態地將切面代碼織入目標代碼中。

在AOP中,常見的概念還包括切點(Pointcut)、通知(Advice)、連接點(Joinpoint)等。切點定義了在何處執行切面代碼,通知定義了切面代碼的具體行為,連接點則是在程序執行過程中能夠插入切面代碼的點。

1.3.2 大白話?

鑒于目前網上的文章都說得異常抽象,這里就用大白話過一遍。

意思就是說,有些時候我們希望在一組函數、方法或語句執行前都執行一段相同(相似)的代碼,例如日志代碼,鑒權代碼等等。該怎么辦呢?一個粗暴的方法就是在每次調用此函數/方法/語句之前都加上這一段代碼。那這樣會造成問題:模塊之間的解耦過高,如果在每次調用之前都加上一段代碼,那這段代碼(或封裝的模塊)和整體得業務代碼是高度耦合的(內容耦合),大大降低了工程的可維護性。而且如果在工程中期突然要求在某個函數/方法之前加入特定語句,那么這樣一處處地修改代碼異常繁瑣,也違反了開閉原則。

所以AOP即使為了解決此問題。將我們需要運行的代碼(切面 Aspect)動態的插入(切入)到指定的地方。 即在業務代碼中設置切點(Pointcut),當運行到這些切點時,我們對程序進行攔截,然后通過通知(Advice) 來執行需要運行的代碼。

1.3.3 名詞解釋

Joinpoint(連接點):指業務中被攔截的點。

Pointcut(切入點):指我們要對哪些 Joinpoint 進行攔截的定義。 連接點被增強了,它就變成切入點

提示:所有切入點都是連接點,但是,所有連接點不都是切入點。

Advice(翻譯–建議,理解為增強可能好一點):指攔截到 Joinpoint 之后所要做的事情就是建議。 這里建議分為:前置建議、后置建議、異常建議、最終建議、環繞建議。

Introduction(引進): 引進是一種特殊的建議在不修改類代碼的前提下, Introduction 可以在運行期為類動態地添加一些方法或 Field

Target(目標): 代理的目標對象。

Weaving(織入): 是指把建議應用到目標對象來創建新的代理對象的過程。 Spring 采用動態代理織入,而 AspectJ 采用編譯期織入和類裝載期織入

Proxy(代理): 一個類被 AOP 織入建議后,就產生一個結果代理類。

Aspect(切面): 是切入點和建議(引進)的結合。

1.3.4 實現

利用預編譯(編譯階段)和動態代理(運行階段)的方式實現。詳見下面章節有關代理模式(Proxy Pattern)的介紹。

2. 耦合與內聚

軟件工程追求模塊之間低耦合,模塊內部高內聚。

2.1 耦合性

耦合性是指軟件結構中模塊相互連接的緊密程度,是模塊間相互連接性的度量耦合強度的大小是由模塊間接口的復雜程度決定的。

具體從三個方面衡量:
① 方式——塊間聯系方式由“直接引用”或“過程語句調用”。
② 作用——塊間傳送的共用信息(參數)類型,可為“數據型”、“控制型”或“混合型” (數據/控制型)
③ 數量——塊間傳送的共用信息的數量。

在這里插入圖片描述

內容耦合

  1. 一個模塊直接訪問另一模塊的內部數據。

  2. 一個模塊不通過正常入口轉到另一模塊的內部。

  3. 一個模塊有多個入口。

  4. 兩個模塊有部分代碼重迭。

內容耦合在高級語言尤其是面向對象語言中是不允許出現的。

公共耦合

若干模塊訪問一個公共的數據環境(全局數據結構、共享的通信區、內存的公共覆蓋區等)。耦合的復雜程度隨耦合模塊的數量的增加而顯著增加。

公共耦合的兩種情況:

  1. 松散公共耦合: 模塊同時只對公共數據環境進行讀或寫一種操作。(一個模塊要么只讀公共數據區,要么只寫公共數據區。)

  2. 緊密公共耦合: 若干模塊對公共數據環境同時讀和寫操作,這種耦合使公共數據區的變化影響所有公共耦合模塊,嚴重影響模塊的可靠性和可適應,降低軟件的可讀性。這是一種強耦合方式。

一般來說,僅當模塊間共享的數據很多,且通過參數的傳遞很不方便時才使用公共耦合。

外部耦合

一組模塊都訪問同一全局簡單變量(而不是同一全局數據結構),而且不是通過參數表傳遞該全局變量的信息。

外部耦合 vs 公共耦合?外部耦合是共享一個簡單變量,而公共耦合是共享一個公共數據環境。

控制耦合

一個模塊傳遞給另一模塊的信息是用于控制該模塊內部邏輯的控制信號。顯然,對被控制模塊的任何修改,都會影響控制模塊。

如何改變?將控制信號轉到上層函數中處理,即定義兩個函數,一個計算平均分,一個計算最高分。然后在上層函數中按需調用不同函數。即可降低至數據耦合

標記耦合(特征耦合)

一個模塊傳送給另一個模塊的參數是一個復合的數據結構。模塊間共享了數據結構,如高級語言中的數組名、記錄名等,其實傳遞的是這些數據結構的地址。標記耦合會使某些本來無關的模塊產生相互依賴性,同時由于某些模塊包含了不需要的數據,也給糾錯帶來了麻煩。

改進:只傳該數據結構中所需要的字段,降級為數據耦合。

數據耦合

一個模塊傳送給另一個模塊的參數是一個單個的數據項或者單個數據項組成的數組。模塊間傳遞的是簡單的數據值,相當于高級語言中的值傳遞。

非直接耦合

兩個模塊間沒有直接的關系,它們分別從屬于不同模塊的控制與調用,它們之間不傳遞任何信息。這種耦合程度最弱,模塊的獨立性最高。

2.2 內聚性

內聚性表示一個模塊內部各個元素(數據、處理)之間聯系的緊密程度。顯然,塊內聯系愈緊,即內聚性愈高,模塊獨立性愈好。

在這里插入圖片描述

偶然內聚

又稱為巧合型,為了節約空間,將毫無關系( 或者聯系不多)的各元素放在一個模塊中。模塊元素關系松散,顯然不易理解、不易修改。

邏輯內聚

將幾個邏輯上相似的功能放在一個模塊中,使用時由調用模塊傳遞的參數確定執行的功能。由于要傳遞控制參數,所以影響了模塊的內聚性。如果二者之間是控制耦合,那么被調用模塊就是邏輯內聚型的模塊。

時間內聚

又稱為經典內聚。是把需要同時執行的成分放在一個模塊中。比如初始化、中止操作這一類內部結構比較簡單的模塊。由于判定較少,因此比邏輯內聚高,但是由于內含多個功能,修改和維護困難。

過程內聚

一個模塊內的處理元素是相關的,而且必須以特定的次序執行。

通信內聚

模塊中的成分引用共同的輸入數據,或者產生相同的輸出數據,則稱為是通信內聚。

在這里插入圖片描述

順序內聚

一個模塊內的處理元素都密切相關于同一功能,模塊中某個成分的輸出是另一成分的輸入。由于這類模塊是按數據執行順序,模塊的一部分依賴于另外一部分,因此具有較好的內聚性。

功能內聚

一個模塊包括而且僅包括完成某一具體功能所必須的所有成分。或者說,模塊的所有成分都是為完成該功能而協同工作、緊密聯系、不可分割的。

3. 設計模式

設計模式是一套被反復使用的、多數人知曉的、經過分類編目的、代碼設計經驗的總結。使用設計模式是為了重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。 毫無疑問,設計模式于己于他人于系統都是多贏的,設計模式使代碼編制真正工程化,設計模式是軟件工程的基石,如同大廈的一塊塊磚石一樣。項目中合理地運用設計模式可以完美地解決很多問題,每種模式在現實中都有相應的原理來與之對應,每種模式都描述了一個在我們周圍不斷重復發生的問題,以及該問題的核心解決方案,這也是設計模式能被廣泛應用的原因。

根據設計模式的參考書 Design Patterns - Elements of Reusable Object-Oriented Software(中文譯名:設計模式 - 可復用的面向對象軟件元素) 中所提到的,總共有 23 種設計模式。這些模式可以分為三大類:創建型模式(Creational Patterns)、結構型模式(Structural Patterns)、行為型模式(Behavioral Patterns)。當然,我們還會討論另一類設計模式:J2EE 設計模式。

3.1 設計模型七大原則

3.1.1 開放-封閉原則(開閉原則)

開閉原則(Open-Closed Principle, OCP):一個軟件實體應當對擴展開放,對修改關閉。即軟件實體應盡量在不修改原有代碼的情況下進行擴展。

注意事項

1.通過接口或者抽象類約束擴展,對擴展進行邊界限定,不允許出現在接口或抽象類中不存在的public方法。
2.參數類型、引用對象盡量使用接口或者抽象類,而不是實現類
3.抽象層盡量保持穩定,一旦確定不允許修改。

介紹

1)開閉原則(Open Closed Principle)是編程中最基礎、最重要的設計原則

2)一個軟件實體如類,模塊和函數應該對擴展開放(對提供方),對修改關閉(對使用方)。用抽象構建框架,用實現擴展細節。
3)當軟件需要變化時,盡量通過擴展軟件實體的行為來實現變化,而不是通過修改已有的代碼來實現變化。
4)編程中遵循其它原則,以及使用設計模式的目的就是遵循開閉原則。

3.1.2 單一職責原則

單一職責原則(Single Responsibility Principle, SRP):一個類只負責一個功能領域中的相應職責,或者可以定義為:就一個類而言,應該只有一個引起它變化的原因。

3.1.3 依賴倒轉原則

依賴倒轉原則(Dependency Inversion Principle, DIP):抽象不應該依賴于細節,細節應當依賴于抽象。換言之,要針對接口編程,而不是針對實現編程。

注意事項

  1. 高層模塊不應該依賴于低層模塊。兩個都應該依賴抽象。

  2. 抽象不應該依賴結節。細節應依賴于抽象。

  3. 依賴倒轉(倒置)的中心思想是面向接口編程

  4. 依賴倒轉原則是基于這樣的設計理念:相對于細節的多變性,抽象的東西要穩定的多。以抽象為基礎搭建的架構比以細節為基礎的架構要穩定的多。在java中,抽象指的是接口或抽象類,細節就是具體的實現類。

  5. 使用接口或抽象類的目的是制定好規范,而不涉及任何具體的操作,把展現細節的任務交給他們的實現類去完成。

3.1.4 迪米特法則(最小知識原則)

迪米特法則(Law of Demeter, LoD):一個軟件實體應當盡可能少地與其他實體發生相互作用。

一個軟件實體應當盡可能的少與其他實體發生相互作用。每一個軟件單位對其他軟件單位都只有最少的知識,而且局限于那些與本單位密切相關的軟件單位。迪米特法則的初衷在于降低類之間的耦合。由于每個類盡量減少對其他類的依賴,因此,很容易使得系統的功能模塊功能獨立,相互之間不存在(或很少有)依賴關系。迪米特法則不希望類之間建立直接的聯系。如果有真的需要建立聯系的,也希望能通過他的友元類來轉達。因此,應用迪米特法則有可能造成一個后果就是:系統中存在大量的中介類,這些類之所以存在完全是為了傳遞類之間的相互關系,這在一定程度上增加了系統的復雜度。
即一個類對自己依賴的類知道的越少越好。也就是說,對于被依賴的類不管多少復雜,都盡量將邏輯封裝在類的內部。對外除了提供的public 方法,不對外泄露任何信息。

3.1.5 接口隔離原則

接口隔離原則(Interface Segregation Principle, ISP):使用多個專門的接口,而不使用單一的總接口,即客戶端不應該依賴那些它不需要的接口。

3.1.6 合成/聚合復用原則

合成/聚合復用原則經常又叫做合成復用原則,就是在一個新的對象里面使用一些已有的對象,使之成為新對象的一部分,新的對象通過這些對象的委派達到復用已有功能的目的。他的設計原則是:要盡量使用合成/聚合,盡量不要使用繼承。

3.1.7 里氏代換原則

里氏代換原則(Liskov Substitution Principle, LSP):所有引用基類(父類)的地方必須能透明地使用其子類的對象。

里氏代換原則是面向對象設計的基本原則之一。即任何基類可以出現的地方,子類一定可以出現。里氏代換原則是繼承復用的基石,只有當衍生類可以替換掉基類,軟件單位的功能不受影響時,基類才能被真正復用,而衍生類也能夠在積累的基礎上增加新的行為,里氏代換原則是對“開-閉”原則的補充。實現“開-閉”原則的關鍵步驟就是抽象化。在基類與子類的繼承關系就是抽象化的具體實現,所以里氏代換原則是對實現抽象化的具體步驟的規范。

3.2 創建型模式

這些設計模式提供了一種在創建對象的同時隱藏創建邏輯的方式,而不是使用 new 運算符直接實例化對象。這使得程序在判斷針對某個給定實例需要創建哪些對象時更加靈活。

下面只給簡介了,設計模式具體介紹:設計模式簡談-CSDN博客。這篇博客非常詳細。

其中例子我都進行了簡化,只截取了與該模式最相關的部分,向查看更詳細的例子請去上面的博客。

3.2.1 工廠模式(Factory Pattern)

在工廠模式中,我們在創建對象時不會對客戶端暴露創建邏輯,并且是通過使用一個共同的接口來指向新創建的對象。

簡介:定義一個創建對象的接口,讓其子類自己決定實例化哪一個工廠類,工廠模式使其創建過程延遲到子類進行。

主要解決:主要解決接口選擇的問題。
何時使用:我們明確地計劃不同條件下創建不同實例時。
如何解決:讓其子類實現工廠接口,返回的也是一個抽象的產品。
關鍵代碼:創建過程在其子類執行。
應用實例: 1、您需要一輛汽車,可以直接從工廠里面提貨,而不用去管這輛汽車是怎么做出來的,以及這個汽車里面的具體實現。 2、Hibernate 換數據庫只需換方言和驅動就可以。
優點: 1、一個調用者想創建一個對象,只要知道其名稱就可以了。 2、擴展性高,如果想增加一個產品,只要擴展一個工廠類就可以。 3、屏蔽產品的具體實現,調用者只關心產品的接口。
缺點:每次增加一個產品時,都需要增加一個具體類和對象實現工廠,使得系統中類的個數成倍增加,在一定程度上增加了系統的復雜度,同時也增加了系統具體類的依賴。這并不是什么好事。
使用場景: 1、日志記錄器:記錄可能記錄到本地硬盤、系統事件、遠程服務器等,用戶可以選擇記錄日志到什么地方。 2、數據庫訪問,當用戶不知道最后系統采用哪一類數據庫,以及數據庫可能有變化時。 3、設計一個連接服務器的框架,需要三個協議,“POP3”、“IMAP”、“HTTP”,可以把這三個作為產品類,共同實現一個接口。
注意事項:作為一種創建類模式,在任何需要生成復雜對象的地方,都可以使用工廠方法模式。有一點需要注意的地方就是復雜對象適合使用工廠模式,而簡單對象,特別是只需要通過 new 就可以完成創建的對象,無需使用工廠模式。如果使用工廠模式,就需要引入一個工廠類,會增加系統的復雜度。

例如,利用工廠從<name>$<school>的模式字符串種創建出Person。

// 需要創建的類
public class Person {public String name, school;public Person(String name, String school) {this.name = name;this.school = school;}
}// 工廠類
public class PersonFactory {public static Person createPersonFromPatternString(String pattern) {String[] pString = pattern.split("$");return new Person(pString[0], pString[1]);}
}// 調用工廠類創建Person對象
String personString = "Alice$UCAS"
Person person = PersonFactory.createPersonFromPatternString(personString);
3.2.2 抽象工廠模式(Abstract Factory Pattern)

抽象工廠模式(Abstract Factory Pattern)是圍繞一個超級工廠創建其他工廠。該超級工廠又稱為其他工廠的工廠。這種類型的設計模式屬于創建型模式,它提供了一種創建對象的最佳方式。

在抽象工廠模式中,接口是負責創建一個相關對象的工廠,不需要顯式指定它們的類。每個生成的工廠都能按照工廠模式提供對象。

意圖:提供一個創建一系列相關或相互依賴對象的接口,而無需指定它們具體的類。

主要解決:主要解決接口選擇的問題。
何時使用:系統的產品有多于一個的產品族,而系統只消費其中某一族的產品。
如何解決:在一個產品族里面,定義多個產品。
關鍵代碼:在一個工廠里聚合多個同類產品。
應用實例:工作了,為了參加一些聚會,肯定有兩套或多套衣服吧,比如說有商務裝(成套,一系列具體產品)、時尚裝(成套,一系列具體產品),甚至對于一個家庭來說,可能有商務女裝、商務男裝、時尚女裝、時尚男裝,這些也都是成套的,即一系列具體產品。假設一種情況(現實中是不存在的,要不然,沒法進入共產主義了,但有利于說明抽象工廠模式),在您的家中,某一個衣柜(具體工廠)只能存放某一種這樣的衣服(成套,一系列具體產品),每次拿這種成套的衣服時也自然要從這個衣柜中取出了。用 OOP 的思想去理解,所有的衣柜(具體工廠)都是衣柜類的(抽象工廠)某一個,而每一件成套的衣服又包括具體的上衣(某一具體產品),褲子(某一具體產品),這些具體的上衣其實也都是上衣(抽象產品),具體的褲子也都是褲子(另一個抽象產品)。
優點:當一個產品族中的多個對象被設計成一起工作時,它能保證客戶端始終只使用同一個產品族中的對象。
缺點:產品族擴展非常困難,要增加一個系列的某一產品,既要在抽象的 Creator 里加代碼,又要在具體的里面加代碼。
使用場景: 1、QQ 換皮膚,一整套一起換。 2、生成不同操作系統的程序。
注意事項:產品族難擴展,產品等級易擴展。

// 需要創建的類
public class Person {public String name, school;public Person(String name, String school) {this.name = name;this.school = school;}
}// 抽象工廠
public abstract class PersonFactory {public static Person createPersonFromPatternString(String pattern);
}// $分隔符工廠
public class DollarSplitPersonFactory extends PersonFactory{@Overridepublic static Person createPersonFromPatternString(String pattern) {String[] pString = pattern.split("$");return new Person(pString[0], pString[1]);}
}// #分隔符工廠
public class PoundSplitPersonFactory extends PersonFactory{@Overridepublic static Person createPersonFromPatternString(String pattern) {String[] pString = pattern.split("#");return new Person(pString[0], pString[1]);}
}
3.2.3 單例模式(Singleton Pattern)

單例模式(Singleton Pattern)是 Java 中最簡單的設計模式之一。這種類型的設計模式屬于創建型模式,它提供了一種創建對象的最佳方式。

這種模式涉及到一個單一的類,該類負責創建自己的對象,同時確保只有單個對象被創建。這個類提供了一種訪問其唯一的對象的方式,可以直接訪問,不需要實例化該類的對象。

注意

  • 1、單例類只能有一個實例。
  • 2、單例類必須自己創建自己的唯一實例。
  • 3、單例類必須給所有其他對象提供這一實例。
// 單例Monitor類
public class Moniter {private static Moniter moniter;public static Moniter getMoniter() {if (moniter != null) {return moniter;} else {moniter = new Moniter();return moniter;}}
}
// 獲取Monitor
Monitor monitor = Moniter.getMonitor();
3.2.4 建造者模式(Builder Pattern)

建造者模式(Builder Pattern)使用多個簡單的對象一步一步構建成一個復雜的對象。這種類型的設計模式屬于創建型模式,它提供了一種創建對象的最佳方式。

意圖:將一個復雜的構建與其表示相分離,使得同樣的構建過程可以創建不同的表示。

主要解決:主要解決在軟件系統中,有時候面臨著"一個復雜對象"的創建工作,其通常由各個部分的子對象用一定的算法構成;由于需求的變化,這個復雜對象的各個部分經常面臨著劇烈的變化,但是將它們組合在一起的算法卻相對穩定。
何時使用:一些基本部件不會變,而其組合經常變化的時候。
如何解決:將變與不變分離開。
關鍵代碼:建造者:創建和提供實例,導演:管理建造出來的實例的依賴關系。
應用實例: 1、去肯德基,漢堡、可樂、薯條、炸雞翅等是不變的,而其組合是經常變化的,生成出所謂的"套餐"。 2、JAVA 中的 StringBuilder。
優點: 1、建造者獨立,易擴展。 2、便于控制細節風險。
缺點: 1、產品必須有共同點,范圍有限制。 2、如內部變化復雜,會有很多的建造類。
使用場景: 1、需要生成的對象具有復雜的內部結構。 2、需要生成的對象內部屬性本身相互依賴。
注意事項:與工廠模式的區別是:建造者模式更加關注與零件裝配的順序。

// 要build的類
public class ColorList {private List<String> colors;public ColorList(List<String> colors) {this.colors = colors;}
}// builder
public class ColorListBuilder {List<String> colors;public ColorListBuilder() {colors = new ArrayList<>();}public ColorListBuilder addRed() {colors.add("red");return this;}public ColorListBuilder addGreen() {colors.add("green");return this;}public ColorList build() {return new ColorList(colors);}
}// 調用builder一步步構建出類
ColorListBuilder builder = new ColorListBuilder();
ColorList colorList = builder.addGreen().addRed().addGreen().build();
3.2.5 原型模式(Prototype Pattern)

原型模式(Prototype Pattern)是用于創建重復的對象,同時又能保證性能。這種類型的設計模式屬于創建型模式,它提供了一種創建對象的最佳方式。這種模式是實現了一個原型接口,該接口用于創建當前對象的克隆。當直接創建對象的代價比較大時,則采用這種模式。

意圖:用原型實例指定創建對象的種類,并且通過拷貝這些原型創建新的對象。

主要解決:在運行期建立和刪除原型。
何時使用: 1、當一個系統應該獨立于它的產品創建,構成和表示時。 2、當要實例化的類是在運行時刻指定時,例如,通過動態裝載。 3、為了避免創建一個與產品類層次平行的工廠類層次時。 4、當一個類的實例只能有幾個不同狀態組合中的一種時。建立相應數目的原型并克隆它們可能比每次用合適的狀態手工實例化該類更方便一些。
如何解決:利用已有的一個原型對象,快速地生成和原型對象一樣的實例。
關鍵代碼: 1、實現克隆操作,在 JAVA 繼承 Cloneable,重寫 clone(),在 .NET 中可以使用 Object 類的 MemberwiseClone() 方法來實現對象的淺拷貝或通過序列化的方式來實現深拷貝。 2、原型模式同樣用于隔離類對象的使用者和具體類型(易變類)之間的耦合關系,它同樣要求這些"易變類"擁有穩定的接口。
應用實例: 1、細胞分裂。 2、JAVA 中的 Object clone() 方法。
優點: 1、性能提高。 2、逃避構造函數的約束。
缺點: 1、配備克隆方法需要對類的功能進行通盤考慮,這對于全新的類不是很難,但對于已有的類不一定很容易,特別當一個類引用不支持串行化的間接對象,或者引用含有循環結構的時候。 2、必須實現 Cloneable 接口。
使用場景: 1、資源優化場景。 2、類初始化需要消化非常多的資源,這個資源包括數據、硬件資源等。 3、性能和安全要求的場景。 4、通過 new 產生一個對象需要非常繁瑣的數據準備或訪問權限,則可以使用原型模式。 5、一個對象多個修改者的場景。 6、一個對象需要提供給其他對象訪問,而且各個調用者可能都需要修改其值時,可以考慮使用原型模式拷貝多個對象供調用者使用。 7、在實際項目中,原型模式很少單獨出現,一般是和工廠方法模式一起出現,通過 clone 的方法創建一個對象,然后由工廠方法提供給調用者。原型模式已經與 Java 融為渾然一體,大家可以隨手拿來使用。
注意事項:與通過對一個類進行實例化來構造新對象不同的是,原型模式是通過拷貝一個現有對象生成新對象的。淺拷貝實現 Cloneable,重寫clone(),深拷貝是通過實現 Serializable 讀取二進制流。

// 需要原型管理的類
public class Monitor implements Cloneable{// ...@Overridepublic Object clone() { }
}// 原型管理器
public class MonitorManager {private static Map<String, Monitor> monitors = new ConcurrentHashMap<>();public static Monitor getMonitor(String id) {if (!monitors.containsKey(id)) {monitors.put(id, new Monitor());}return (Monitor) monitors.get(id).clone();}
}// 獲取Monitor
Monitor monitor1 = MonitorManager.getMonitor("1");
Monitor monitor2 = MonitorManager.getMonitor("2");// 第二次獲取id=1的monitor直接復制原有對象
Monitor monitor3 = MonitorManager.getMonitor("1"); 

工廠模式 vs 抽象工廠模式 vs 建造者模式

三者均使用一個外部類完成一個類的構建,其中建造者模式強調建造的順序,逐步建造出對象。而工廠模式通常是一次性建立出整個完整可用的對象。

實踐中似乎工廠模式常常使用靜態方法?

// 建造者模式逐步建立起newObj
NewObject newObj = new NewObjectBuilder().addConfigA().addConfigB().build();// 工廠模式一次性建立出一個可用對象
NewObject newObj = NewObjectFactory.createNewObject(configA, configB);

抽象工廠模式在工廠模式的基礎上新增一層抽象工廠層,工廠類繼承抽象工廠。抽象工廠聲明了一個相似產品族的創建方式。利用抽象工廠模式可以完成一個產品族的創建。

原型模式 vs 單例模式

兩者均是一種依靠已經創建好的對象創建新的對象的方式。

原型模式在首次創建出一個對象后,再次請求創建對象時使用Clone方式將一個創建好的對象clone一份,返回clone后的新對象。

單例模式在首次請求創建對象時創建一個新的對象,此后再次請求該對象時僅返回創建好的對象的引用。

原型模式創建了同一個對象的多個副本,單例模式從始至終只有一個對象和多個引用。

3.3 結構型模式

3.3.1 適配器模式

適配器模式(Adapter Pattern)是作為兩個不兼容的接口之間的橋梁。這種類型的設計模式屬于結構型模式,它結合了兩個獨立接口的功能。

意圖:將一個類的接口轉換成客戶希望的另外一個接口。適配器模式使得原本由于接口不兼容而不能一起工作的那些類可以一起工作。

主要解決:主要解決在軟件系統中,常常要將一些"現存的對象"放到新的環境中,而新環境要求的接口是現對象不能滿足的。
何時使用: 1、系統需要使用現有的類,而此類的接口不符合系統的需要。 2、想要建立一個可以重復使用的類,用于與一些彼此之間沒有太大關聯的一些類,包括一些可能在將來引進的類一起工作,這些源類不一定有一致的接口。 3、通過接口轉換,將一個類插入另一個類系中。(比如老虎和飛禽,現在多了一個飛虎,在不增加實體的需求下,增加一個適配器,在里面包容一個虎對象,實現飛的接口。)
如何解決:繼承或依賴(推薦)。
關鍵代碼:適配器繼承或依賴已有的對象,實現想要的目標接口。
應用實例: 1、美國電器 110V,中國 220V,就要有一個適配器將 110V 轉化為 220V。 2、JAVA JDK 1.1 提供了 Enumeration 接口,而在 1.2 中提供了 Iterator 接口,想要使用 1.2 的 JDK,則要將以前系統的 Enumeration 接口轉化為 Iterator 接口,這時就需要適配器模式。 3、在 LINUX 上運行 WINDOWS 程序。 4、JAVA 中的 jdbc。
優點: 1、可以讓任何兩個沒有關聯的類一起運行。 2、提高了類的復用。 3、增加了類的透明度。 4、靈活性好。
缺點: 1、過多地使用適配器,會讓系統非常零亂,不易整體進行把握。比如,明明看到調用的是 A 接口,其實內部被適配成了 B 接口的實現,一個系統如果太多出現這種情況,無異于一場災難。因此如果不是很有必要,可以不使用適配器,而是直接對系統進行重構。 2.由于 JAVA 至多繼承一個類,所以至多只能適配一個適配者類,而且目標類必須是抽象類。
使用場景:有動機地修改一個正常運行的系統的接口,這時應該考慮使用適配器模式。
注意事項:適配器不是在詳細設計時添加的,而是解決正在服役的項目的問題。

例如,現在有兩個接口:

public interface Drawable {public void draw(String graphType);
}public interface BarDrawable {public void drawGreenBar();public void drawRedBar();
}

顯然兩者雖然很相似,但是無法兼容,如果我們想讓BarDrawable的實現也支持Drawable接口,那么最好的辦法就是新增一個適配器類。

public class BarDrawableAapter implements Drawable {private BarDrawable barDrawer;public BarDrawableAapter (BarDrawable barDrawer) {this.barDrawer = barDrawer;}@Overridepublic void draw(String graphType) {if ("green_bar".equals(graphType)) {barDrawer.drawGreenBar();} else if ("red_bar".equals(graphType)) {barDrawer.drawRedBar();}}
}

可見,適配器BarDrawableAapter實現了需要兼容的接口Drawable,并將需要被兼容的接口BarDrawable包裝起來,對外提供Drawable,這使得BarDrawable對外提供出Drawable接口。完成DrawableBarDrawable的兼容。

3.3.2 橋接模式

橋接(Bridge)是用于把抽象化與實現化解耦,使得二者可以獨立變化。這種類型的設計模式屬于結構型模式,它通過提供抽象化和實現化之間的橋接結構,來實現二者的解耦。

這種模式涉及到一個作為橋接的接口,使得實體類的功能獨立于接口實現類。這兩種類型的類可被結構化改變而互不影響。

** 意圖**:將抽象部分與實現部分分離,使它們都可以獨立的變化。

主要解決:在有多種可能會變化的情況下,用繼承會造成類爆炸問題,擴展起來不靈活。
何時使用:實現系統可能有多個角度分類,每一種角度都可能變化。
如何解決:把這種多角度分類分離出來,讓它們獨立變化,減少它們之間耦合。
關鍵代碼:抽象類依賴實現類。
應用實例: 1、豬八戒從天蓬元帥轉世投胎到豬,轉世投胎的機制將塵世劃分為兩個等級,即:靈魂和肉體,前者相當于抽象化,后者相當于實現化。生靈通過功能的委派,調用肉體對象的功能,使得生靈可以動態地選擇。 2、墻上的開關,可以看到的開關是抽象的,不用管里面具體怎么實現的。
優點: 1、抽象和實現的分離。 2、優秀的擴展能力。 3、實現細節對客戶透明。
缺點:橋接模式的引入會增加系統的理解與設計難度,由于聚合關聯關系建立在抽象層,要求開發者針對抽象進行設計與編程。
使用場景: 1、如果一個系統需要在構件的抽象化角色和具體化角色之間增加更多的靈活性,避免在兩個層次之間建立靜態的繼承聯系,通過橋接模式可以使它們在抽象層建立一個關聯關系。 2、對于那些不希望使用繼承或因為多層次繼承導致系統類的個數急劇增加的系統,橋接模式尤為適用。 3、一個類存在兩個獨立變化的維度,且這兩個維度都需要進行擴展。
注意事項:對于兩個獨立變化的維度,使用橋接模式再適合不過了。

個人感覺橋接模式主要應用于回調高層模塊的解偶

例如,讓一個任務執行器TaskExecutor在執行完任務后通知視圖View,寫成這樣:

// 視圖
public class View {public void accept() {// ...}
}// 任務執行器
public class TaskExecutor {public View view;public TaskExecutor(View view) {this.view = view;}public void execute() {// Execute task......// 任務完成后提交給viewview.accept();}
}

但是這么寫存在很大問題,因為視圖View一般都是高層模組,這么寫無疑使得底層模塊依賴了高層模塊。所以這里利用接口進行橋接, 將抽象accpet方法和View的具體實現進行分離。

// 提交接口
public interface Acceptable {public void accept();} 
// 視圖
public class Viewer implements Acceptable {@Overridepublic void accept() {// ...}
}// 任務執行器
public class TaskExecutor {public Acceptable view;public TaskExecutor(Acceptable view) {this.view = view;}public void execute() {// Execute task......// 任務完成后提交給viewview.accept();}
}
3.3.3 過濾器模式

過濾器模式(Filter Pattern)或標準模式(Criteria Pattern)是一種設計模式,這種模式允許開發人員使用不同的標準來過濾一組對象,通過邏輯運算以解耦的方式把它們連接起來。這種類型的設計模式屬于結構型模式,它結合多個標準來獲得單一標準。

似乎還有約束器模式這一稱謂?

public class Person {private String name;private int sex;private int age;// getter, setter 方法
}// 設置一個抽象過濾器
public interface PersonFilter {public List<Person> filter(Collection<Person> persons);}// 實現一個性別過濾器,保留女性。
public class FemaleFilter implements PersonFilter{@Overridepublic List<Person> filter(Collection<Person> persons) {List<Person> filteredPersons = new ArrayList<>();for (Person person : persons) {if (person.getSex() == 0) continue;filteredPersons.add(person);}return filteredPersons;}
}
3.3.4 組合模式

組合模式(Composite Pattern),又叫部分整體模式,是用于把一組相似的對象當作一個單一的對象。組合模式依據樹形結構來組合對象,用來表示部分以及整體層次。這種類型的設計模式屬于結構型模式,它創建了對象組的樹形結構。

意圖:將對象組合成樹形結構以表示"部分-整體"的層次結構。組合模式使得用戶對單個對象和組合對象的使用具有一致性。

主要解決:它在我們樹型結構的問題中,模糊了簡單元素和復雜元素的概念,客戶程序可以像處理簡單元素一樣來處理復雜元素,從而使得客戶程序與復雜元素的內部結構解耦。
何時使用: 1、您想表示對象的部分-整體層次結構(樹形結構)。 2、您希望用戶忽略組合對象與單個對象的不同,用戶將統一地使用組合結構中的所有對象。
如何解決:樹枝和葉子實現統一接口,樹枝內部組合該接口。
關鍵代碼:樹枝內部組合該接口,并且含有內部屬性 List,里面放 Component。
應用實例: 1、算術表達式包括操作數、操作符和另一個操作數,其中,另一個操作數也可以是操作數、操作符和另一個操作數。 2、在 JAVA AWT 和 SWING 中,對于 Button 和 Checkbox 是樹葉,Container 是樹枝。
優點: 1、高層模塊調用簡單。 2、節點自由增加。
缺點:在使用組合模式時,其葉子和樹枝的聲明都是實現類,而不是接口,違反了依賴倒置原則。
使用場景:部分、整體場景,如樹形菜單,文件、文件夾的管理。

例子中是國家三級行政區的表示:

public class Region {public static final int PROVINCE_LEVEL = 0;public static final int CITY_LEVEL = 1;public static final int COUNTY_LEVEL = 2;private int level;private String name;private List<Region> subRegions;public Region(int level, String name, List<Region> subRegions) {//......}// getter, setter方法
}// 創建:山東-青島-{市南區,嶗山區}的層級關系
Region shinan = new Region(Region.COUNTY_LEVEL, "市南區", null);
Region laoshan = new Region(Region.COUNTY_LEVEL, "嶗山區", null);
List<Region> qingdaoSub = new ArrayList<>();
qingdaoSub.add(shinan);
qingdaoSub.add(laoshan);
Region qingdao = new Region(Region.CITY_LEVEL, "青島", qingdaoSub);
List<Region> shandongSub = new ArrayList<>();
shandongsub.add(qingdao);
Region shandong = new Region(Region.PROVINCE_LEVEL, "山東", shandongsub);
3.3.5 裝飾器模式

裝飾器模式(Decorator Pattern)允許向一個現有的對象添加新的功能,同時又不改變其結構。這種類型的設計模式屬于結構型模式,它是作為現有的類的一個包裝。

這種模式創建了一個裝飾類,用來包裝原有的類,并在保持類方法簽名完整性的前提下,提供了額外的功能。

意圖:動態地給一個對象添加一些額外的職責。就增加功能來說,裝飾器模式相比生成子類更為靈活。

主要解決:一般的,我們為了擴展一個類經常使用繼承方式實現,由于繼承為類引入靜態特征,并且隨著擴展功能的增多,子類會很膨脹。
何時使用:在不想增加很多子類的情況下擴展類。
如何解決:將具體功能職責劃分,同時繼承裝飾者模式。
關鍵代碼: 1、Component 類充當抽象角色,不應該具體實現。 2、修飾類引用和繼承 Component 類,具體擴展類重寫父類方法。
應用實例: 1、孫悟空有 72 變,當他變成"廟宇"后,他的根本還是一只猴子,但是他又有了廟宇的功能。 2、不論一幅畫有沒有畫框都可以掛在墻上,但是通常都是有畫框的,并且實際上是畫框被掛在墻上。在掛在墻上之前,畫可以被蒙上玻璃,裝到框子里;這時畫、玻璃和畫框形成了一個物體。
優點:裝飾類和被裝飾類可以獨立發展,不會相互耦合,裝飾模式是繼承的一個替代模式,裝飾模式可以動態擴展一個實現類的功能。
缺點:多層裝飾比較復雜。
使用場景: 1、擴展一個類的功能。 2、動態增加功能,動態撤銷。

public interface Singable {public void sing();
}// 歌手唱歌
public class Singer implements Singable {public void sing() {// }
}// 抽象歌手裝飾器類
public abstract class SingerDecorator implements Singable {private Singable singer;public SingerDecorator(Singable singer) {this.singer = singer;}public void sing() {singer.sing();}
}
// 實現一個裝飾器類,使得唱歌后還要跳舞
public class SingerDanceDecorator extends SingerDecorator {public SingerDanceDecorator(Singable singer) {super(singer);}private void dance() {// ... }public void sing() {super.sing();dance();}
}Singable singer = new Singer();
Singable decorator = new SingerDanceDecorator(singer);// 歌手只會唱歌
singer.sing();// 通過修飾器后,歌手唱歌并跳舞
decorator.sing();
3.3.6 外觀模式

外觀模式(Facade Pattern)隱藏系統的復雜性,并向客戶端提供了一個客戶端可以訪問系統的接口。這種類型的設計模式屬于結構型模式,它向現有的系統添加一個接口,來隱藏系統的復雜性。

這種模式涉及到一個單一的類,該類提供了客戶端請求的簡化方法和對現有系統類方法的委托調用。

意圖:為子系統中的一組接口提供一個一致的界面,外觀模式定義了一個高層接口,這個接口使得這一子系統更加容易使用。

主要解決:降低訪問復雜系統的內部子系統時的復雜度,簡化客戶端之間的接口。
何時使用: 1、客戶端不需要知道系統內部的復雜聯系,整個系統只需提供一個"接待員"即可。 2、定義系統的入口。
如何解決:客戶端不與系統耦合,外觀類與系統耦合。
關鍵代碼:在客戶端和復雜系統之間再加一層,這一層將調用順序、依賴關系等處理好。
應用實例: 1、去醫院看病,可能要去掛號、門診、劃價、取藥,讓患者或患者家屬覺得很復雜,如果有提供接待人員,只讓接待人員來處理,就很方便。 2、JAVA 的三層開發模式。
優點: 1、減少系統相互依賴。 2、提高靈活性。 3、提高了安全性。
缺點:不符合開閉原則,如果要改東西很麻煩,繼承重寫都不合適。
使用場景: 1、為復雜的模塊或子系統提供外界訪問的模塊。 2、子系統相對獨立。 3、預防低水平人員帶來的風險。

個人理解是在一層復雜的多樣的接口層上再套一層簡化后的接口。

// 定義歌手、舞者、演奏者接口
public interface Singer {void sing();
}
public interface Dancer {void dance();
}
public interface Player {void play();
}// 定義劇院接口,開始表演。
public class Theatre {Singer singer;Dancer dancer;Player player;public void show() {singer.sing();dancer.dance();player.play();}
}
3.3.7 享元模式

享元模式(Flyweight Pattern)主要用于減少創建對象的數量,以減少內存占用和提高性能。這種類型的設計模式屬于結構型模式,它提供了減少對象數量從而改善應用所需的對象結構的方式。

享元模式嘗試重用現有的同類對象,如果未找到匹配的對象,則創建新對象。我們將通過創建 5 個對象來畫出 20 個分布于不同位置的圓來演示這種模式。由于只有 5 種可用的顏色,所以 color 屬性被用來檢查現有的 Circle 對象。

// 需要原型管理的類
public class Monitor {// ...
}
// 原型管理器
public class MonitorManager {private static Map<String, Monitor> monitors = new ConcurrentHashMap<>();public static Monitor getMonitor(String id) {if (!monitors.containsKey(id)) {monitors.put(id, new Monitor());}return (Monitor) monitors.get(id);}
}// 獲取Monitor
Monitor monitor1 = MonitorManager.getMonitor("1");
Monitor monitor2 = MonitorManager.getMonitor("2");// 第二次獲取id=1的monitor直接傳遞已經創建的引用
Monitor monitor3 = MonitorManager.getMonitor("1");
3.3.8 代理模式

在代理模式(Proxy Pattern)中,一個類代表另一個類的功能。這種類型的設計模式屬于結構型模式。

在代理模式中,我們創建具有現有對象的對象,以便向外界提供功能接口。

意圖:為其他對象提供一種代理以控制對這個對象的訪問。

主要解決:在直接訪問對象時帶來的問題,比如說:要訪問的對象在遠程的機器上。在面向對象系統中,有些對象由于某些原因(比如對象創建開銷很大,或者某些操作需要安全控制,或者需要進程外的訪問),直接訪問會給使用者或者系統結構帶來很多麻煩,我們可以在訪問此對象時加上一個對此對象的訪問層。
何時使用:想在訪問一個類時做一些控制。
如何解決:增加中間層。
關鍵代碼:實現與被代理類組合。
應用實例: 1、Windows 里面的快捷方式。 2、豬八戒去找高翠蘭結果是孫悟空變的,可以這樣理解:把高翠蘭的外貌抽象出來,高翠蘭本人和孫悟空都實現了這個接口,豬八戒訪問高翠蘭的時候看不出來這個是孫悟空,所以說孫悟空是高翠蘭代理類。 3、買火車票不一定在火車站買,也可以去代售點。 4、一張支票或銀行存單是賬戶中資金的代理。支票在市場交易中用來代替現金,并提供對簽發人賬號上資金的控制。 5、spring aop。
優點: 1、職責清晰。 2、高擴展性。 3、智能化。
缺點: 1、由于在客戶端和真實主題之間增加了代理對象,因此有些類型的代理模式可能會造成請求的處理速度變慢。 2、實現代理模式需要額外的工作,有些代理模式的實現非常復雜。
使用場景:按職責來劃分,通常有以下使用場景: 1、遠程代理。 2、虛擬代理。 3、Copy-on-Write 代理。 4、保護(Protect or Access)代理。 5、Cache代理。 6、防火墻(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理。
注意事項: 1、和適配器模式的區別:適配器模式主要改變所考慮對象的接口,而代理模式不能改變所代理類的接口。 2、和裝飾器模式的區別:裝飾器模式為了增強功能,而代理模式是為了加以控制。

代理模式 vs 裝飾器模式

1.代理模式對對象實現訪問控制而不增強對象本身的功能,裝飾器模式是增強對象的功能。

2.代理模式為對象生成一個代理對象,由代理對象訪問原對象;裝飾器模式更多對原對象的功能進行增強,是繼承方案的一個代替。

3.代理模式的重心在于調用對象的某個功能,并做一個和對象本身無關的業務,裝飾器模式重心在于擴展自身的功能。

個人疑問:不過Spring的AOP也用代理實現,其宣稱是增強功能?所以很奇怪吧?感覺就是個名頭的區別而已。

3.4 行為性模式

3.4.1 責任鏈模式

顧名思義,責任鏈模式(Chain of Responsibility Pattern)為請求創建了一個接收者對象的鏈。這種模式給予請求的類型,對請求的發送者和接收者進行解耦。這種類型的設計模式屬于行為型模式。

在這種模式中,通常每個接收者都包含對另一個接收者的引用。如果一個對象不能處理該請求,那么它會把相同的請求傳給下一個接收者,依此類推。

意圖:避免請求發送者與接收者耦合在一起,讓多個對象都有可能接收請求,將這些對象連接成一條鏈,并且沿著這條鏈傳遞請求,直到有對象處理它為止。

主要解決:職責鏈上的處理者負責處理請求,客戶只需要將請求發送到職責鏈上即可,無須關心請求的處理細節和請求的傳遞,所以職責鏈將請求的發送者和請求的處理者解耦了。
何時使用:在處理消息的時候以過濾很多道。
如何解決:攔截的類都實現統一接口。
關鍵代碼:Handler 里面聚合它自己,在 HandlerRequest 里判斷是否合適,如果沒達到條件則向下傳遞,向誰傳遞之前 set 進去。
應用實例: 1、紅樓夢中的"擊鼓傳花"。 2、JS 中的事件冒泡。 3、JAVA WEB 中 Apache Tomcat 對 Encoding 的處理,Struts2 的攔截器,jsp servlet 的 Filter。
優點: 1、降低耦合度。它將請求的發送者和接收者解耦。 2、簡化了對象。使得對象不需要知道鏈的結構。 3、增強給對象指派職責的靈活性。通過改變鏈內的成員或者調動它們的次序,允許動態地新增或者刪除責任。 4、增加新的請求處理類很方便。
缺點: 1、不能保證請求一定被接收。 2、系統性能將受到一定影響,而且在進行代碼調試時不太方便,可能會造成循環調用。 3、可能不容易觀察運行時的特征,有礙于除錯。
使用場景: 1、有多個對象可以處理同一個請求,具體哪個對象處理該請求由運行時刻自動確定。 2、在不明確指定接收者的情況下,向多個對象中的一個提交一個請求。 3、可動態指定一組對象處理請求。

// 抽象日志類
public abstract class AbstractLogger {private AbstractLogger nextLogger;public abstract void w(String info);public void log(String info) {w(info);nextLogger.log(info);}// getter setter
}public class ConsoleLogger extends AbstractLogger{public void w(String info) {}
}public class FileLogger extends AbstractLogger{public void w(String info) {}
}public class SocketLogger extends AbstractLogger{public void w(String info) {}
}AbstractLogger consoleLogger = new ConsoleLogger();
AbstractLogger fileLogger = new FileLogger();
AbstractLogger socketLogger = new SocketLogger();consoleLogger.setNextLogger(fileLogger);
fileLogger.setNextLogger(socketLogger);consoleLogger.log("Hello!");
3.4.2 命令模式

命令模式(Command Pattern)是一種數據驅動的設計模式,它屬于行為型模式。請求以命令的形式包裹在對象中,并傳給調用對象。調用對象尋找可以處理該命令的合適的對象,并把該命令傳給相應的對象,該對象執行命令。

意圖:將一個請求封裝成一個對象,從而使您可以用不同的請求對客戶進行參數化。

主要解決:在軟件系統中,行為請求者與行為實現者通常是一種緊耦合的關系,但某些場合,比如需要對行為進行記錄、撤銷或重做、事務等處理時,這種無法抵御變化的緊耦合的設計就不太合適。
何時使用:在某些場合,比如要對行為進行"記錄、撤銷/重做、事務"等處理,這種無法抵御變化的緊耦合是不合適的。在這種情況下,如何將"行為請求者"與"行為實現者"解耦?將一組行為抽象為對象,可以實現二者之間的松耦合。
如何解決:通過調用者調用接受者執行命令,順序:調用者→命令→接受者。
關鍵代碼:定義三個角色:1、received 真正的命令執行對象 2、Command 3、invoker 使用命令對象的入口
應用實例:struts 1 中的 action 核心控制器 ActionServlet 只有一個,相當于 Invoker,而模型層的類會隨著不同的應用有不同的模型類,相當于具體的 Command。
優點: 1、降低了系統耦合度。 2、新的命令可以很容易添加到系統中去。
缺點:使用命令模式可能會導致某些系統有過多的具體命令類。
使用場景:認為是命令的地方都可以使用命令模式,比如: 1、GUI 中每一個按鈕都是一條命令。 2、模擬 CMD。
注意事項:系統需要支持命令的撤銷(Undo)操作和恢復(Redo)操作,也可以考慮使用命令模式,見命令模式的擴展。

// 定義一個計數器,這是命令執行的主體
public class Count {private int count = 0;public void add() {count++;}public void sub() {count--;}
}// 定義一個抽象命令類
public interface Order {public void execute();
}// +1命令
public class AddOrder implements Order {private Count count;public AddOrder(Count count) {this.count = count;}@Overridepublic void execute() {this.count.add();}
}// -1命令
public class SubOrder implements Order {private Count count;public SubOrder(Count count) {this.count = count;}@Overridepublic void execute() {this.count.sub();}
}// 定義命令執行者
public class Invoker {private List<Order> orderList = new ArrayList<Order>(); public void takeOrder(Order order){orderList.add(order);      }public void placeOrders(){for (Order order : orderList) {order.execute();}orderList.clear();}
}Count count = new Count();
Order addOrder = new AddOrder(count);
Order subOrder = new SubOrder(count);Invoker invoker = new Invoker();
invoker.takeOrder(addOrder);
invoker.takeOrder(subOrder);invoker.placeOrders();
3.4.3 解釋器模式

解釋器模式(Interpreter Pattern)提供了評估語言的語法或表達式的方式,它屬于行為型模式。這種模式實現了一個表達式接口,該接口解釋一個特定的上下文。這種模式被用在 SQL 解析、符號處理引擎等。

意圖:給定一個語言,定義它的文法表示,并定義一個解釋器,這個解釋器使用該標識來解釋語言中的句子。

主要解決:對于一些固定文法構建一個解釋句子的解釋器。
何時使用:如果一種特定類型的問題發生的頻率足夠高,那么可能就值得將該問題的各個實例表述為一個簡單語言中的句子。這樣就可以構建一個解釋器,該解釋器通過解釋這些句子來解決該問題。
如何解決:構建語法樹,定義終結符與非終結符。
關鍵代碼:構建環境類,包含解釋器之外的一些全局信息,一般是 HashMap。
應用實例:編譯器、運算表達式計算。
優點: 1、可擴展性比較好,靈活。 2、增加了新的解釋表達式的方式。 3、易于實現簡單文法。
缺點: 1、可利用場景比較少。 2、對于復雜的文法比較難維護。 3、解釋器模式會引起類膨脹。 4、解釋器模式采用遞歸調用方法。
使用場景: 1、可以將一個需要解釋執行的語言中的句子表示為一個抽象語法樹。 2、一些重復出現的問題可以用一種簡單的語言來進行表達。 3、一個簡單語法需要解釋的場景。

// 設置表達式抽象類
public interface Expression {public int interpret(String context);   
}
// 加法表達式
public class AdditionExpression implements Expression {@Overridepublic int interpret(String context) {String[] nums = context.split("+");return Integer.parseInt(nums[0]) + Integer.parseInt(nums[1]);}
}// 解析加法表達式,獲得加和。
Expression et = new AdditionExpression();
int sum = et.interpret("123+45");
3.4.4 迭代器模式

迭代器模式(Iterator Pattern)是 Java 和 .Net 編程環境中非常常用的設計模式。這種模式用于順序訪問集合對象的元素,不需要知道集合對象的底層表示。

意圖:提供一種方法順序訪問一個聚合對象中各個元素, 而又無須暴露該對象的內部表示。

主要解決:不同的方式來遍歷整個整合對象。
何時使用:遍歷一個聚合對象。
如何解決:把在元素之間游走的責任交給迭代器,而不是聚合對象。
關鍵代碼:定義接口:hasNext, next。
應用實例:JAVA 中的 iterator。
優點: 1、它支持以不同的方式遍歷一個聚合對象。 2、迭代器簡化了聚合類。 3、在同一個聚合上可以有多個遍歷。 4、在迭代器模式中,增加新的聚合類和迭代器類都很方便,無須修改原有代碼。
缺點:由于迭代器模式將存儲數據和遍歷數據的職責分離,增加新的聚合類需要對應增加新的迭代器類,類的個數成對增加,這在一定程度上增加了系統的復雜性。
使用場景: 1、訪問一個聚合對象的內容而無須暴露它的內部表示。 2、需要為聚合對象提供多種遍歷方式。 3、為遍歷不同的聚合結構提供一個統一的接口。
注意事項:迭代器模式就是分離了集合對象的遍歷行為,抽象出一個迭代器類來負責,這樣既可以做到不暴露集合的內部結構,又可讓外部代碼透明地訪問集合內部的數據。

ps.java中有內置的IteratorIterable可以繼承/實現。

// 迭代器接口
public interface Iterator {public boolean hasNext();public Object next();}// 容器
public interface Container {public Iterator getIterator();
}// 可迭代對象
public class NameRepository implements Container {public String[] names = {"Robert" , "John" ,"Julie" , "Lora"};@Overridepublic Iterator getIterator() {return new NameIterator();}private class NameIterator implements Iterator {int index;@Overridepublic boolean hasNext() {if(index < names.length){return true;}return false;}@Overridepublic Object next() {if(this.hasNext()){return names[index++];}return null;}     }
}for(Iterator iter = namesRepository.getIterator(); iter.hasNext();){String name = (String)iter.next();System.out.println("Name : " + name);
}
3.4.5 中介者模式

中介者模式(Mediator Pattern)是用來降低多個對象和類之間的通信復雜性。這種模式提供了一個中介類,該類通常處理不同類之間的通信,并支持松耦合,使代碼易于維護。中介者模式屬于行為型模式。

意圖:用一個中介對象來封裝一系列的對象交互,中介者使各對象不需要顯式地相互引用,從而使其耦合松散,而且可以獨立地改變它們之間的交互。

主要解決:對象與對象之間存在大量的關聯關系,這樣勢必會導致系統的結構變得很復雜,同時若一個對象發生改變,我們也需要跟蹤與之相關聯的對象,同時做出相應的處理。
何時使用:多個類相互耦合,形成了網狀結構。
如何解決:將上述網狀結構分離為星型結構。
關鍵代碼:對象 Colleague 之間的通信封裝到一個類中單獨處理。
應用實例: 1、中國加入 WTO 之前是各個國家相互貿易,結構復雜,現在是各個國家通過 WTO 來互相貿易。 2、機場調度系統。 3、MVC 框架,其中C(控制器)就是 M(模型)和 V(視圖)的中介者。
優點: 1、降低了類的復雜度,將一對多轉化成了一對一。 2、各個類之間的解耦。 3、符合迪米特原則。
缺點:中介者會龐大,變得復雜難以維護。
使用場景: 1、系統中對象之間存在比較復雜的引用關系,導致它們之間的依賴關系結構混亂而且難以復用該對象。 2、想通過一個中間類來封裝多個類中的行為,而又不想生成太多的子類。
注意事項:不應當在職責混亂的時候使用。

// 需要通信的User類
public class User {private String name;public User(Stirng name) {this.name = name;}public void sendToRoom(String msg) {ChatRoom.send(name, msg);}
}// 聊天室(中介User類的類)
public class ChatRoom {public static void send(String name, String msg) {}
}// 兩者利用聊天室通信
User user1 = new User("A");
User user2 = new User("B");
user1.sendToRoom("Hahaha");
user2.sendToRoom("Hahaha");
3.4.6 備忘錄模式

備忘錄模式(Memento Pattern)保存一個對象的某個狀態,以便在適當的時候恢復對象。備忘錄模式屬于行為型模式。

意圖:在不破壞封裝性的前提下,捕獲一個對象的內部狀態,并在該對象之外保存這個狀態。

主要解決:所謂備忘錄模式就是在不破壞封裝的前提下,捕獲一個對象的內部狀態,并在該對象之外保存這個狀態,這樣可以在以后將對象恢復到原先保存的狀態。
何時使用:很多時候我們總是需要記錄一個對象的內部狀態,這樣做的目的就是為了允許用戶取消不確定或者錯誤的操作,能夠恢復到他原先的狀態,使得他有"后悔藥"可吃。
如何解決:通過一個備忘錄類專門存儲對象狀態。
關鍵代碼:客戶不與備忘錄類耦合,與備忘錄管理類耦合。
應用實例: 1、后悔藥。 2、打游戲時的存檔。 3、Windows 里的 ctrl + z。 4、IE 中的后退。 5、數據庫的事務管理。
優點: 1、給用戶提供了一種可以恢復狀態的機制,可以使用戶能夠比較方便地回到某個歷史的狀態。 2、實現了信息的封裝,使得用戶不需要關心狀態的保存細節。
缺點:消耗資源。如果類的成員變量過多,勢必會占用比較大的資源,而且每一次保存都會消耗一定的內存。
使用場景: 1、需要保存/恢復數據的相關狀態場景。 2、提供一個可回滾的操作。
注意事項: 1、為了符合迪米特原則,還要增加一個管理備忘錄的類。 2、為了節約內存,可使用原型模式+備忘錄模式。

3.4.7 觀察者模式

當對象間存在一對多關系時,則使用觀察者模式(Observer Pattern)。比如,當一個對象被修改時,則會自動通知依賴它的對象。觀察者模式屬于行為型模式。

意圖:定義對象間的一種一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴于它的對象都得到通知并被自動更新。

主要解決:一個對象狀態改變給其他對象通知的問題,而且要考慮到易用和低耦合,保證高度的協作。
何時使用:一個對象(目標對象)的狀態發生改變,所有的依賴對象(觀察者對象)都將得到通知,進行廣播通知。
如何解決:使用面向對象技術,可以將這種依賴關系弱化。
關鍵代碼:在抽象類里有一個 ArrayList 存放觀察者們。
應用實例: 1、拍賣的時候,拍賣師觀察最高標價,然后通知給其他競價者競價。 2、西游記里面悟空請求菩薩降服紅孩兒,菩薩灑了一地水招來一個老烏龜,這個烏龜就是觀察者,他觀察菩薩灑水這個動作。
優點: 1、觀察者和被觀察者是抽象耦合的。 2、建立一套觸發機制。
缺點: 1、如果一個被觀察者對象有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間。 2、如果在觀察者和觀察目標之間有循環依賴的話,觀察目標會觸發它們之間進行循環調用,可能導致系統崩潰。 3、觀察者模式沒有相應的機制讓觀察者知道所觀察的目標對象是怎么發生變化的,而僅僅只是知道觀察目標發生了變化。
使用場景:一個抽象模型有兩個方面,其中一個方面依賴于另一個方面。將這些方面封裝在獨立的對象中使它們可以各自獨立地改變和復用。
一個對象的改變將導致其他一個或多個對象也發生改變,而不知道具體有多少對象將發生改變,可以降低對象之間的耦合度。
一個對象必須通知其他對象,而并不知道這些對象是誰。
需要在系統中創建一個觸發鏈,A對象的行為將影響B對象,B對象的行為將影響C對象……,可以使用觀察者模式創建一種鏈式觸發機制。
注意事項: 1、JAVA 中已經有了對觀察者模式的支持類。 2、避免循環引用。 3、如果順序執行,某一觀察者錯誤會導致系統卡殼,一般采用異步方式。

3.4.8 狀態模式
3.4.9 空對象模式
3.4.10 策略模式
3.4.11 模板模式
3.4.12 訪問者模式

3.5 J2EE模式

3.5.1 MVC模式
3.5.2 業務代碼模式
3.5.3 組合實體模式
3.5.4 數據訪問對象模式
3.5.5 前端控制器模式
3.5.6 攔截過濾器模式

4. Spring介紹

4.1 介紹

Spring框架是一個開源的Java平臺,它為開發者提供了一種方法來簡化企業級應用程序的開發。Spring框架的核心是控制反轉(IoC)和面向切面編程(AOP)。

控制反轉(IoC)

控制反轉是一種軟件設計模式,在這種模式中,對象的依賴關系不是由應用程序代碼直接管理,而是由外部容器來管理。

面向切面編程(AOP)

面向切面編程允許你把系統中的關注點(如日志記錄、權限控制等)從主業務邏輯中分離出來

核心組件

在這里插入圖片描述

  1. Core Container(核心容器):提供核心功能,包括依賴注入(DI),事件發布,資源訪問等。

  2. Data Access/Integration(數據訪問/集成):提供與數據庫交互的支持,包括事務管理。

  3. Web:提供構建Web應用的支持,例如Spring MVC。

  4. AOP:提供面向切面編程的支持。

  5. Instrumentation:提供類檢測和類加載器實現,用于某些應用服務器。

  6. Test:提供用于測試Spring應用的支持。

常用模塊

在這里插入圖片描述

常見注解

bean 注入與裝配的的方式有很多種,可以通過 xml,get set 方式,構造函數或者注解等。簡單易用的方式就是使用 Spring 的注解了,Spring 提供了大量的注解方式。

在這里插入圖片描述

@Controller vs @RestController

@RestController包含了@Controller@ResponseBody兩個注解,表明該controller是構建符合RESTful風格api,會將所有處理請求的方法默認解析為將方法返回值直接作為響應體內容返回。

4.2 常見注解

4.2.1 Spring Bean注解

由以下注解標記的類會被識別為Spring Bean,在Spring容器初始化時被創建,并由其管理。

@Component

// Compoent源碼
@Target({ElementType.TYPE}) // 注解允許被在類、接口、枚舉上
@Retention(RetentionPolicy.RUNTIME) // 注解在程序運行時仍被保留
@Documented // 由其標注的信息會被記錄到文檔中
public @interface Component {String value() default ""; // 定義組件名
}

@Component注解是Spring Bean內所用不同類型的根注解,被其標注的類被Spring識別為Bean,并在App啟動時由容器管理。

@Repository

// Repository
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {@AliasFor( //別名annotation = Component.class)String value() default "";
}

@Repository注解用于標注Dao層實現,本質上還是一個Compoent。

@Service

// Service
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {@AliasFor(annotation = Component.class)String value() default "";
}

@Service注解用于標注業務層實現,本質上還是一個Compoent。

@Controller

@Controller注解用于標注控制器實現,本質上還是一個Compoent。源碼和前面幾種一樣。

@RestController

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {String value() default "";
}

@RestController包含了@Controller@ResponseBody兩個注解,表明該controller是構建符合RESTful風格api,會將所有處理請求的方法默認解析為將方法返回值直接作為響應體內容返回。

@Configuration

由其標注的類時配置類,在Spring容器啟動時,依照配置類中的定義配置Bean等配置。源碼與上述幾個基本一致。

4.2.2 自動裝配相關注解

@Autowired

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
// 可以標注在構造器、方法、參數、字段和注解上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {boolean required() default true; 
// 必須標記:如果置為false,則Spring容器在嘗試裝配時,沒有找到匹配的Bean對象,則置為Null。
// 如果置為true,當Spring無法找到匹配的Bean時會直接報錯退出。
}

按照類型自動注入,如果bean對象中僅有一個類型與注入變量一致,澤注入成功。 如果用多個類型一樣的bean,使用變量名id注入。如果沒有對應id的,則報錯。

@Qualifier

在按照類型注入的基礎上,再按照名稱注入。 屬性: value 用于指定注入bean的id。 他不能獨立使用,要先寫一個Autowired。

@Resource

直接按照bean的id注入,可以獨立適應,

以上三個注解只能注入bean類型,不能注入基本類型,集合類型的注入,只能通過xml實現。

@Value

直接注入一個值,也支持el表達式,注入配置文件中的值。

4.2.3 配置類注解

@Bean

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {@AliasFor("name") // Bean IdString[] value() default {};@AliasFor("value") // Bean idString[] name() default {};Autowire autowire() default Autowire.NO;// No:不嘗試自動裝配Bean中的對象(一般用都用這個,如果想自動裝配,在類里面配置)// byName:依次調用setter方法,在容器中尋找Bean裝配// byType,在容器中查找對應成員類型的bean,自動裝配// construct:利用構造函數裝配String initMethod() default "";// 當Bean被創建后,首先調用initMethod方法String destroyMethod() default "(inferred)";// 當Bean生命周期結束,調用此方法
}

在配置類中(@Configuratoin標注的類)出現,標注在一個方法上,表示配置一個bean對象。Spring容器初始化時,掃描到此類方法,將調用方法,并將返回的對象作為bean載入到容器中。在調用方法時,會自動掃描容器中的對象,填充方法參數。

@Import

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {Class<?>[] value(); // 要導入的另一個配置類
}

在配置類前使用該注解可以導入其他配置類。

@CompoentScan

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {@AliasFor("basePackages") // 掃描根路徑String[] value() default {};@AliasFor("value")String[] basePackages() default {};Class<?>[] basePackageClasses() default {};Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;String resourcePattern() default "**/*.class";boolean useDefaultFilters() default true;Filter[] includeFilters() default {}; // 包含的組件Filter[] excludeFilters() default {}; // 排除組件boolean lazyInit() default false;@Retention(RetentionPolicy.RUNTIME)@Target({})public @interface Filter {FilterType type() default FilterType.ANNOTATION;// ANNOTATION 按照組件過濾// ASSIGNABLE_TYPE 掃描給定的類型// ASPECTJ 使用ASPECTJ表達式// REGEX 正則// CUSTOM 自定義規則@AliasFor("classes")Class<?>[] value() default {};@AliasFor("value")Class<?>[] classes() default {};String[] pattern() default {};}
}

指定Spring容器掃描Bean的配置,一般加載配置類或者主類前面。

// includeFilters 用法 包含Animal.class類可以被掃描到,包括其子類
@ComponentScan(value = "com.spring"includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {Animal.class}
)}
)
// excludeFilters 用法 排除包含@Controller注解的類
@ComponentScan(value = "com.spring", excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class}),
})
// ComponentScans用法
@ComponentScans(value = {@ComponentScan(value = "com.spring", includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})}, useDefaultFilters = false) ,@ComponentScan(value = "com.spring", excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = { Repository.class})})}
)*/
// @ComponentScan 
// 針對Java8 語法可以指定多個@ComponentScan,Java8以下可以用 //@ComponentScans() 配置多個規則
@ComponentScan(value = "com.spring", excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Controller.class}),
}, includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Controller.class}),
})

@PropertySource

指定一個配置文件源。

4.3 原理

4.3.1 IOC原理

IoC (Inversion of Control )即控制反轉/反轉控制。它是一種思想不是一個技術實現。描述的是:Java 開發領域對象的創建以及管理的問題。

在這里插入圖片描述

Why IOC?

  1. 對象之間的耦合度或者說依賴程度降低;
  2. 資源變的容易管理;比如你用 Spring 容器提供的話很容易就可以實現一個單例。

IOC in Spring(IOC在Spring框架中的實現)

利用反射技術,遵循工廠設計模式,通過外部配置文件生成對象,并將對象放入容器管理。

在這里插入圖片描述

4.3.2 AOP原理

4.3.3 MVC原理

4.4 Spring啟動過程

4.4.1 前置了解

在了解Spring啟動過程之前,需要對Spring最核心組件ApplicationContext和IOC容器底層抽象定義BeanFactory有所了解。

ApplicationContextBeanFactory都是Spring的抽象接口定義,其中ApplicationContextBeanFactory的子類。具體關系如下圖所示:

在這里插入圖片描述

ApplicationContext

是Spring的主體,具備IOC、AOP、消息發布/訂閱、消息源轉換等功能。有幾種不同的實現,這些實現分別通過不同的方式初始化一個Spring實例。(主要方式就兩種,1.通過注解的方式初始化Spring,2.通過xml配置文件的方式初始化Spring。)

這里給出幾種常用的ApplicationContext實現類

實現類說明
AnnotationConfigApplicationContext從一個或多個基于Java的配置類中加載上下文定義,適用于Java注解的方式
ClassPathXmlApplicationContext從類路徑下的一個或多個xml配置文件中加載上下文定義,適用于xml配置的方式
FileSystemXmlApplicationContext從文件系統下的一個或多個xml配置文件中加載上下文定義,也就是說系統盤符中加載xml配置文件
AnnotationConfigWebApplicationContext專門為web應用準備的,適用于注解方式
XmlWebApplicationContext從Web應用下的一個或多個xml配置文件加載上下文定義,適用于xml配置方式

BeanFactory

在這里插入圖片描述

4.4.2 啟動過程

編寫代碼,通過注解的方式啟動一個Spring實例,觀察啟動過程。

首先定義兩個服務組件,并要求一個要裝配另一個。然后實例化一個AnnotationConfigApplicationContext,這個ApplicationContext的實現可以掃描一個package內的組件,完成Spring的創建。

// TestService.java
@Component
public class TestService {    // ......
}//AutowiredTestService.java
@Component
public class AutowiredTestService {@Autowiredprivate TestService testService;}// SpringTestApplication.java
public class SpringTestApplication {public static void main(String[] args) {ApplicationContext context = new AnnotationConfigApplicationContext("kalzn.spring_test.bean");AutowiredTestService service = context.getBean(AutowiredTestService.class);}}

進入AnnotationConfigApplicationContext構造方法后,發現其中很簡單,主要就是三個步驟:1. 初始化配置 2. 掃描目標package下的注解,登記這些注解 3. 根據這些注解完成Spring的初始化。

在這里插入圖片描述

1.初始化配置

進入this()

在這里插入圖片描述

BeanDefinitionReader

從xml文件、類路徑下使用了@Component系列注解的類、或者從@Configuration注解的配置類,獲取BeanDefintiions,然后注冊到BeanFactory中。BeanDefintiion其實就是對Bean的一些元數據定義,在掃描組件(第二步)的時候由BeanFactory實現保存進一個Map中,然后在refresh(第三步)中依據這些定義實例化出對象。

幾種常見實現:

實現說明
XmlBeanDefinitionReader從XML文件讀取定義
AnnotatedBeanDefinitionReader通過注解讀取定義
ConfigurationClassBeanDefinitionReader基于@Configuration注解的類配置(這里注意區分其與AnnotatedBeanDefinitionReader的區別:AnnotatedBeanDefinitionReader是通過類上的組件注解如Service、Component等注冊,而ConfigurationClassBeanDefinitionReader是通過@Configuration類內的例如@Bean注解注冊)

繼續深入,看看BeanDefinitionReader是如何搞出來的:

在這里插入圖片描述

ClassPathBeanDefinitionScanner

指定一個掃描器組件,在對應ClassPath下掃描組件。

BeanDefinitionRegistry

ApplicationContext在初始化Reader和Scanner時,將自身作為BeanDefinitionRegistry傳入了進去。

一個存放BeanDefinition的注冊表,用于存儲和管理所有的BeanDefinition。

2. 組件掃描

進入this.scan(),經過數層調用,最終調用了ClassPathBeanDefinitionScanner核心方法doScan

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {Assert.notEmpty(basePackages, "At least one base package must be specified");Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet();String[] var3 = basePackages;int var4 = basePackages.length;for(int var5 = 0; var5 < var4; ++var5) { // 遍歷所有要掃描的包String basePackage = var3[var5]; //尋找候選的組件Set<BeanDefinition> candidates = this.findCandidateComponents(basePackage);Iterator var8 = candidates.iterator();while(var8.hasNext()) { // 迭代這些候選組件// 獲取候選組件的BeanDefinitionBeanDefinition candidate = (BeanDefinition)var8.next();// 獲取生命周期的元定義信息ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);candidate.setScope(scopeMetadata.getScopeName());// 生成組件名稱String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);// 根據類型不同做一些后處理,例如設定懶加載等等if (candidate instanceof AbstractBeanDefinition abstractBeanDefinition) {this.postProcessBeanDefinition(abstractBeanDefinition, beanName);}if (candidate instanceof AnnotatedBeanDefinition annotatedBeanDefinition) {AnnotationConfigUtils.processCommonDefinitionAnnotations(annotatedBeanDefinition);}// 檢測當前BeanDefinition是否已經存在Spring中,已存在是否兼容if (this.checkCandidate(beanName, candidate)) {BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);beanDefinitions.add(definitionHolder);// 注冊組件定義this.registerBeanDefinition(definitionHolder, this.registry);}}}return beanDefinitions;
}    
3.refresh

是Spring核心方法,在這里完成容器的初始化,完成Bean對象的實例化,并初始化其他Spring組件。這里首先看refresh方法概況,然后在逐個簡要分析這些方法。

public void refresh() throws BeansException, IllegalStateException {this.startupShutdownLock.lock();try {this.startupShutdownThread = Thread.currentThread();StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");// 1. 刷新前的預處理this.prepareRefresh();// 2. 創建一個刷新的BeanFactoryConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();// 3. BeanFactory的準備工作this.prepareBeanFactory(beanFactory);try {// 4. BeanFactory準備完成后的后處理工作this.postProcessBeanFactory(beanFactory);StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");// 5. 執行執行 BeanFactoryPostProcessor 和 BeanFactory 方法的后置處理器。this.invokeBeanFactoryPostProcessors(beanFactory);// 6. 注冊BeanPostProcessorthis.registerBeanPostProcessors(beanFactory);beanPostProcess.end();// 7. 初始化MessageSource組件this.initMessageSource();// 8. 初始化ApplicationEventMulticaster (Spring的事件發布/訂閱機制)this.initApplicationEventMulticaster();// 9. 留給子容器,例如在springboot中,用來啟動tomcat、jettythis.onRefresh();// 10. 注冊ApplicationListeners (Spring的事件發布/訂閱機制)this.registerListeners();// 11. 核心方法,根據掃描到的BeanDefinition構建對應Bean實例this.finishBeanFactoryInitialization(beanFactory);// 12. 完成refresh,IOC容器創建完成,Spring實例啟動完成this.finishRefresh();} catch (Error | RuntimeException var12) {Throwable ex = var12;if (this.logger.isWarnEnabled()) {this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + ex);}this.destroyBeans();this.cancelRefresh(ex);throw ex;} finally {contextRefresh.end();}} finally {this.startupShutdownThread = null;this.startupShutdownLock.unlock();}}

prepareRefresh

刷新前的預處理,調用 initPropertySources() 方法初始化一些屬性設置,調用 getEnvironment().validateRequiredProperties() 校驗屬性的合法性,設置 earlyApplicationEvents= new LinkedHashSet() 保存容器中較早期的事件。

obtainFreshBeanFactory

獲取 BeanFactory,創建一個刷新的 Bean 工廠,refreshBeanFactory() 并設置容器 ID,然后將創建的 DefaultListableBeanFactory 的對象進行返回。這里將ApplicationContext的ID關聯到BeanFactory。

prepareBeanFactory

BeanFactory 的預準備工作,設置 BeanFactory 的類加載器和表達式解析器,并添加 BeanPostProcessor【ApplicationContextAwareProcessor】,設置自動裝配的接口,添加 BeanPostProcessor。實例化幾個基礎單例,定義了環境信息以及環境啟動必要的組件。

在這里插入圖片描述

postProcessBeanFactory

BeanFactory 準備工作完成后進行的后置處理工作,子類通過重寫這個方法來做進一步的設置。

registerBeanPostProcessors

注冊 BeanPostProcessor(Bean 的后置處理器),不同接口類型的 BeanPostProcessor 在 Bean 創建前后的執行時機是不一樣的。

initMessageSource

初始化 MessageSource 組件, 做國際化功能、消息綁定、消息解析等。

initApplicationEventMulticaster

初始化事件派發器,如果容器中沒有事件派發器,那么就創建一個 SimpleApplicationEventMulticaster 并添加到容器中。

onRefresh

留給子容器(子類),例如在 springboot 中,用來創建 tomcat、jetty 容器并啟動。

registerListeners

給容器中將所有項目里面的 ApplicationListener 注冊進來,并將監聽器注冊到事件派發器中。

finishBeanFactoryInitialization

初始化所有剩下的單實例 bean,這個方法是核心方法,

4.5 Bean生命周期

Spring之Bean的生命周期_checkcandidate方法-CSDN博客

參考

面向對象及其五大基本編程原則[簡潔易懂]_面向對象編程-CSDN博客

面向過程編程和面向對象編程的區別_面向對象編程和面向過程編程的區別-CSDN博客

字節面試雜談——JAVA基礎_character 緩存的范圍是-CSDN博客

Spring 學習3–AOP(面向切面編程)_aop通配符含義-CSDN博客

軟件工程概論—內聚性和耦合性_軟件工程內聚和耦合-CSDN博客

軟件的內聚度和耦合度 - 知乎 (zhihu.com)

設計模式簡談-CSDN博客

代理模式和裝飾器模式的區別-CSDN博客

深度解析Spring框架原理_spring框架原理及流程-CSDN博客

Spring的@ComponentScan注解用法介紹_java_腳本之家 (jb51.net)

IoC & AOP詳解(快速搞懂) | JavaGuide

Spring BeanFactory和ApplicationContext詳解-CSDN博客

Spring之BeanFactory詳解-CSDN博客

Spring系列 BeanDefinitionRegistry解讀(超通俗易懂)-CSDN博客

Spring之Bean的生命周期_checkcandidate方法-CSDN博客

Spring中的refresh方法分析_java_腳本之家 (jb51.net)

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/14718.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/14718.shtml
英文地址,請注明出處:http://en.pswp.cn/web/14718.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【Nodejs-多進程之Cluster】

cluster 模塊是 Node.js 提供的一個用于多進程的模塊&#xff0c;它可以輕松地創建一組共享同一個服務器端口的子進程&#xff08;worker進程&#xff09;。通過使用 cluster 模塊&#xff0c;可以充分利用多核系統&#xff0c;提高應用程序的性能和可靠性。 基本原理 cluste…

#php把pdf文件轉成圖片#

本地環境 系統&#xff1a;win11 64位 環境&#xff1a;phpStudy PHP版本&#xff1a;8.0.2 礦建&#xff1a;laravel 配置擴展 一、安裝imageMagick 下載地址&#xff1a;https://imagemagick.org/script/download.php 安裝版本&#xff1a;ImageMagick-最新版本-Q16-HDRI-x64…

Docker: exec命令淺析

簡介 Docker exec命令是Docker提供的一個強大工具&#xff0c;用于在正在運行的容器中執行命令。在此將介紹Docker exec命令的用法和示例&#xff0c;幫助大家更好地理解和使用這個命令。 Docker是一種流行的容器化平臺&#xff0c;允許用戶在容器中運行應用程序。有時候&#…

React開發環境配置詳細講解-04

React環境 前端隨著規范化&#xff0c;可以說規范和環境插件配置滿天飛&#xff0c;筆者最早接觸的是jquery&#xff0c;那個開發非常簡單&#xff0c;只要引入jquery就可以了&#xff0c;當時還寫了一套UI框架&#xff0c;至今在做小型項目中還在使用&#xff0c;show一張效果…

一款顏值頗高的虛擬列表!差點就被埋沒了,終于還是被我挖出來了

大家好&#xff0c;我是曉衡&#xff01; 今天&#xff0c;推薦一款頗有顏值的虛擬列表組件&#xff0c;不然真的被埋沒就可惜了&#xff01; 我們先來看下效果&#xff1a; 感覺怎么樣&#xff1f;還不錯吧&#xff01; 為什么說這個資源差點被埋沒呢&#xff1f;因為個朋友找…

用數據,簡單點!奇點云2024 StartDT Day數智科技大會,直播見

在充滿挑戰的2024&#xff0c;企業如何以最小化的資源投入和試錯成本&#xff0c;挖掘新的增長機會&#xff0c;實現確定性發展&#xff1f; “簡單點”是當前商業環境的應對策略&#xff0c;也是奇點云2024 StartDT Day的核心理念。 5月28日&#xff0c;由奇點云主辦的2024 S…

Linux —— 信號量

Linux —— 信號量 什么是信號量P操作&#xff08;Wait操作&#xff09;V操作&#xff08;Signal操作&#xff09;信號量的類型 一些接口POSIX 信號量接口&#xff1a;其他相關命令&#xff1a; 基于循環隊列的生產者和消費者模型同步關系 多生產多消費 我們今天接著來學習信號…

【譯】組復制和 Percona XtraDB 集群: 常見操作概述

原文地址&#xff1a;Group Replication and Percona XtraDB Cluster: Overview of Common Operations 在這篇博文中&#xff0c;我將概述使用 MySQL Group Replication 8.0.19&#xff08;又稱 GR&#xff09;和 Percona XtraDB Cluster 8 (PXC)&#xff08;基于 Galera&…

Jetbrains插件AI Assistant,終于用上了

ai assistant激活成功后&#xff0c;如圖 ai assistant獲取&#xff1a;https://web.52shizhan.cn/activity/ai-assistant 主要功能如下

Spring Boot 配置使用 PEM 格式SSL/TLS證書和私鑰

傳統的為 Spring Boot 配置SSL/TLS證書一般都會把證書打包成 JKS(Java KeyStore) 或 PKCS12 (Public Key Cryptographic Standards) 格式&#xff0c;然后為Spring Boot 增加以下類似配置&#xff1a; # The format used for the keystore. It could be set to JKS in case it…

SpringBoot(六)之內嵌容器

SpringBoot&#xff08;六&#xff09;之內嵌容器 文章目錄 SpringBoot&#xff08;六&#xff09;之內嵌容器內嵌容器的特點如何替換默認容器1.pom形式2.主動配置 如何通過配置切換serlvet容器 Spring Boot 提供了一種便捷的方式來創建獨立運行的 Spring 應用程序&#xff0c;…

計算機畢業設計hadoop+spark微博輿情大數據分析 微博爬蟲可視化 微博數據分析 微博采集分析平臺 機器學習(大屏+LSTM情感分析+爬蟲)

電商數據建模 一、分析背景與目的 1.1 背景介紹 電商平臺數據分析是最為典型的一個數據分析賽道&#xff0c;且電商數據分析有著比較成熟的數據分析模型&#xff0c;比如&#xff1a;人貨場模型。此文中我將通過分析國內最大的電商平臺——淘寶的用戶行為&#xff0c;來鞏固數…

算法打卡 Day13(棧與隊列)-滑動窗口最大值 + 前 K 個高頻元素 + 總結

文章目錄 Leetcode 239-滑動窗口最大值題目描述解題思路 Leetcode 347-前 K 個高頻元素題目描述解題思路 棧與隊列總結 Leetcode 239-滑動窗口最大值 題目描述 https://leetcode.cn/problems/sliding-window-maximum/description/ 解題思路 在本題中我們使用自定義的單調隊列…

C語言指針指針和數組筆試題(必看)

前言&#xff1a; 前面介紹了指針的大體內容&#xff0c;如果接下來能夠把這些代碼的含義搞得清清楚楚&#xff0c;那么你就是代碼king&#xff01; 一維數組&#xff1a; int a[] {1,2,3,4}; printf("%d\n",sizeof(a)); printf("%d\n",sizeof(a0)); pr…

element-ui輸入框和多行文字輸入框字體不一樣解決

element-ui的type"textarea"的字體樣式與其他樣式不同 <el-input type"textarea"></el-input> <el-input ></el-input>設置&#xff1a; .el-textarea__inner::placeholder {font-family: "Helvetica Neue", Helvetic…

linux排查思路

1.賬號安全 who 查看當前登錄用戶&#xff08;tty本地登錄pts遠程登錄&#xff09; w 查看系統信息&#xff0c;想知道某一時刻用戶的行為 uptime 查看登錄多久、多少用戶&#xff0c;負載 1.查看用戶信息文件/etc/passwd root:x:0:0:root:/root:/bin:/b…

刪除MySQL中所有表的外鍵

方法一&#xff1a; 原理 查詢schema中所有外鍵名稱然后拼接生成刪除語句 第一步&#xff1a; SELECT CONCAT(ALTER TABLE ,TABLE_SCHEMA,.,TABLE_NAME, DROP FOREIGN KEY ,CONSTRAINT_NAME, ;) FROM information_schema.TABLE_CONSTRAINTS c WHERE c.TABLE_SCHEMA數據庫名…

Vue 跨域代理設置

Vue CLI允許你通過項目根目錄下的vue.config.js文件來定制devServer的配置。以下是一些常見的配置示例&#xff1a; module.exports {devServer: {// 跨域代理配置&#xff0c;解決開發環境API跨域問題proxy: {//匹配以api路徑請求的URL&#xff0c;轉發請求的服務器地址/api…

課時135:awk實踐_邏輯控制_綜合實踐

1.3.8 綜合實踐 學習目標 這一節&#xff0c;我們從 網絡實踐、文件實踐、小結 三個方面來學習 網絡實踐 簡介 所謂的網絡實踐&#xff0c;主要是借助于awk的數組功能&#xff0c;進行站點的信息統計操作。準備網絡環境 安裝軟件 yum install nignx -y重啟nginx [rootloca…