今天開始酒甸,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>
這樣就能把BraveKnight
和SlayDragonQuest
聲明為 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 還是別的編程語言。