什么是spring框架?spring框架到底有什么用?spring框架到底做了些什么?


什么是spring框架,spring框架究竟有什么用呢?我們可以用spring框架來做些什么呢?這是我今天要說的內容。

當然,百度spring框架會出現一大堆spring框架的介紹,以及IOC和AOP。但是這些官方的語言,看書都有解釋,關鍵是我不知道為什么要用spring,spring框架和不用框架到底區別在哪,還有它到底是通過什么來表明我用的框架就是spring框架的?

spring很抽象,spring是框架,框架的主要目的是什么呢?大概所有框架的目的都一樣吧,那就是簡化開發。而它存在的目的也是為了簡化java開發。

它是怎樣來簡化開發的呢?讓我們看看spring采取的關鍵策略。

  • 基於POJO的輕量級和最小侵入性編程;
  • 通過依賴注入和面向接口實現松耦合;
  • 基於切面和慣例進行聲明式編程;
  • 通過切面和模板減少樣板式代碼。

這就是spring框架的四種關鍵策略。我們下面詳細談談。

spring框架的最小侵入性

Spring竭力避免因自身的API而弄亂你的應用代碼。 Spring不會強迫你實現Spring規范的接口或繼承Spring規范的類, 相反, 在基於Spring構建的應用中, 它的類通常沒有任何痕跡表明你使用了Spring。只能通過xml配置文件來看出spring的思想。

最壞的場景是, 一個類或許會使用Spring注解, 但它依舊是POJO。

那可能有人要問了,不侵入如何實現spring框架強大的功能,spring框架不是很強大嗎?在這里我要說明,spring框架是很強大,但它的強大在於它的理念,它的思想,而不是它實現了多強大的功能。

當然不侵入有不侵入的解決方法,在我的理解里它的解決方法也算是spring的核心吧!那就是配置文件或者注解。

讓我們來看一個javabean。它是一個普通的java類。

  1.  
    public class exampleBean(){
  2.  
    public String testPrint(){
  3.  
    System.out.println( "===test===");
  4.  
    }
  5.  
    }

Spring的非入侵式就是不強制類要實現Spring的任何接口或類,沒有任何地方表明它是一個Spring組件。 意味着這個類在Spring應用和非Spring應用中都可以發揮同樣的作用。

那這個類在不耦合的情況下是如何發揮作用的呢?這就要提到spring框架的DI(依賴注入)了。

依賴注入

任何一個有實際意義的應用都會由兩個或者更多的類組成, 這些類相互之間進行協作來完成特定的業務邏輯。 按照傳統的做法, 每個對象負責管理與自己相互協作的對象(即它所依賴的對象) 的引用, 這將會導致高度耦合和難以測試的代碼。而spring框架利用依賴注入恰恰解決了這一難題。

耦合具有兩面性 。 一方面, 緊密耦合的代碼難以測試、 難以復用、 難以理解, 並且典型地表現出“打地鼠”式的bug特性 。 另一方面, 一定程度的耦合又是必須的——完全沒有耦合的代碼什么也做不了。 為了完成有實際意義的功能, 不同的類必須以適當的方式進行交互。 總而言之, 耦合是必須的, 但應當被小心謹慎地管理。

  1.  
    public void DealData(){
  2.  
    public Data data;
  3.  
    public void setData(Data data){
  4.  
    this.data=data;
  5.  
    }
  6.  
    public void add(){
  7.  
    data.add();
  8.  
    }
  9.  
     
  10.  
    }

如上圖,DealData類和Data類耦合了。

通過DI, 對象的依賴關系將由系統中負責協調各對象的第三方組件在創建對象的時候進行設定。 對象無需自行創建或管理它們的依賴關系。

應用切面

DI能夠讓相互協作的軟件組件保持松散耦合, 而面向切面編程(aspect-oriented programming, AOP) 允許你把遍布應用各處的功能分離出來形成可重用的組件。

面向切面編程往往被定義為促使軟件系統實現關注點的分離一項技術。 系統由許多不同的組件組成, 每一個組件各負責一塊特定功能。 除了實現自身核心的功能之外, 這些組件還經常承擔着額外的職責。 諸如日志、 事務管理和安全這樣的系統服務經常融入到自身具有核心業務邏輯的組件中去, 這些系統服務通常被稱為橫切關注點, 因為它們會跨越系統的多個組件。

如果將這些關注點分散到多個組件中去, 你的代碼將會帶來雙重的復雜性。

  • 實現系統關注點功能的代碼將會重復出現在多個組件中。 這意味着如果你要改變這些關注點的邏輯, 必須修改各個模塊中的相關實現。
  • 即使你把這些關注點抽象為一個獨立的模塊, 其他模塊只是調用它的方法, 但方法的調用還是會重復出現在各個模塊中。
  • 組件會因為那些與自身核心業務無關的代碼而變得混亂。 一個向地址簿增加地址條目的方法應該只關注如何添加地址, 而不應該關注它是不是安全的或者是否需要支持事務

圖1.2展示了這種復雜性。 左邊的業務對象與系統級服務結合得過於緊密。 每個對象不但要知道它需要記日志、 進行安全控制和參與事務, 還要親自執行這些服務。

image.png

在整個系統內, 關注點(例如日志和安全)的調用經常散布到各個模塊中, 而這些關注點並不是模塊的核心業務

AOP能夠使這些服務模塊化, 並以聲明的方式將它們應用到它們需要影響的組件中去。 所造成的結果就是這些組件會具有更高的內聚性並且會更加關注自身的業務, 完全不需要了解涉及系統服務所帶來復雜性。 總之, AOP能夠確保POJO的簡單性。

如圖1.3所示, 我們可以把切面想象為覆蓋在很多組件之上的一個外殼。 應用是由那些實現各自業務功能的模塊組成的。 借助AOP, 可以使用各種功能層去包裹核心業務層。 這些層以聲明的方式靈活地應用到系統中, 你的核心應用甚至根本不知道它們的存在。 這是一個非常強大的理念, 可以將安全、 事務和日志關注點與核心業務邏輯相分離。

image.png

利用AOP, 系統范圍內的關注點覆蓋在它們所影響組件之上

為了示范在Spring中如何應用切面, 讓我們重新回到騎士的例子, 並為它添加一個切面。

每一個人都熟知騎士所做的任何事情, 這是因為吟游詩人用詩歌記載了騎士的事跡並將其進行傳唱。 假設我們需要使用吟游詩人這個服務類來記載騎士的所有事跡。 程序清單1.9展示了我們會使用的Minstrel類。

  1.  
    1
  2.  
    2
  3.  
    3
  4.  
    4
  5.  
    5
  6.  
    6
  7.  
    7
  8.  
    8
  9.  
    9
  10.  
    10
  11.  
    11
  12.  
    12
  13.  
    13
  14.  
    14
  15.  
    15
  16.  
    16
  17.  
    17
  18.  
    18
  19.  
    19
  20.  
    20
  21.  
    21
  22.  
    22
  1.  
    package sia.knights;
  2.  
     
  3.  
    import java.io.PrintStream;
  4.  
     
  5.  
    public class Minstrel {
  6.  
     
  7.  
    private PrintStream stream;
  8.  
     
  9.  
    public Minstrel(PrintStream stream) {
  10.  
    this.stream = stream;
  11.  
    }
  12.  
     
  13.  
    public void singBeforeQuest() {
  14.  
    stream.println("Fa la la, the knight is so brave!");
  15.  
    }
  16.  
     
  17.  
    public void singAfterQuest() {
  18.  
    stream.println("Tee hee hee, the brave knight " +
  19.  
    "did embark on a quest!");
  20.  
    }
  21.  
     
  22.  
    }

正如你所看到的那樣, Minstrel是只有兩個方法的簡單類。 在騎士執行每一個探險任務之前, singBeforeQuest()方法會被調用; 在騎士完成探險任務之后, singAfterQuest()方法會被調用。 在這兩種情況下, Minstrel都會通過一個PrintStream類來歌頌騎士的事跡, 這個類是通過構造器注入進來的。

把Minstrel加入你的代碼中並使其運行起來, 這對你來說是小事一樁。 我們適當做一下調整從而讓BraveKnight可以使用Minstrel。 程序清單1.10展示了將BraveKnight和Minstrel組合起來的第一次嘗試。

  1.  
    1
  2.  
    2
  3.  
    3
  4.  
    4
  5.  
    5
  6.  
    6
  7.  
    7
  8.  
    8
  9.  
    9
  10.  
    10
  11.  
    11
  12.  
    12
  13.  
    13
  14.  
    14
  15.  
    15
  16.  
    16
  17.  
    17
  18.  
    18
  19.  
    19
  1.  
    package sia.knights;
  2.  
     
  3.  
    public class BraveKnight implements Knight {
  4.  
     
  5.  
    private Quest quest;
  6.  
    private Minstrel minstrel;
  7.  
     
  8.  
    public BraveKnight(Quest quest, Minstrel minstrel) {
  9.  
    this.quest = quest;
  10.  
    this.minstrel = minstrel;
  11.  
    }
  12.  
     
  13.  
    public void embarkOnQuest() {
  14.  
    minstrel.singBeforeQuest();
  15.  
    quest.embark();
  16.  
    minstrel.singAfterQuest();
  17.  
    }
  18.  
     
  19.  
    }

這應該可以達到預期效果。 現在, 你所需要做的就是回到Spring配置中, 聲明Minstrel bean並將其注入到BraveKnight的構造器之中。

但是, 請稍等……

我們似乎感覺有些東西不太對。 管理他的吟游詩人真的是騎士職責范圍內的工作嗎? 在我看來, 吟游詩人應該做他份內的事, 根本不需要騎士命令他這么做。 畢竟, 用詩歌記載騎士的探險事跡, 這是吟游詩人的職責。 為什么騎士還需要提醒吟游詩人去做他份內的事情呢?此外, 因為騎士需要知道吟游詩人, 所以就必須把吟游詩人注入到BarveKnight類中。 這不僅使BraveKnight的代碼復雜化了, 而且還讓
我疑惑是否還需要一個不需要吟游詩人的騎士呢? 如果Minstrel為null會發生什么呢? 我是否應該引入一個空值校驗邏輯來覆蓋該場景?

簡單的BraveKnight類開始變得復雜, 如果你還需要應對沒有吟游詩人時的場景, 那代碼會變得更復雜。 但利用AOP, 你可以聲明吟游詩人必須歌頌騎士的探險事跡, 而騎士本身並不用直接訪問Minstrel的方法。

要將Minstrel抽象為一個切面, 你所需要做的事情就是在一個Spring配置文件中聲明它。 程序清單1.11是更新后的knights.xml文件, Minstrel被聲明為一個切面。

  1.  
    1
  2.  
    2
  3.  
    3
  4.  
    4
  5.  
    5
  6.  
    6
  7.  
    7
  8.  
    8
  9.  
    9
  10.  
    10
  11.  
    11
  12.  
    12
  13.  
    13
  14.  
    14
  15.  
    15
  16.  
    16
  17.  
    17
  18.  
    18
  19.  
    19
  20.  
    20
  21.  
    21
  22.  
    22
  23.  
    23
  24.  
    24
  25.  
    25
  26.  
    26
  27.  
    27
  28.  
    28
  29.  
    29
  30.  
    30
  31.  
    31
  32.  
    32
  33.  
    33
  34.  
    34
  35.  
    35
  1.  
    <?xml version="1.0" encoding="UTF-8"?>
  2.  
    <beans xmlns="http://www.springframework.org/schema/beans"
  3.  
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.  
    xmlns:aop="http://www.springframework.org/schema/aop"
  5.  
    xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
  6.  
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  7.  
     
  8.  
    <bean id="knight" class="sia.knights.BraveKnight">
  9.  
    <constructor-arg ref="quest" />
  10.  
    </bean>
  11.  
     
  12.  
    <bean id="quest" class="sia.knights.SlayDragonQuest">
  13.  
    <constructor-arg ref="fakePrintStream" />
  14.  
    </bean>
  15.  
     
  16.  
    <bean id="minstrel" class="sia.knights.Minstrel">
  17.  
    <constructor-arg ref="fakePrintStream" />
  18.  
    </bean>
  19.  
     
  20.  
    <bean id="fakePrintStream" class="sia.knights.FakePrintStream" />
  21.  
     
  22.  
    <aop:config>
  23.  
    <aop:aspect ref="minstrel">
  24.  
    <aop:pointcut id="embark"
  25.  
    expression="execution(* *.embarkOnQuest(..))"/>
  26.  
     
  27.  
    <aop:before pointcut-ref="embark"
  28.  
    method="singBeforeQuest"/>
  29.  
     
  30.  
    <aop:after pointcut-ref="embark"
  31.  
    method="singAfterQuest"/>
  32.  
    </aop:aspect>
  33.  
    </aop:config>
  34.  
     
  35.  
    </beans>

這里使用了Spring的aop配置命名空間把Minstrel bean聲明為一個切面。 首先, 需要把Minstrel聲明為一個bean, 然后在元素中引用該bean。 為了進一步定義切面, 聲明(使用) 在embarkOnQuest()方法執行前調用Minstrel的singBeforeQuest()方法。 這種方式被稱為前置通知(before advice) 。 同時聲明(使用)在embarkOnQuest()方法執行后調用singAfter Quest()方法。 這種方式被稱為后置通知(after advice) 。

首先, Minstrel仍然是一個POJO, 沒有任何代碼表明它要被作為一個切面使用。 當我們按照上面那樣進行配置后, 在Spring的上下文中, Minstrel實際上已經變成一個切面了。

其次, 也是最重要的, Minstrel可以被應用到BraveKnight中, 而BraveKnight不需要顯式地調用它。 實際上, BraveKnight完全不知道Minstrel的存在。

必須還要指出的是, 盡管我們使用Spring魔法把Minstrel轉變為一個切面, 但首先要把它聲明為一個Spring bean。 能夠為其他Spring bean做到的事情都可以同樣應用到Spring切面中, 例如為它們注入依賴。

使用模板消除樣板式代碼

你是否寫過這樣的代碼, 當編寫的時候總會感覺以前曾經這么寫過? 我的朋友, 這不是似曾相識。 這是樣板式的代碼(boilerplate code) 。 通常為了實現通用的和簡單的任務, 你不得不一遍遍地重復編寫這樣的代碼。

遺憾的是, 它們中的很多是因為使用Java API而導致的樣板式代碼。 樣板式代碼的一個常見范例是使用JDBC訪問數據庫查詢數據。 舉個例子, 如果你曾經用過JDBC, 那么你或許會寫出類似下面的代碼。

image.png

正如你所看到的, 這段JDBC代碼查詢數據庫獲得員工姓名和薪水。 我打賭你很難把上面的代碼逐行看完, 這是因為少量查詢員工的代碼淹沒在一堆JDBC的樣板式代碼中。 首先你需要創建一個數據庫連接, 然后再創建一個語句對象, 最后你才能進行查詢。

為了平息JDBC可能會出現的怒火, 你必須捕捉SQLException, 這是一個檢查型異常, 即使它拋出后你也做不了太多事情。

最后, 畢竟該說的也說了, 該做的也做了, 你不得不清理戰場, 關閉數據庫連接、 語句和結果集。 同樣為了平息JDBC可能會出現的怒火, 你依然要捕SQLException。

程序清單1.12中的代碼和你實現其他JDBC操作時所寫的代碼幾乎是相同的。 只有少量的代碼與查詢員工邏輯有關系, 其他的代碼都是JDBC的樣板代碼。

JDBC不是產生樣板式代碼的唯一場景。 在許多編程場景中往往都會導致類似的樣板式代碼, JMS、 JNDI和使用REST服務通常也涉及大量的重復代碼。

Spring旨在通過模板封裝來消除樣板式代碼。 Spring的JdbcTemplate使得執行數據庫操作時, 避免傳統的JDBC樣板代碼成為了可能。

舉個例子, 使用Spring的JdbcTemplate(利用了 Java 5特性的JdbcTemplate實現) 重寫的getEmployeeById()方法僅僅關注於獲取員工數據的核心邏輯, 而不需要迎合JDBC API的需求。 程序清單1.13展示了修訂后的getEmployeeById()方法。

image.png

正如你所看到的, 新版本的getEmployeeById()簡單多了, 而且僅僅關注於從數據庫中查詢員工。 模板的queryForObject()方法需要一個SQL查詢語句, 一個RowMapper對象(把數據映射為一個域對象) , 零個或多個查詢參數。 GetEmp loyeeById()方法再也看不到以前的JDBC樣板式代碼了, 它們全部被封裝到了模板中。

我已經向你展示了Spring通過面向POJO編程、 DI、 切面和模板技術來簡化Java開發中的復雜性。 在這個過程中, 我展示了在基於XML的配置文件中如何配置bean和切面。

但這些文件是如何加載的呢? 它們被加載到哪里去了? 讓我們再了解下Spring容器, 這是應用中的所有bean所駐留的地方。下次聊。

參考自:https://blog.csdn.net/huanghanqian/article/details/79340762


免責聲明!

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



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