Spring 學(xué)習(xí)筆記(一) 初識(shí) Spring

第一章 Spring 之旅

簡(jiǎn)化 Java 開發(fā)

Spring是為了解決企業(yè)級(jí)應(yīng)用開發(fā)的復(fù)雜性而創(chuàng)建的肩杈,使用 Spring 可以讓簡(jiǎn)單的 JavaBean 實(shí)現(xiàn)之前只有 EJB 才能完成的事情。但 Spring 不僅僅局限于服務(wù)器端開發(fā)艘儒,任何 Java 應(yīng)用都能在簡(jiǎn)單性界睁、可測(cè)試性和松耦合等方面從 Spring 中獲益兵拢。

為了降低Java開發(fā)的復(fù)雜性卵佛,Spring采取了以下4種關(guān)鍵策略:
-基于POJO的輕量級(jí)和最小侵入性編程;
-通過依賴注入和面向接口實(shí)現(xiàn)松耦合植捎;
-基于切面和慣例進(jìn)行聲明式編程阳柔;
-通過切面和模板減少樣板式代碼舌剂。

Spring竭力避免因自身的API而弄亂你的應(yīng)用代碼。Spring不會(huì)強(qiáng)迫你實(shí)現(xiàn)Spring規(guī)范的接口或繼承Spring規(guī)范的類荐绝,相反低滩,在基于Spring構(gòu)建的應(yīng)用中恕沫,它的類通常沒有任何痕跡表明你使用了Spring纱意。最壞的場(chǎng)景是偷霉,一個(gè)類或許會(huì)使用Spring注解,但它依舊是POJO跑筝。

package com.habuma.spring;
public class HelloWorldBean{
    public String sayHello(){
        return "Hello World";
    }   
}

程序清單1.1 Spring不會(huì)在HelloWorldBean上有任何不合理的要求

依賴注入
直接看代碼吧:

package com.springination.knights;
public class DamselRescuingKnight implements Knight{
    private RescueDamselQuest quest;
    public DamselRescuingKnight(){
        this.quest = new RescueDamselQuest();//緊耦合
    }
    
    public void embarkOnQuest(){
        quest.embark();
    }
}

可以看到這個(gè)騎士自行創(chuàng)建了拯救公主的對(duì)象曲梗,因此極大地限制了他的能力虏两,如果他要去殺一條龍定罢,或者去干掉壞人旁瘫,就不行了。

通過DI遭庶,對(duì)象的依賴關(guān)系將由系統(tǒng)中負(fù)責(zé)協(xié)調(diào)各對(duì)象的第三方組件在創(chuàng)建對(duì)象的時(shí)候進(jìn)行設(shè)定稠屠。對(duì)象無需自行創(chuàng)建或管理它們的依賴關(guān)系权埠,如圖1.1所示攘蔽,依賴關(guān)系將被自動(dòng)注入到需要它們的對(duì)象當(dāng)中去。


image.png

看下面這個(gè)例子:

package com.springination.knights;
public class BraveKnight implements Knight{
    private Quest quest;
    public BraveKnight(Quest quest){ //Quest 被注入進(jìn)來
        this.quest = quest;
    }
    
    public void embarkOnQuest(){
        quest.embark();
    }
}

我們可以看到事哭,不同于之前的DamselRescuingKnight鳍咱,BraveKnight沒有自行創(chuàng)建探險(xiǎn)任務(wù),而是在構(gòu)造的時(shí)候把探險(xiǎn)任務(wù)作為構(gòu)造器參數(shù)傳入蓄坏。這是依賴注入的方式之一涡戳,即構(gòu)造器注入(constructor injection)渔彰。
更重要的是推正,傳入的探險(xiǎn)類型是Quest植榕,也就是所有探險(xiǎn)任務(wù)都必須實(shí)現(xiàn)的一個(gè)接口尊残。所以淤堵,BraveKnight能夠響應(yīng)RescueDamselQuest粘勒、 SlayDragonQuest庙睡、 MakeRoundTableRounderQuest等任意的Quest實(shí)現(xiàn)技俐。
程序清單1.4 為了測(cè)試BraveKnight雕擂,需要注入一個(gè)mock Quest

package com.springination.knights;
import static org.mockito.Mockito.*;
import org.junit.Test;

public class BraveKnightTest{
    @Test
    public void knightShouldEmbarkOnQuest(){
        Quest mockQuest = mock(Quest.class);//創(chuàng)建 mock Quest
        BraveKnight knight = new BraveKnight(mockQuest); //注入 mock Quest
        knight.embarkOnQuest();
        verify(mockQuest,times(1)).embark();
    }
}

你可以使用mock框架Mockito去創(chuàng)建一個(gè)Quest接口的mock實(shí)現(xiàn)井赌。通過這個(gè)mock對(duì)象仇穗,就可以創(chuàng)建一個(gè)新的BraveKnight實(shí)例纹坐,并通過構(gòu)造器注入這個(gè)mock Quest。當(dāng)調(diào)用embarkOnQuest()方法時(shí)果漾,你可以要求Mockito框架驗(yàn)證Quest的mock實(shí)現(xiàn)的embark()方法僅僅被調(diào)用了一次绒障。

現(xiàn)在BraveKnight類可以接受你傳遞給它的任意一種Quest的實(shí)現(xiàn)户辱,但該怎樣把特定的Query實(shí)現(xiàn)傳給它呢焕妙?假設(shè)弓摘,希望BraveKnight所要進(jìn)行探險(xiǎn)任務(wù)是殺死一只怪龍,那么程序清單1.5中的SlayDragonQuest也許是挺合適的研叫。程序清單1.5 SlayDragonQuest是要注入到BraveKnight中的Quest
實(shí)現(xiàn)

package com.springination.knights;
import java.io.PrintStream;

public class SlayDragonQuest implements Quest{
    private PrintStream stream;
    public SlayDragonQuest(PrintStream stream){
        this.stream = stream;
    }
    
    public void embark(){
        stream.println("Embarking on quest to slay the dragon!");
    }
}

我們可以看到嚷炉,SlayDragonQuest實(shí)現(xiàn)了Quest接口申屹,這樣它就適合注入到BraveKnight中去了隧膏。與其他的Java入門樣例有所不同胞枕,SlayDragonQuest沒有使用System.out.println()腐泻,而是在構(gòu)造方法中請(qǐng)求一個(gè)更為通用的PrintStream派桩。這里最大的問題在于,我們?cè)撊绾螌layDragonQuest交給BraveKnight呢唤反?又如何將PrintStream交給SlayDragonQuest呢?
創(chuàng)建應(yīng)用組件之間協(xié)作的行為通常稱為裝配(wiring)逆趋。Spring有多種裝配bean的方式晒奕,采用XML是很常見的一種裝配方式脑慧。程序清單1.6展現(xiàn)了一個(gè)簡(jiǎn)單的Spring配置文件:knights.xml闷袒,該配置文件將BraveKnight囊骤、SlayDragonQuest和PrintStream裝配到了一起冀值。
程序清單1.6 使用Spring將SlayDragonQuest注入到BraveKnight中

<?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">
    <bean id="knight" class="com.springframework.knight.BraveKnight">
        <constructor-arg reg="quest"/>
    </bean>
    <bean id="quest" class="com.springination.knights.SlayDragonQuest">
        <constructor-arg value="#{T(System).out}"/>
    </bean>
</beans>

對(duì)應(yīng) Java 實(shí)現(xiàn):

package com.springination.knights.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.springinaction.Knights.BraveKnight;
import com.springinaction.Knights.Knight;
import com.springinaction.Knights.Quest;
import com.springinaction.Knights.SlayDragonQuest;

public class KnightConfig {
    @Configuration
    public class KnightConfig{
        @Bean
        public Knight knight() {
            return new BraveKnight(quest());
        }
        @Bean
        public Quest quest() {
            return new SlayDragonQuest(System.out);
        }
    }
}

盡管BraveKnight依賴于Quest,但是它并不知道傳遞給它的是什么類型的Quest宫屠,也不知道這個(gè)Quest來自哪里列疗。與之類似,SlayDragonQuest依賴于PrintStream浪蹂,但是在編碼時(shí)它并不需要知道這個(gè)PrintStream是什么樣子的抵栈。只有Spring通過它的配置,能夠了解這些組成部分是如何裝配起來的坤次。這樣的話竭讳,就可以在不改變所依賴的類的情況下浙踢,修改依賴關(guān)系绢慢。

Spring通過應(yīng)用上下文(Application Context)裝載bean的定義并把它
們組裝起來。程序清單1.8 KnightMain.java加載包含Knight的Spring上下文

package com.springinaction.knights;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class KnightMain{
    public static void main(String[] args) throws Exception{
        ClassPathXmlApplicationContext context = 
                new ClassPathXmlApplicationContext("META-INF/spring/knights.xml");
        Knight knight = context.getBean(Knight.class);
        knight.embarkOnQuest();
        context.close();
    }
}

這里的main()方法基于knights.xml文件創(chuàng)建了Spring應(yīng)用上下文洛波。隨后它調(diào)用該應(yīng)用上下文獲取一個(gè)ID為knight的bean胰舆。得到Knight對(duì)象的引用后,只需簡(jiǎn)單調(diào)用embarkOnQuest()方法就可以執(zhí)行所賦予的探險(xiǎn)任務(wù)了蹬挤。注意這個(gè)類完全不知道我們的英雄騎士接受哪種探險(xiǎn)任務(wù)缚窿,而且完全沒有意識(shí)到這是由BraveKnight來執(zhí)行的。只有knights.xml文件知道哪個(gè)騎士執(zhí)行哪種探險(xiǎn)任務(wù)焰扳。

應(yīng)用切面
DI能夠讓相互協(xié)作的軟件組件保持松散耦合倦零,而面向切面編程(aspect-oriented programming,AOP)允許你把遍布應(yīng)用各處的功能分離出來形成可重用的組件吨悍。先來看下面這張圖:

image.png

AOP能夠使這些服務(wù)模塊化扫茅,并以聲明的方式將它們應(yīng)用到它們需要影響的組件中去。所造成的結(jié)果就是這些組件會(huì)具有更高的內(nèi)聚性并且會(huì)更加關(guān)注自身的業(yè)務(wù)育瓜,完全不需要了解涉及系統(tǒng)服務(wù)所帶來復(fù)雜性葫隙。總之躏仇,AOP能夠確保POJO的簡(jiǎn)單性恋脚。


image.png

AOP 應(yīng)用:
下面程序展示了,吟游詩(shī)人吟唱其實(shí)的英勇事跡:

package com.springinaction.knights;

import java.io.PrintStream;

public class Minstrel{
    private PrintStream stream;
    
    public Minstrel(PrintStream stream) {
        this.stream = stream;
    }
    
    public void singBeforeQuest() {
        stream.println("Fa la la, the knight is so brave!");
    }
    
    public void singAfterQuest() {
        stream.println("Tee hee hee, the brave knight" + "did embark on a quest!");
    }
}
package com.springinaction.knights;

public class BraveKnight implements Knight{
    private Quest quest;
    private Minstrel minstrel;
    
    public BraveKnight(Quest quest, Minstrel minstrel) {
        this.quest = quest;
        this.minstrel = minstrel;
    }
    
    public void embarkOnQuest() throws QuestException{
        minstrel.singBeforeQuest();
        quest.embark();
        minstrel.singAfterQuest();
    }
}

這樣應(yīng)該能達(dá)到效果焰手,在騎士行動(dòng)前后糟描,詩(shī)人歌頌騎士。但是詩(shī)人應(yīng)該是獨(dú)立的個(gè)體书妻,但是這個(gè)程序復(fù)雜化了船响,這個(gè)騎士居然去管理詩(shī)人,其實(shí)跟詩(shī)人應(yīng)該是沒有關(guān)系的。
所以下面要將詩(shī)人聲明為一個(gè)切面:

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/beans
http://www/springframework.org/schema/beans/spring-beans.xsd">
    <bean id="knight" class="com.springination.knights.BraveKnight">
        <constructor-arg ref="quest"/>
    </bean>

    <bean id="quest" class="com.springination.knights.SlayDragonQuest">
        <constructor-arg value="#{T(System).out}"/>
    </bean>

    <bean id="minstrel" class="com.springination.knights.Minstrel"> //聲明 Minstrel bean
        <constructor-arg value="#{T(System).out}"/>
    </bean>

    <aop:config>
        <aop:aspect ref="minstrel">
            <aop:pointcut id="embark" expression="execution(* *.embarkOnQuest(..))"/> //定義切點(diǎn)
            <aop:before pointcut-ref="embark" method="singBeforeQuest"/>
            <aop:after pointcut-ref="embark" method="singAfterQuest"/>
        </aop:aspect>
    </aop:config>
</beans>

使用模板消除樣板式代碼
程序清單1.12 許多Java API灿意,例如JDBC估灿,會(huì)涉及編寫大量的樣板式代碼

    public Employee getEmployeeById(long id){
        Connection conn = null;
        PreparedStatement stmt = null;
        ResultSet rs = null;
        try{
            conn = dataSource.getConnection();
            stmt = conn.prepareStatement("select id, firstname,lastname,salary from" +
                    "employee where id =?");
            stmt.setLong(1,id);
            rs = stmt.executeQuery();
            Employee employee = null;
            if(rs.next()){
                employee = new Employee();
                employee.setId(rs.getLong("id"));
                employee.setFirstName(rs.getString("firstname"));
                employee.setLastName(rs.getString("lastname"));
                employee.setSalary(rs.getBigDecimal("salary"));
            }
            return employee;
        }catch (SQLException e){
        }finally {
            if(rs != null){
                try{
                    stmt.close();
                }catch(SQLException e){}
            }
            if(conn != null){
                try{
                    conn.close();
                }catch(SQLException e){}
            }
        }
        return null;
    }

這段代碼很惡心,就是一個(gè)查詢數(shù)據(jù)庫(kù)的功能要寫很多重復(fù)的代碼缤剧,很多重復(fù)的異常處理馅袁。
使用Spring的JdbcTemplate(利用了 Java 5特性的JdbcTemplate實(shí)現(xiàn))重寫的getEmployeeById()方法僅僅關(guān)注于獲取員工數(shù)據(jù)的核心邏輯,而不需要迎合JDBC API的需求荒辕。程序清單1.13展示了修訂后的getEmployeeById()方法汗销。

    public Employee getEmployeeById(long id){
        return jdbcTemplate.queryForObject("select id, firstname, lastname, salary" +
                "from employee where id=?",
                new RowMapper<Employee>(){
                    public Employee mapRow(ResultSet rs, int rowNum) throws SQLException{
                        Employee employee = new Employee();
                        employee.setId(rs.getLong("id"));
                        employee.setFirstName(rs.getString("firstname"));
                        employee.setLastName(rs.getString("lastname"));
                        employee.setSalary(rs.getBigDecimal("salary"));
                        return employee;
                    }
                },
                id);
    }

這樣代碼簡(jiǎn)單多了。

容納你的 bean

Spring自帶了多個(gè)容器實(shí)現(xiàn)抵窒,可以歸為兩種不同的類型弛针。bean工廠(由org.springframework. beans.factory.eanFactory接口定義)是最簡(jiǎn)單的容器,提供基本的DI支持李皇。應(yīng)用上下文(由org.springframework.context.ApplicationContext接口定義)基于BeanFactory構(gòu)建削茁,并提供應(yīng)用框架級(jí)別的服務(wù),例如從屬性文件解析文本信息以及發(fā)布應(yīng)用事件給感興趣的事件監(jiān)聽者掉房。

Spring自帶了多種類型的應(yīng)用上下文茧跋。下面羅列的幾個(gè)是你最有可能
遇到的。
-AnnotationConfigApplicationContext:從一個(gè)或多個(gè)基于Java的配置類中加載Spring應(yīng)用上下文卓囚。
-AnnotationConfigWebApplicationContext:從一個(gè)或多個(gè)基于Java的配置類中加載Spring Web應(yīng)用上下文瘾杭。
-ClassPathXmlApplicationContext:從類路徑下的一個(gè)或多個(gè)XML配置文件中加載上下文定義,把應(yīng)用上下文的定義文件作為類資源哪亿。
-FileSystemXmlapplicationcontext:從文件系統(tǒng)下的一個(gè)或多個(gè)XML配置文件中加載上下文定義粥烁。
-XmlWebApplicationContext:從Web應(yīng)用下的一個(gè)或多個(gè)XML配置文件中加載上下文定義。

Bean 的生命空間

image.png

1.Spring對(duì)bean進(jìn)行實(shí)例化蝇棉;
2.Spring將值和bean的引用注入到bean對(duì)應(yīng)的屬性中讨阻;
3.如果bean實(shí)現(xiàn)了BeanNameAware接口,Spring將bean的ID傳遞給setBean-Name()方法银萍;
4.如果bean實(shí)現(xiàn)了BeanFactoryAware接口变勇,Spring將調(diào)用setBeanFactory()方法,將BeanFactory容器實(shí)例傳入贴唇;
5.如果bean實(shí)現(xiàn)了ApplicationContextAware接口,Spring將調(diào)用setApplicationContext()方法飞袋,將bean所在的應(yīng)用上下文的引用傳入進(jìn)來戳气;
6.如果bean實(shí)現(xiàn)了BeanPostProcessor接口,Spring將調(diào)用它們的post-ProcessBeforeInitialization()方法巧鸭;
7.如果bean實(shí)現(xiàn)了InitializingBean接口瓶您,Spring將調(diào)用它們的after-PropertiesSet()方法。類似地,如果bean使用initmethod聲明了初始化方法呀袱,該方法也會(huì)被調(diào)用贸毕;
8.如果bean實(shí)現(xiàn)了BeanPostProcessor接口,Spring將調(diào)用它們的post-ProcessAfterInitialization()方法夜赵;
9.此時(shí)明棍,bean已經(jīng)準(zhǔn)備就緒,可以被應(yīng)用程序使用了寇僧,它們將一直駐留在應(yīng)用上下文中摊腋,直到該應(yīng)用上下文被銷毀;
10.如果bean實(shí)現(xiàn)了DisposableBean接口嘁傀,Spring將調(diào)用它的destroy()接口方法兴蒸。同樣,如果bean使用destroy-method聲明了銷毀方法细办,該方法也會(huì)被調(diào)用橙凳。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市笑撞,隨后出現(xiàn)的幾起案子痕惋,更是在濱河造成了極大的恐慌,老刑警劉巖娃殖,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件值戳,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡炉爆,警方通過查閱死者的電腦和手機(jī)堕虹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來芬首,“玉大人赴捞,你說我怎么就攤上這事∮羯裕” “怎么了赦政?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)耀怜。 經(jīng)常有香客問我恢着,道長(zhǎng),這世上最難降的妖魔是什么财破? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任掰派,我火速辦了婚禮,結(jié)果婚禮上左痢,老公的妹妹穿的比我還像新娘靡羡。我一直安慰自己系洛,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布略步。 她就那樣靜靜地躺著描扯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪趟薄。 梳的紋絲不亂的頭發(fā)上绽诚,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音竟趾,去河邊找鬼憔购。 笑死,一個(gè)胖子當(dāng)著我的面吹牛岔帽,可吹牛的內(nèi)容都是我干的玫鸟。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼犀勒,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼屎飘!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起贾费,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤钦购,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后褂萧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體押桃,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年导犹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了唱凯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡谎痢,死狀恐怖磕昼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情节猿,我是刑警寧澤票从,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站滨嘱,受9級(jí)特大地震影響峰鄙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜九孩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一先馆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧躺彬,春花似錦煤墙、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至她君,卻和暖如春脚作,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背缔刹。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工球涛, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人校镐。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓亿扁,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親鸟廓。 傳聞我的和親對(duì)象是個(gè)殘疾皇子从祝,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容