Spring 學(xué)習(xí)筆記(一):簡化 Java 開發(fā)

今天開始酒甸,Spring 筆記開始更新,主要還是作為閱讀《Spring 實戰(zhàn)》(第4版)的學(xué)習(xí)筆記,該系列的內(nèi)容大多也是引用自該書中拭荤。

雖然工作中大量接觸 Spring,但回顧發(fā)現(xiàn)自己只是知道怎么做疫诽,而并沒有去深究舅世,導(dǎo)致有時候遇到問題不知從何下手,所以想要系統(tǒng)學(xué)習(xí) Spring奇徒。

Spring 的使命便是如標(biāo)題所說:簡化 Java 開發(fā)雏亚。具體怎么簡化的呢?分為以下四個方面:

  • 基于 POJO 的輕量級和最小侵入性編程摩钙;
  • 通過依賴注入和面向接口實現(xiàn)松耦合罢低;
  • 基于切面和慣例進行聲明式編程;
  • 通過切面和模板減少樣板式代碼胖笛。

這四點現(xiàn)在也不用花太多時間去深究网持,就像面向?qū)ο?/code>一樣,有個大概的了解长踊,在實際中我們會慢慢體會到的翎碑。

接下來就簡單了解下:

POJO

POJO:全程 Plain Old Java Object,直譯就是簡單老式 Java 對象之斯。

更詳細(xì)的一點的解釋就是:一個簡單的Java類日杈,這個類沒有實現(xiàn)/繼承任何特殊的java接口或者類,不遵循任何主要java模型佑刷,約定或者框架的java對象莉擒。在理想情況下,POJO不應(yīng)該有注解瘫絮。

public class MyPOJO {
    public String sayHello() {
        return "Hello";
    }
}

這就是一個 POJO涨冀,很簡單,但 Spring 可以使用 DI 來裝配它們麦萤,讓它們變得強大鹿鳖。接下來就講一下 DI扁眯,也就是依賴注入。

依賴注入

目的解耦

注:以下代碼只為表現(xiàn)本文中心理念翅帜,不能作實際編譯用

/**
 * 拯救少女的騎士
 */
public class DamselRescuingKnight implements Knight {

    //拯救少女的任務(wù)
    private RescueDamselQuest quest;

    public DamselRescuingKnight() {
        this.quest = new RescueDamselQuest();
    }

    public void embarkOnQuest() {
        quest.embark();
    }
}

以上代碼就是按正常邏輯編寫的姻檀,DamselRescuingKnight在他的構(gòu)造函數(shù)中 new 了一個RescueDamselQuest,于是這兩者便緊緊耦合在一起了涝滴,這個騎士只能去拯救少女绣版,而別的任務(wù)就完成不了了。

step1:如何實現(xiàn) DI


public class BraveKnight implements Knight{
    private Quest quest;
    
    public BraveKnight(Quest quest) {
        this.quest = quest;
    }

    public void embarkOnQuest() {
        quest.embark();
    }
}

這段代碼便一定程度上降低了耦合性歼疮,騎士沒有自己創(chuàng)建任務(wù)杂抽,而且在構(gòu)造的時候把任務(wù)作為參數(shù)傳遞進來,這便是依賴注入的方式之一韩脏,即構(gòu)造器注入 constructor injection缩麸。

而且上述代碼中構(gòu)造器傳入的是一個Quest接口,這樣騎士便能隨心所欲的完成繼承了Quest接口的任何任務(wù)赡矢。這樣BraveKnight沒有和任何Quest發(fā)生耦合杭朱,這就是 DI 帶來的最大收益——松耦合

step2:如何注入 Quest


首先需要一個Quest的實現(xiàn)類济竹,如下,屠龍勇士再次出現(xiàn)了:

import java.io.PrintStream;

public class SlayDragonQuest implements Quest {
    
    private PrintStream stream;
    
    public SlayDragonQuest(PrintStream stream) {
        this.stream = stream;
    }

    @Override
    public void embark() {
        stream.println("Embarking on quest to slay the dragon");
    }
}

接下來便有兩個問題霎槐,如何把SlayDragonQuest交給BraveKnight送浊、如何把PrintStream交給SlayDragonQuest。這個過程稱為裝配 wiring丘跌,Spring 提供了很多種裝配 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/scheme/beans
       http://www.springframework.org/scheme/beans/spring-beans.xsd">
    
    <!-- 注入 Quest bean-->
    <bean id="knight" class="BraveKnight">
        <constructor-arg ref="quest"/>
    </bean>

    <!-- 創(chuàng)建 SlayDragonQuest-->
    <bean id="quest" class="SlayDragonQuest">
        <constructor-arg value="#{T(System.out)}"/>
    </bean>
</beans>

這樣就能把BraveKnightSlayDragonQuest聲明為 spring 中的 bean 了闭树,這里只是展示下大概怎么配置耸棒,具體在書中后面的章節(jié)會講,我到時候也會整理成筆記的报辱。

雖然他們之間會想依賴与殃,但并不知道會有什么樣的 Quest 傳給他,只有通過 spring 的配置文件才能知道他們之間是如何裝配起來的碍现。而且我們可以通過修改 xml 直接修改依賴關(guān)系幅疼。

step3:如何工作的


Spring 通過應(yīng)用上下文 Application Context 裝載 bean 并把他們組裝起來。

xml 對應(yīng)的上下文是 ClassPathXmlApplicationContext昼接,示例代碼如下:

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("knights.xml");
        // 獲取 Knight bean
        Knight knight = context.getBean(Knight.class);
        // 使用 knight
        knight.embarkOnQuest();
        context.close();
    }
}

這里通過knights.xml創(chuàng)建了 Spring 應(yīng)用上下文爽篷,然后從該應(yīng)用上下文中獲取一個 ID 為 knight 的 bean,然后就可以調(diào)用他的方法慢睡。這個類完全不知道那種類接受了哪種任務(wù)逐工,只有 xml 知道铡溪。

應(yīng)用切面

DI 是為了讓相互協(xié)作的組件保持松耦合,而面向切面編程 aspect-oriented-programming 允許你把那些遍布應(yīng)用各處的功能分離出來形成可重用的組件泪喊。

最常用的需要切面的便是日志棕硫、事務(wù)管理、安全窘俺。

還是騎士的例子饲帅,屠龍騎士應(yīng)該只需要專注于如何去屠龍,而屠龍后怎么把他的事跡記錄下來不應(yīng)該需要他操心瘤泪。而是有專門的記錄歷史的灶泵。用代碼來看一下,我們把記錄屠龍勇士的人稱為吟游詩人对途。

首先需要創(chuàng)建一個吟游詩人的類:

import java.io.PrintStream;

/**
 * 吟游詩人
 */
public class Minstrel {
    private PrintStream stream;
    
    public Minstrel(PrintStream stream) {
        this.stream = stream;
    }
    
    public void singBeforeQuest() {
        stream.println("這個騎士要上了");
    }
    
    public void singAfterQuest() {
        stream.println("騎士探險結(jié)束赦邻,真牛逼");
    }
}

代碼很簡單,就是需要在騎士執(zhí)行任務(wù)前后分別輸出兩句話实檀。

似乎我們把 braveKnight 稍加改造就能實現(xiàn)記錄了惶洲,如下

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() {
        minstrel.singBeforeQuest();
        quest.embark();
        minstrel.singAfterQuest();
    }
}

但就如同一開始所說,我們應(yīng)該讓騎士自己去調(diào)用吟游詩人然后記錄嗎膳犹?答案肯定是不恬吕,騎士不應(yīng)該考慮這些,他只要專心完成他的任務(wù)就行了须床。

這時候我們需要把 Minstrel 抽象為一個切面铐料,并在 spring 的配置文件中聲明他

<?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/beans http://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">

    <!-- 注入 Quest bean-->
    <bean id="knight" class="BraveKnight">
        <constructor-arg ref="quest"/>
    </bean>

    <!-- 創(chuàng)建 SlayDragonQuest-->
    <bean id="quest" class="SlayDragonQuest">
        <constructor-arg value="#{T(System.out)}"/>
    </bean>

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

    <aop:config>
        <aop:aspect ref="minstrel">
            <!-- 定義切點-->
            <aop:pointcut id="embark" expression="execution(* *.embarkOnQuest(..))"/>
            <!-- 聲明前置通知-->
            <aop:before method="singBeforeQuest" pointcut-ref="embark"/>
            <!-- 聲明后置通知-->
            <aop:after method="singAfterQuest" pointcut-ref="embark"/>
        </aop:aspect>
    </aop:config>

</beans>

首先和之前一樣把 Minstrel 聲明 為一個 bean,然后在 aop:config 中定義切面豺旬,并定義什么時候調(diào)用方法钠惩。具體配置也是后面再詳細(xì)介紹,這里先大致看懂意思就行族阅。

這樣的話篓跛,BraveKnight 不需要顯示的調(diào)用它,甚至不知道它的存在坦刀,這就是 AOP 的妙處愧沟。

使用模板消除樣板式代碼

簡單的說就是減少重復(fù)代碼,很常見的一個例子就是用 JDBC 訪問數(shù)據(jù)庫鲤遥,正常我們可能需要寫如下代碼:

public static void queryTeacher() {
    String driverName = "com.mysql.jdbc.Driver";
    String url = "jdbc:mysql://localhost:3306/zyc";
    String username = "root";
    String password = "123456";

    Connection connection = null;
    Statement statement = null;
    try {
        Class.forName(driverName);
        connection = (Connection) DriverManager.getConnection(url,username,password);
        statement = connection.createStatement();
        ResultSet rs = statement.executeQuery("SELECT tno,tname from teacher");
        while (rs.next()) {
            String no = rs.getString("tno");
            String name = rs.getString("tname");
            System.out.println(no + ":" + name);
        }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

相似代碼會在同一個項目中出現(xiàn)很多遍央渣,這時候可以使用 Spring 中 JDBCTemplate 來簡化代碼

public static void insertTeacher() {
    
    return jdbcTemplate.queryForObject("SELECT tno,tname from teacher",
                                      ...,
                                      ...); 
    // 代碼省略,意思很簡單渴频,就是把模板中變的因素作為參數(shù)傳進來

這種思想在代碼中很常見芽丹,不管是 Java 還是別的編程語言。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末卜朗,一起剝皮案震驚了整個濱河市拔第,隨后出現(xiàn)的幾起案子咕村,更是在濱河造成了極大的恐慌,老刑警劉巖蚊俺,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件懈涛,死亡現(xiàn)場離奇詭異,居然都是意外死亡泳猬,警方通過查閱死者的電腦和手機批钠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來得封,“玉大人埋心,你說我怎么就攤上這事∶ι希” “怎么了拷呆?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長疫粥。 經(jīng)常有香客問我茬斧,道長,這世上最難降的妖魔是什么梗逮? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任项秉,我火速辦了婚禮,結(jié)果婚禮上慷彤,老公的妹妹穿的比我還像新娘娄蔼。我一直安慰自己,他們只是感情好瞬欧,可當(dāng)我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布贷屎。 她就那樣靜靜地躺著罢防,像睡著了一般艘虎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上咒吐,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天野建,我揣著相機與錄音,去河邊找鬼恬叹。 笑死候生,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的绽昼。 我是一名探鬼主播唯鸭,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼硅确!你這毒婦竟也來了目溉?” 一聲冷哼從身側(cè)響起明肮,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎缭付,沒想到半個月后柿估,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡陷猫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年秫舌,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绣檬。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡足陨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出河咽,到底是詐尸還是另有隱情钠右,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布忘蟹,位于F島的核電站飒房,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏媚值。R本人自食惡果不足惜狠毯,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望褥芒。 院中可真熱鬧嚼松,春花似錦、人聲如沸锰扶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坷牛。三九已至罕偎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間京闰,已是汗流浹背颜及。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蹂楣,地道東北人俏站。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像痊土,于是被迫代替她去往敵國和親肄扎。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,941評論 2 355

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