【萬字長文】Spring框架 層層遞進輕鬆入門 (IOC和DI)

(一) 初識 Spring

Spring框架是 Java 平臺的一個開源的全棧(Full-stack)應用程序框架和控制反轉容器實現,通常被直接稱爲 Spring。該框架的一些核心功能理論上可用於任何 Java 應用,但 Spring 還爲基於Java企業版平臺構建的 Web 應用提供了大量的拓展支持。雖然 Spring 沒有直接實現任何的編程模型,但它已經在 Java 社區中廣爲流行,基本上徹底代替了企業級JavaBeans(EJB)模型php

—— 維基百科java

上面的一段引言,基本闡述了這個Spring框架,而一門框架的流行,天然有其必然之處,怎麼理解框架這個詞呢?web

(1) 簡單說說啥是框架

不扯什麼太專業的名詞,我們就講點大白話,你們應該都聽過 「框架是一個半成品」 ,這句話沒毛病,框架就是封裝了不少不少的代碼,而後你只須要直接調用它提供給你的 API 就能直接使用它的功能,省了我們不少功夫。最近閒在家,給你們整個比較通俗的例子——雞蛋灌餅的製做spring

  • 全手工模式:準備300g麪粉加少許鹽,一分爲半,兩側分別加冷、熱水,和成絮狀,加20ml食用油,揉成偏軟的麪糰,蓋蓋子醒10-20分鐘,找一個碗加兩大勺麪粉,澆上熱油,攪拌均勻,製成油酥,醒好的面,切塊,擀成餅狀,包入油酥,用包包子的方式收口,鬆弛5分鐘,擀麪成薄餅,放入加好適量油的平底鍋或者電餅鐺,翻第二面的時候,麪糰會像半個氣球同樣膨脹起來,筷子挑開一個口,倒入攪拌好加了蔥和鹽的雞蛋,而後順便煎一些喜歡的東西,就能夠等麪餅熟了(能夠本身整點辣椒醬,孜然粉塗上)
  • 半成品模式:打開某寶,某東,購買雞蛋灌餅的現成麪皮,直接開火煎,倒入雞蛋,裹入食材就能夠了

使用這個提供好的麪餅,就能夠省去咱們不少功夫,只須要在麪餅上進行加工就能夠了,這和使用框架是一個道理,框架就是一個半成品,即節省了咱們開發的成本,又可讓咱們在這個框架的基礎上進行自定義的加工apache

但願你們能看完個人文章,別光記住了雞蛋灌餅怎麼作(捂臉),畢竟我感受這是一個技術貼編程

撈一下,貼一個弟弟我全手工作的灌餅(狗頭保命)設計模式

我好像說的有點多了,趕忙拉回來,框架的最初意願固然都是爲了簡化開發,幫助開發者快速的完成項目需求,說的確切一點,就是框架中結合了不少的設計模式,可讓你 「動態」 的開發,將代碼實現了通用性,通常本身寫的簡單的代碼,都涉及太多的 「硬編碼」 問題了 ,而若是本身去寫這些設計模式又太複雜了數組

因此,作不了巨人,不如咱們就學習站在巨人的肩膀上! Let's go!緩存

(2) 什麼是耦合?(高/低)

做爲一篇新手都能看懂的文章,開始就一堆 IOC AOP等專業名詞扔出去,好像是不太禮貌,我得把須要鋪墊的知識給你們儘可能說一說,若是對這塊比較明白的大佬,直接略過就OK了安全

耦合,就是模塊間關聯的程度,每一個模塊之間的聯繫越多,也就是其耦合性越強,那麼獨立性也就越差了,因此咱們在軟件設計中,應該儘可能作到低耦合,高內聚

生活中的例子:家裏有一條串燈,上面有不少燈泡,若是燈壞了,你須要將整個燈帶都換掉,這就是高耦合的表現,由於燈和燈帶之間是緊密相連,不可分割的,可是若是燈泡能夠隨意拆卸,並不影響整個燈帶,那麼這就叫作低耦合

代碼中的例子:來看一個多態的調用,前提是 B 繼承 A,引用了不少次

A a = new B();
a.method();
複製代碼

若是你想要把B變成C,就須要修改全部new B() 的地方爲 new C() 這也就是高耦合

若是若是使用咱們今天要說的 spring框架 就能夠大大的下降耦合

A a = BeanFactory().getBean(B名稱);
a.method();
複製代碼

這個時候,咱們只須要將B名稱改成C,同時將配置文件中的B改成C就能夠了

常見的耦合有這些分類:

A: 內容耦合

當一個模塊直接修改或操做另外一個模塊的數據,或者直接轉入另外一個模塊時,就發生了內容耦合。此時,被修改的模塊徹底依賴於修改它的模塊。 這種耦合性是很高的,最好避免

public class A {
    public int numA = 1;
}

public class B {
    public static A a = new A();
    public static void method(){
        a.numA += 1;
    }
    public static void main(String[] args) {
       method();
       System.out.println(a.numA);
    }
}
複製代碼

B: 公共耦合

兩個以上的模塊共同引用一個全局數據項就稱爲公共耦合。大量的公共耦合結構中,會讓你很難肯定是哪一個模塊給全局變量賦了一個特定的值

C: 外部耦合

一組模塊都訪問同一全局簡單變量,並且不經過參數表傳遞該全局變量的信息,則稱之爲外部耦合 從定義和圖中也能夠看出,公共耦合和外部耦合的區別就在於前者是全局數據結構後者是全局簡單變量

D: 控制耦合

控制耦合 。一個模塊經過接口向另外一個模塊傳遞一個控制信號,接受信號的模塊根據信號值而進行適當的動做,這種耦合被稱爲控制耦合,也就是說,模塊之間傳遞的不是數據,而是一些標誌,開關量等等

E: 標記耦合

標記耦合指兩個模塊之間傳遞的是數據機構,如高級語言的數組名、記錄名、文件名等這些名字即爲標記,其實傳遞的是這個數據結構的地址

F: 數據耦合

模塊之間經過參數來傳遞數據,那麼被稱爲數據耦合。數據耦合是最低的一種耦合形 式,系統中通常都存在這種類型的耦合,由於爲了完成一些有意義的功能,每每須要將某些模塊的輸出數據做爲另 一些模塊的輸入數據

G: 非直接耦合

兩個模塊之間沒有直接關係,它們之間的聯繫徹底是經過主模塊的控制和調用來實現的

(3) Spring框架好在哪 ?

  • 下降耦合度:Spring神奇的 IoC 容器,能夠控制對象間的依賴關係,解決了硬編碼問題,讓你的程序變得 「動態且高效」
  • AOP 編程支持:Spring 提供了面向切面編程,能夠很是方便的實現一些權限攔截或運行監控等的功能
  • 方便集成各類優秀框架:Spring 不排斥各類優秀的開源框架,其內部提供了不少優秀框架(Struts、Hibernate、MyBatis、Hessian、Quartz)的直接支持
  • 方便程序測試:Spring 支持 junit4 ,能夠經過註解方便的測試程序
  • 聲明式事務的支持:Spring 幫助咱們從普通的事物管理代碼中解放出來,經過配置就能夠完成對事物的管理
  • 下降 JavaEE API 的使用難度:Spring 將 JavaEE 中一些比較難用的 API (JDBC、JavaMail、遠程調用等) 進行了封裝,使得它們的使用難度大大下降

(4) Spring 框架的結構

講完了Spring框架的一些優勢,如今咱們來看一下,Spring框架的結構,來對咱們要學習的框架有一個總體的認識,下面是一張官方的結構圖

顯而易見,Spring框架是一個分層的架構,根據不一樣的功能,分紅了多個模塊,而這些模塊都是能夠單獨或者組合使用的,下面咱們來簡單的介紹一下每個部分

首先將目光放到 CoreContainer 上,它是 Spring 框架最基本也是最核心的部分,其餘部分的模塊,都是基於這一部分創建的

① 核心容器(CoreContainer)

提供 Spring框架的基本功能,分爲圖中四個模塊,核心容器中重要的組件就是 BeanFactory ,本質就是實現了工廠模式,且它使用了 IoC(控制反轉)模式,將程序的配置以及依賴性規範與實際程序的代碼分開

  • Beans:提供了 BeanFactory,Spring中將管理對象稱做 Bean
  • Core:提供 Spring 框架的基本組成部分,包括咱們首先要學習的 IoC 和 DI
  • Context:訪問定義和配置任何對象的媒介,之前二者爲基礎,ApplicationContext 接口是這部分的重點
  • spEL (Spring Expression Language):一個比較強大的運行時查詢和操做數據的表達式語言

② 數據訪問/集成(Data Access/Integration)

  • JDBC:提供了一個JDBC抽象層,減小了一些重複無聊的JDBC代碼,提高了開發效率

  • ORM:提供了對流行對象關係映射API的集成層 (JPA、JDO、Hibernate、 mybatis )

  • OXM:提供了一個支持對象/XML映射實現的抽象層( JAXB、Castor、XMLBeans、JiBX 、XStrea )

  • JMS:Java消息服務, 包含用於生產和消費消息的功能

  • Transactions:事務模塊,用於支持實現特殊接口和全部的POJO的類的編程和聲明式事物管理

③ Web 模塊

  • Web:提供了基本的 Web 的集成功能,例如多部分文件上傳功能,以及初始化了一個使用了Servlet監聽器和麪向Web應用程序上下文的 IoC 容器,它還包含一個HTTP客戶端和Spring遠程支持的相關部分

  • Servelt:包含 Spring 模型—視圖—控制器 (MVC) ,用來實現Web應用

  • WebSocket:Spring4.0之後新增的模塊,它提供了WebSocket和SocketJS的實現

  • Portlet:就好像是Servlet通常,它提供了Portlet環境下的MVC實現

④ 其他模塊

  • AOP:提供了面向切面編程的能力,容許定義方法攔截器和切入點,按功能分離代碼,下降耦合性,能夠實現一些面向對象編程中不太好實現的功能

  • Aspects:提供與 AspectJ 的繼承,是一個功能強大且成熟的面向切面編程的框架

  • Instrumentation:提供了類工具的支持和類加載器的實現,能夠在特定的應用服務器中使用

  • Messaging: 它提供了對消息傳遞體系結構和協議的支持

  • Test:其支持使用 JUnit 或者 TestNG,能夠實現單元測試,集合測試等測試流程

(二) 分析耦合及改進

首先,咱們簡單的模擬一個對帳戶進行添加的操做,咱們先採用咱們之前經常使用的方式進行模擬,而後再給出改進方案,再引出今天要將的 Spring 框架,能幫助更好的理解這個框架

(1) 之前的程序

首先,按照咱們常規的方式先模擬,咱們先將一套基本流程走下來

A:Service 層

/** * 帳戶業務層接口 */
public interface AccountService {
    void addAccount();
}

/** * 帳戶業務層實現類 */
public class AccountServiceImpl implements AccountService {
	
	private AccountDao accountDao = new AccountDaoImpl();
	
    public void addAccount() {
        accountDao.addAccount();
    }
}
複製代碼

B:Dao 層

/** * 帳戶持久層接口 */
public interface AccountDao {
    void addAccount();
}

/** * 帳戶持久層實現類 */
public class AccountDaoImpl implements AccountDao {

    public void addAccount() {
        System.out.println("添加用戶成功!");
    }
}
複製代碼

C:調用

因爲,咱們建立的Maven工程並非一個web工程,咱們也只是爲了簡單模擬,因此在這裏,建立了一個 Client 類,做爲客戶端,來測試咱們的方法

public class Client {
    public static void main(String[] args) {
		AccountService  as = new AccountServiceImpl();
		as.addAccount();
    }
}
複製代碼

運行的結果,就是在屏幕上輸出一個添加用戶成功的字樣

D:分析:new 的問題

上面的這段代碼,應該是比較簡單也容易想到的一種實現方式了,可是它的耦合性倒是很高的,其中這兩句代碼,就是形成耦合性高的根由,由於業務層(service)調用持久層(dao),這個時候業務層將很大的依賴於持久層的接口(AccountDao)和實現類(AccountDaoImpl)

private AccountDao accountDao = new AccountDaoImpl();

AccountService as = new AccountServiceImpl();
複製代碼

這種經過 new 對象的方式,使得不一樣類之間的依賴性大大加強,其中一個類的問題,就會直接致使出現全局的問題,若是咱們將被調用的方法進行錯誤的修改,或者說刪掉某一個類,執行的結果就是:

編譯期就出現了錯誤,而咱們做爲一個開發者,咱們應該努力讓程序在編譯期不依賴,而運行時才能夠有一些必要的依賴(依賴是不可能徹底消除的)

因此,咱們應該想辦法進行解耦,要解耦就要使調用者被調用者之間沒有什麼直接的聯繫,那麼工廠模式就能夠幫助咱們很好的解決這個問題

應該你們在 JavaWeb 或者 JavaSE的學習中,或多或少是有接觸過工廠這個設計模式的,而工廠模式,咱們簡單提一下,工廠就是在調用者和被調用者之間起一個鏈接樞紐的做用,調用者和被調用者都只與工廠進行聯繫,從而減小了二者之間直接的依賴(若是有一些迷茫的朋友,能夠了解一下這種設計模式)

傳統模式:

工廠模式:

(2) 工廠模式改進

A:BeanFactory

具體怎麼實現呢?在這裏能夠將 serivice 和 dao 均配置到配置文件中去(xml/properties),經過一個類讀取配置文件中的內容,並使用反射技術建立對象,而後存起來,完成這個操做的類就是咱們的工廠

注:在這裏咱們使用了 properties ,主要是爲了實現方便,xml還涉及到解析的一些代碼,相對麻煩一些,不過咱們下面要說的 Spring 就是使用了 xml作配置文件

  • bean.properties:先寫好配置文件,將 service 和 dao 以 key=value 的格式配置好
accountService=cn.ideal.service.impl.AccountServiceImpl
accountDao=cn.ideal.dao.impl.AccountDaoImpl
複製代碼
  • BeanFactory
public class BeanFactory {
    //定義一個Properties對象
    private static Properties properties;
    //使用靜態代碼塊爲Properties對象賦值
    static {
        try{
            //實例化對象
            properties = new Properties();
            //獲取properties文件的流對象
            InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            properties.load(in);
        }catch (Exception e){
            throw  new ExceptionInInitializerError("初始化properties失敗");
        }
    }  
}
複製代碼

簡單的解釋一下這部分代碼(固然還沒寫完):首先就是要將配置文件中的內容讀入,這裏經過類加載器的方式操做,讀入一個流文件,而後從中讀取鍵值對,因爲只須要執一次,因此放在靜態代碼塊中,又由於 properties 對象在後面的方法中還要用,因此寫在成員的位置

接着在 BeanFactory 中繼續編寫一個 getBean 方法其中有兩句核心代碼的意義就是:

  • 經過方法參數中傳入的字符串,找到對應的全類名路徑,實際上也就是經過剛纔獲取到的配置內容,經過key 找到 value值
  • 下一句就是經過 Class 的加載方法加載這個類,實例化後返回
public static Object getBean(String beanName){
    Object bean = null;

    try {
        //根據key獲取value
        String beanPath = properties.getProperty(beanName);
        bean = Class.forName(beanPath).newInstance();
    }catch (Exception e){
        e.printStackTrace();
    }
    return bean;
}
複製代碼

B:測試代碼:

public class Client {
    public static void main(String[] args) {
        AccountService as = 					   (AccountService)BeanFactory.getBean("accountService");
        as.addAccount();
    }
}
複製代碼

C:執行效果:

當咱們按照一樣的操做,刪除掉被調用的 dao 的實現類,能夠看到,這時候編譯期錯誤已經消失了,而報出來的只是一個運行時異常,這樣就解決了前面所思考的問題

咱們應該努力讓程序在編譯期不依賴,而運行時才能夠有一些必要的依賴(依賴是不可能徹底消除的)

C:小總結:

爲何使用工廠模式替代了 new 的方式?

打個比方,在你的程序中,若是一段時間後,你發如今你 new 的這個對象中存在着bug或者不合理的地方,或者說你甚至想換一個持久層的框架,這種狀況下,沒辦法,只能修改源碼了,而後從新編譯,部署,可是若是你使用工廠模式,你只須要從新將想修改的類,單獨寫好,編譯後放到文件中去,只須要修改一下配置文件就能夠了

我分享下我我的精簡下的理解就是:

【new 對象依賴的是具體事物,而不 new 則是依賴抽象事物】

Break it down:

  • 依賴具體事物,這個很好理解,你依賴的是一個具體的,實實在在內容,它與你係相關,因此有什麼問題,都是連環的,可能爲了某個點,咱們須要修改 N 個地方,絕望
  • 依賴抽象事物,你所調用的並非一個直接就能夠觸手可及的東西,是一個抽象的概念,因此不存在上面那種狀況下的連環反應

D:再分析:

到這裏,彷佛還不錯,不過咱們的程序還可以繼續優化! 來分析一下:

首先在測試中,多打印幾回,工廠所建立出的對象,咱們寫個for循環打印下

for(int i = 0; i < 4; i++){
	AccountService as = (AccountService)BeanFactory.getBean("accountService");
	System.out.println(as);
}
複製代碼

看下結果:特別顯眼的四次輸出,咱們的問題也就出來了,我所建立的4個對象是不一樣的,也就是說,每一次調用,都會實例化一個新的對象,這也叫作多例

這有什麼問題嗎?

  • :屢次建立對象的代價就是消耗性能,致使效率會低一些

  • :相比較單例,jvm會回收較多的垃圾

  • :獲取速度比單例慢,由於單例除了第一次,其後都是從緩存中獲取

因此,咱們要試着將它改爲單例的,單例從表現上來看,咱們查詢到的對象都應該是一個

(3) 多例->單例之再改進

A:分析:

前面咱們每一次調用都要將類進行 newInstance(),也就是實例化,想要再也不建立新的對象,只須要將咱們第一次建立的對象,在建立後就存到一個集合(容器)中,因爲咱們有查詢的需求因此在 Map 和 List 中選擇了 Map

B:代碼:

簡單解讀一下:

  • 首先在成員位置定義一個 Map,稱做beans,至於實例化就不說了

  • 經過 keys 方法,取出全部的 配置中全部的key,而後進行遍歷出每個key

  • 經過每一個 key 從配置中取出對應的 value 在這裏就是對應類的全類名

  • 將每一個取出的 value,使用反射建立出對象 obj

  • 將 key 與 obj 存入Map容器

  • 在 getBean 方法中只須要從 Map中取就能夠了

public class BeanFactory {
    //定義一個Properties對象
    private static Properties properties;
    //定義Map,做爲存放對象的容器
    private static Map<String, Object> beans;

    //使用靜態代碼塊爲Properties對象賦值
    static {
        try {
            //實例化對象
            properties = new Properties();
            //獲取properties文件的流對象
            InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            properties.load(in);
            //實例化容器
            beans = new HashMap<String, Object>();
            //取出全部key
            Enumeration keys = properties.keys();
            //遍歷枚舉
            while (keys.hasMoreElements()) {
                String key = keys.nextElement().toString();
                //根據獲取到的key獲取對應value
                String beanPath = properties.getProperty(key);
                //反射創對象
                Object obj = Class.forName(beanPath).newInstance();
                beans.put(key, obj);
            }

        } catch (Exception e) {
            throw new ExceptionInInitializerError("初始化properties失敗");
        }
    }

    public static Object getBean(String beanName) {
        return beans.get(beanName);
    }
}
複製代碼

C:執行效果:

測試結果已經變成了單例的

D:單例的劣勢:

單例一個很明顯的問題,就是在併發狀況下,可能會出現線程安全問題

由於因爲單例狀況下,對象只會被實例化一次,這也就說,全部請求都會共享一個 bean 實例,若一個請求改變了對象的狀態,同時對象又處理別的請求,以前的請求形成的對象狀態改變,可能會影響在操做時,對別的請求作了錯誤的處理

舉個簡單的例子幫助理解:

修改一下 dao 的實現類

public class AccountDaoImpl implements AccountDao {
	//定義一個類成員
    private int i = 1;

    public void addAccount() {
        System.out.println("添加用戶成功!");
        System.out.println(i);
        i++;
    }
}
複製代碼

測試中依舊是哪一個循環,不過此次執行一下 addAccount() 方法

經過測試能夠看到,單例的狀況下,我在dao實現類中 添加了一個類成員 i,而後在方法中對其進行累加並輸出操做,每個值都會被修改,這就出現了咱們擔憂的問題

可是回顧咱們從前的編程習慣,彷佛咱們從未在 service 或 dao 中書寫過 類成員,並在方法中對其進行操做,咱們通常都是在方法內定義,而這種習慣,也保證了咱們如今不會出現這樣的問題

將變量定義到方法內

public class AccountDaoImpl implements AccountDao {
    public void addAccount() {
        int i = 1;
        System.out.println("添加用戶成功!");
        System.out.println(i);
        i++;
    }
}
複製代碼

測試一下

好了這樣就沒有問題了!

講這麼多,就是爲了配合 Spring 的學習,前面咱們使用工廠模式對傳統的程序進行了改造,程序再也不與衆多資源等直接聯繫,而是經過工廠進行提供分配,這種被動接受獲取對象的方式就是控制反轉,也是它的核心之一,如今就能夠開始進入正題了:

(三) 控制反轉 -IOC

如今咱們就正式開始進入到 Spring 框架的學習中去,而在這部分,並非說作增刪改查,而是經過 Spring 解決依賴的問題,這也就是咱們上面衆多鋪墊內容的緣由

因爲咱們使用的是 maven 建立出一個普通的 java 工程就能夠了,不須要建立 java web工程,固然若是不是使用 maven的朋友能夠去官網下載jar包 將須要的 bean context core spel log4j 等放到lib中

(1) 第一個入門程序

仍是使用前面這個帳戶的案例,具體的一些接口等等仍是用前面的 將第二大點的時候,我已經貼出來了

首先在 maven 中導入須要內容的座標

A:Maven 導入座標

  • pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>cn.ideal</groupId>
    <artifactId>spring_02_ioc</artifactId>
    <version>1.0-SNAPSHOT</version>
    
    <packaging>jar</packaging>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
    </dependencies>
    
</project>
複製代碼

B:添加配置文件

  • bean.xml
    • 引入頭部文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
複製代碼
  • bean.xml
    • 使用spring管理對象建立 (在beans標籤中添加 bean標籤)
    • 也就是說在配置文件中,對service和dao進行配置
      • id:對象的惟一標識
      • class:指定要建立的對象的全限定類名
<!--把對象的建立交給spring來管理-->
<bean id="accountService" class="cn.ideal.service.impl.AccountServiceImpl"></bean>
<bean id="accountDao" class="cn.ideal.dao.impl.AccountDaoImpl"></bean>
複製代碼

C:測試代碼

爲何用這些,等運行後說,先讓程序跑起來

public class Client {
    public static void main(String[] args) {
        //獲取核心容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //根據id後去Bean對象,下面兩種方式均可以
        AccountService as = (AccountService)ac.getBean("accountService");
        AccountDao ad = ac.getBean("accountDao", AccountDao.class);
        System.out.println(as);
        System.out.println(ad);
    }
}
複製代碼

D:執行效果

程序運行起來是沒有問題的,到這裏一個入門例程就跑起來了

(2) ApplicationContext

首先咱們來分析一下在調用時的一些內容,測試時,第一個內容,就是獲取核心容器,經過了一個 ApplicationContext 進行接收,那麼它是什麼呢

A:與 BeanFactory 的區別

首先看一下這個圖

能夠看到 BeanFactory 纔是 Spring 管理 Bean 的頂級接口,它提供了實例化對象和取出對象的功能,可是因爲BeanFactory的簡單與一些侷限性,有時候並非很適合於大型企業級的開發,所以,Spring提供了一個新的內容也就是 ApplicationContext:它是一個更加高級的容器,而且功能更加分豐富

在使用時最明顯的一個區別就是:二者建立對象的時間點不同

  • ApplicationContext:單例對象適用採用此接口

    • 構建核心容器時,建立對象時採用當即加載的方式。即:只要一讀取完配置文件立刻就建立配置文件中配置的對象
  • BeanFactory:多例對象適合

    • 構建核心容器時,建立對象時採用延遲加載的方式。即:何時根據id獲取對象,何時才真正的建立對象

下面是使用 BeanFactory 進行測試的代碼,不過有一些方法已通過時了,給你們參考使用,可使用打斷點的方式進行測試

Resource resource = new ClassPathResource("bean.xml");
BeanFactory factory = new XmlBeanFactory(resource);
AccountService as  = (AccountService)factory.getBean("accountService");
System.out.println(as);
複製代碼

B:三個實現類

查看 ApplicationContext 的實現類咱們要說的就是紅框中的幾個

  • ClassPathXmlApplicationContext:能夠加載類路徑下的配置文件,固然配置文件必須在類路徑下(用的更多)
    複製代碼
  • FileSystemXmlApplicationContext:能夠加載磁盤任意路徑下的配置文件(有磁盤訪問權限)
  • AnnotationConfigApplicationContext:讀取註解建立容器
    複製代碼

咱們因爲這篇文章中並無說註解的問題,因此咱們先只看前兩個

ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
複製代碼
ApplicationContext ac = new FileSystemXmlApplicationContext("D:\\bean.xml");
複製代碼

(3) bean標籤 以及一些小細節

配置文件中的bean標籤,它的做用是配置對象,方便 spring進行建立,介紹一下其中的經常使用屬性

  • id:對象的惟一標識

  • class:指定要建立的對象的全限定類名

  • scope:指定對象的做用範圍

  • singleton:單例的(默認)

  • prototype:多例的

  • request:WEB 項目中,Spring 建立 Bean 對象,將對象存入到 request 域中

  • session:WEB 項目中,Spring 建立 Bean 的對象,將對象存入到 session 域中

    global session:WEB 項目中, Portlet 環境使用,若沒有 Portlet 環境那麼globalSession 至關於 session

  • init-method:指定類中的初始化方法名稱

  • destroy-method:指定類中銷燬方法名稱

在Spring 中默認是單例的,這也就是咱們在前面的自定義工廠過程當中所作的,在Spring中還須要說明,補充一下:

做用範圍

  • 單例對象:在一個應用中只會有一個對象的實例,它的做用範圍就是整個引用
  • 多例對象:每一次訪問調用對象,會從新建立對象的實例

生命週期

  • 單例對象:建立容器時出生,容器在則活着,銷燬容器時死亡
  • 多例對象:使用對象時出生,堆在在則或者,當對象長時間不使用,被垃圾回收回收時死亡

(4) 實例化 Bean 的三種方式

  • ①:使用默認無參構造函數
    • 根據默認無參構造函數來建立類對象,若沒有無參構造函數,則會建立失敗
<bean id="accountService" class="cn.ideal.service.impl.AccountServiceImpl"></bean>
複製代碼

​ 在某些狀況下,例如咱們想要使用一些別人封裝好的方法,頗有可能存在於jar包中,而且都是 一些字節碼文件,咱們是沒有修改的權利了,那這時候咱們想要使用還可使用下面兩種方法

  • ②:Spring 實例工廠

    • 使用普通工廠中的方法建立對象,存入Spring
      • id:指定實例工廠中 bean 的 id
      • class:實例工廠的全限定類名
      • factory-method:指定實例工廠中建立對象的方法

    模擬一個實例工廠,建立業務層實現類,這種狀況下,必須先有工廠實例對象,才能調用方法

public class InstanceFactory {
	public AccountService createAccountService(){
		return new AccountServiceImpl();
	} 
}
複製代碼
<bean id="instancFactory" class="cn.ideal.factory.InstanceFactory"></bean> 
<bean id="accountService"factory-bean="instancFactory"factory-method="createAccountService"></bean>
複製代碼
  • ③:Spring 靜態工廠
    • 使用工廠中的靜態方法建立對象
      • id:指定 bean id
      • class:靜態工廠的全限定類名
      • factory-method:指定生產對象的靜態方法、
public class StaticFactory {
	public static IAccountService createAccountService(){
		return new AccountServiceImpl();
	} 
}
複製代碼
<bean id="accountService"class="cn.ideal.factory.StaticFactory" factory-method="createAccountService"></bean>
複製代碼

(四) 依賴注入

控制反轉(IoC)是一種思想,而依賴注入(Dependency Injection)則是實現這種思想的方法

其實泛概念上二者是接近的,能夠簡單的理解爲一個概念的不一樣角度描述

咱們前面寫程序的時候,經過控制反轉,使得 Spring 能夠建立對象,這樣減低了耦合性,可是每一個類或模塊之間的依賴是不可能徹底消失的,而這種依賴關係,咱們能夠徹底交給 spring 來維護

這種注入方式有三種,先來看第一種

(1) 構造函數注入

這一種的前提就是:類中必須提供一個和參數列表相對應的構造函數

看個例子就清楚了

咱們就在 service 中建立幾個成員,而後給出其對應的帶參構造,以及添加一個方法

/** * 帳戶業務層實現類 */
public class AccountServiceImpl implements AccountService {

    private String username;
    private Integer age;
    private Date birthday;

    public AccountServiceImpl(String username, Integer phone, Date birthday) {
        this.username = username;
        this.age = phone;
        this.birthday = birthday;
    }

    public void addAccount() {
        System.out.println("username: " + username
                + ", phone: " + age
                + ", birthday: " + birthday);
    }
}
複製代碼

添加配置,這裏先運行,再解釋

<bean id="accountService" class="cn.ideal.service.impl.AccountServiceImpl">
	<constructor-arg name="username" value="湯姆"></constructor-arg>
    <constructor-arg name="phone" value="21"></constructor-arg>
    <constructor-arg name="birthday" ref="nowdt"></constructor-arg>
</bean>
<bean id="nowdt" class="java.util.Date"></bean>
複製代碼

測試後,成功的獲取到了這些值,而且根據方法內的格式,打印到了屏幕上

username: 湯姆, phone: 21, birthday: Sat Feb 15 16:09:00 CST 2020

看完這個例子,好像有點明白了,上面所作的不就是,使用類的構造函數給成員變量進行賦值,但特別的是,這裏是經過配置,使用 Spring 框架進行注入

來講一下所涉及到的標籤:

  • constructor-arg(放在 bean 標籤內) 再說一說其中的屬性值
    • 給誰賦值:
      • index:指定參數在構造函數參數列表的索引位置
      • type:指定參數在構造函數中的數據類型
      • name:指定參數在構造函數中的名稱(更經常使用)
    • 賦什麼值:
      • value:這裏能夠寫基本數據類型和 String
      • ref:這裏能夠引入另外一個bean,幫助咱們給其餘類型賦值(例如文中 birthday )

(2) set 注入(經常使用)

顧名思義,咱們將前面的構造函數先註釋掉,而後補充成員變量的 set 方法

在配置的時候,須要修改

<bean id="accountService"class="cn.ideal.service.impl.AccountServiceImpl">
	<property name="username" value="湯姆"></property>
	<property name="age" value="21"></property>
	<property name="birthday" ref="nowdt"></property>
</bean>
<bean id="nowdt" class="java.util.Date"></bean>
複製代碼
  • property
    • name:與成員變量名無關,與set方法後的名稱有關,例如 setUsername() 獲取到的就是username,而且已經小寫了開頭
    • value:這裏能夠寫基本數據類型和 String
    • ref:這裏能夠引入另外一個bean,幫助咱們給其餘類型賦值(例如文中 birthday )

在這裏,還有一種方式就是使用 p名稱空間注入數據 (本質仍是set)

頭部中須要修改引入這一句

xmlns:p="http://www.springframework.org/schema/p"

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
 
<bean id="accountService" class="cn.ideal.service.impl.AccountServiceImpl" p:name="湯姆" p:age="21" p:birthday-ref="nowdt"/>
<bean id="nowdt" class="java.util.Date"></bean>
</beans>
複製代碼

(3) 注入集合屬性

爲了演示這些方式,咱們在成員中將常見的一些集合都寫出來,而後補充其 set 方法

private String[] strs;
private List<String> list;
private Set<String> set;
private Map<String,String> map;
private Properties props;
複製代碼

在配置中也是很簡單的,只須要按照下列格式寫標籤就能夠了,能夠本身測試一下

<bean id="accountService" class="cn.ideal.service.impl.AccountServiceImpl">
	<property name="strs">
		<array>
            <value>張三</value>
            <value>李四</value>
            <value>王五</value>
        </array>
    </property>

    <property name="list">
        <list>
            <value>張三</value>
            <value>李四</value>
            <value>王五</value>
        </list>
    </property>

    <property name="set">
        <set>
            <value>張三</value>
            <value>李四</value>
            <value>王五</value>
        </set>
    </property>

    <property name="map">
        <map>
            <entry key="name" value="張三"></entry>
            <entry key="age" value="21"></entry>
        </map>
    </property>

    <property name="props">
        <props>
            <prop key="name">張三</prop>
            <prop key="age">21</prop>
        </props>
    </property>
 </bean>
複製代碼

(五) 總結

寫到這裏,這部分Spring框架的入門內容就結束了,先簡單提了下 Spring 框架的基本知識,一個是因爲爲了能讓你們詳細的理解 Spring 爲咱們帶來的好處,二呢是我寫文章的出發點,是想讓全部剛接觸 Spring 框架的人均可以看得懂,因此首先,先從耦合的一些概念,而後分析到咱們傳統程序中存在的耦合問題,接着用單例改進這個程序,實際上這都是爲了向 Spring 靠近,接下來就是真正的上手,使用Spring框架,瞭解了其 控制反轉(IoC)和依賴注入(DI)技術 ,不是很複雜,主要是想用一種按部就班的方式,一步一步入門,不知不覺寫了都快1w字了

很是但願能給你們提供一些幫助!

或許你掉進了黑暗,我惟一能作的,就是走進黑暗,陪你慢慢走出來

(六) 結尾

若是文章中有什麼不足,歡迎你們留言交流,感謝朋友們的支持!

若是能幫到你的話,那就來關注我吧!若是您更喜歡微信文章的閱讀方式,能夠關注個人公衆號

在這裏的咱們素不相識,卻都在爲了本身的夢而努力 ❤

一個堅持推送原創開發技術文章的公衆號:理想二旬不止

相關文章
相關標籤/搜索