附上示例程序的github地址:https://github.com/bjtudujunlin/SpringDataExample
一、Spring設計目標
Spring設計的初衷在於提供一套輕量級的應用開發框架,解決開發者在應用開發中的共性問題。這句話有兩個關鍵字,一是“輕量級”,二是“共性問題”。爲啥叫“輕量級”呢,Spring框架本身不能給你解決業務問題,也沒有相關庫,只是提供了一個框架,讓你的程序更加健壯、易於維護,你看到的Spring MVC、Spring Data之類的都是基於Spring框架的組件,不屬於Spring框架內容。那麼“共性問題”有啥呢,耦合,還是耦合,日常程序開發中,有兩類耦合問題經常遇見,一是類與類關係的耦合,軟件開發中,類與類的“組合”關係普遍存在,比如B類有屬性B1是A類的實例,在B中需要對B1進行初始化操作,Spring中的IOC容器提供了配置文件的方式對B類進行管理,包括初始化操作,配置屬性B1的值等等。二是服務與服務關係的耦合,比如現在有一個數據庫操作類C,現在我們想爲這個類加上權限檢測的功能,只有具有特定權限的人羣才能調用數據庫操作類的方法,一般處理是在類C中調用權限檢測方法,這樣就導致了服務之間的耦合,違背了“單一職責原則”,Spring提供了AOP方法,以非侵入式的方式解決這種耦合問題,保持了服務的獨立性,又滿足了業務的需求。
二、Spring整體架構
Spring整體架構如下圖所示,核心模塊是Spring IOC和Spring AOP模塊,其它的模塊都是基於這兩個核心模塊進行開發,然後以組件的方式集成進來的。這裏先重點介紹核心的IOC和AOP模塊,其它組件留在後面的組件環節進行詳細介紹。
圖 1
三、Spring核心模塊
1. IOC模塊
IOC模塊其實是一個IOC容器,提供了對Bean進行管理的功能,依賴於這個功能,解決了類與類關係的耦合。下圖比較適合解釋IOC容器。由於引進了中間位置的“第三方”,也就是IOC容器,使得A、B、C、D這4個對象沒有了耦合關係,齒輪之間的傳動全部依靠“第三方”了,全部對象的控制權全部上繳給“第三方”IOC容器,所以,IOC容器成了整個系統的關鍵核心,它起到了一種類似“粘合劑”的作用,把系統中的所有對象粘合在一起發揮作用,如果沒有這個“粘合劑”,對象與對象之間會彼此失去聯繫,這就是有人把IOC容器比喻成“粘合劑”的由來。
圖 2
有了前面的基礎,我們再來看看IOC(Inversion of Control)——控制反轉的意思。爲啥叫控制反轉呢?因爲它涉及到控制權在誰手裏的問題,舉個例子,引入IOC容器之前,對象A依賴於對象B,那麼對象A在初始化或者運行到某一點的時候,自己必須主動去創建對象B或者使用已經創建的對象B。無論是創建還是使用對象B,控制權都在A自己手上。那麼引入IOC容器之後呢,對象A與對象B之間失去了直接聯繫,所以,當對象A運行到需要對象B的時候,IOC容器會主動創建一個對象B注入到對象A需要的地方。可以看見創建對象B的控制權由A轉移到了IOC容器,控制權反轉了。
還有一個平時容易混淆的概念DI(Dependence Injection)——依賴注入,這個和IOC有什麼關係呢。前面我們說了IOC容器會主動創建一個對象B注入到對象A需要的地方,DI其實就是IOC實現控制反轉的方法。
下面舉一個IOC具體的例子,這裏先採用XML的方式,這種方式比較複雜,但是能更好的理解spring的IOC配置,後續會補充java配置方式。
a) 建立maven的java工程,並添加spring-core的依賴
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.3.RELEASE</version>
</dependency>
b) 建立Person類
public class Person {
private Stringname;
private Stringage;
private Regionregion;
public String getName() {
returnname;
}
public void setName(String name) {
this.name =name;
}
public String getAge() {
returnage;
}
public void setAge(String age) {
this.age =age;
}
private Stringdiscribe;
public Person(Regionregion,Stringdiscribe){
this.region =region;
this.discribe =discribe;
}
public void Introduce(){
System.out.println("I am " +name +",I comefrom " +region +"my age is" +age);
System.out.println(discribe);
}
}
c) 建立Region類
public class Region {
private Stringprovince;
private Stringcity;
public StringgetProvince() {
returnprovince;
}
public void setProvince(String province) {
this.province =province;
}
public String getCity() {
returncity;
}
public void setCity(String city) {
this.city =city;
}
@Override
public String toString(){
returnprovince +" " +city;
}
}
d) 在resource下面新建spring的xml配置文件spring-config.xml
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="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
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context">
<!-- 配置細節 -->
<!-- bean如果沒有指定ID,會生成默認ID,形式爲“類名#0”,0是計數 -->
<beanid="person"class="iscas.springstudy.Person">
<!-- 配置構造函數,按照配置函數從左到右的順序,ref標籤表示參數爲指定id的bean,value標籤表示參數值 -->
<constructor-argref="region-a"></constructor-arg>
<constructor-argvalue="a funny man"></constructor-arg>
<!-- 配置屬性,name對應屬性名,value對應屬性值,屬性中也可以右ref標籤,跟構造函數是一樣的意思
需要注意的是配置屬性要求該屬性必須右getter方法和setter方法-->
<propertyname="name"value="liming"></property>
<propertyname="age"value="19"></property>
</bean>
<beanid="region-a"class="iscas.springstudy.Region">
<propertyname="province"value="beijing"></property>
<propertyname="city"value="haidian"></property>
</bean>
</beans>
e) 加載xml文件,生成beanfactory,獲取bean,並調用其中方法
XmlBeanFactory factory =newXmlBeanFactory(newClassPathResource("spring-config.xml"));
Person person = (Person)factory.getBean("person");
person.Introduce();
f) applicationcontext方式加載xml文件
beanfactory類現在已經過期了,applicationcontext類繼承自beanfactory,能夠提供比beanfactory更多的支持,所以在看看applicationcontext怎麼調用。首先需要在maven中添加applicationcontext依賴。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.3.RELEASE</version>
</dependency>
代碼中用applicationcontext取代beanfactory即可,改變如下
ApplicationContext application =newClassPathXmlApplicationContext("spring-config.xml");
Person person = (Person)application.getBean("person");
person.Introduce();
2. AOP模塊
在軟件開發中,散佈於應用中多處的功能被稱爲橫切關注點(cross-cutting concern)。通常來講,這些橫切關注點從概念上是與應用的業務邏輯相分離的(但是往往會直接嵌入到應用的業務邏輯之中)。把這些橫切關注點與業務邏輯相分離正是面向切面編程(AOP)所要解決的問題。下面這個圖可以加深理解。
圖 3
上圖中,CourseService、StudentService和MiscService都屬於業務服務,比如CourseService就是課程管理服務。現在需要爲CourseService服務加上安全校驗功能,傳統的做法是創建一個安全類,然後在CourseService服務中需要安全校驗的地方進行調用,這樣邏輯簡單,但是該方式是侵入式的,改變了CourseService服務本身的代碼,CourseService服務和安全類耦合起來,比如我以後將CourseService服務換到別的地方去用,要麼刪除安全功能,要麼把安全類也帶上。同樣的StudentService、MiscService都用到了同樣的安全類。這裏的安全類就是橫切關注點。要解決前面這個侵入式的問題,我們就需要引入AOP的思想,既滿足上述功能需求,又是非侵入式的。
理解AOP需要理解以下幾個概念:
a) 通知(Advice),切面必須要完成的工作。在AOP術語中,切面的工作被稱爲通知。Spring切面支持的最小粒度是方法,可以應用5種類型的通知:
前置通知(Before):在目標方法被調用之前調用通知功能;
後置通知(After):在目標方法完成之後調用通知,此時不會關心方法的輸出是什麼;
返回通知(After-returning):在目標方法成功執行之後調用通知;
異常通知(After-throwing):在目標方法拋出異常後調用通知;
環繞通知(Around):通知包裹了被通知的方法,在被通知的方法調用之前和調用之後執行自定義的行爲。
b) 連接點(Join point),連接點是在應用執行過程中能夠插入切面的一個點。比如Spring AOP只能支持到方法粒度,所以類型的方法可以作爲連接點,AOP可以在方法上插入切面,監聽方法的調用,並觸發通知。
c) 切點(Poincut),一個切面並不需要通知應用的所有連接點,選擇的連接點就是切點。連接點表示有這個能力,而切點就是已經選擇的連接點。
d) 切面(Aspect),切面是通知和切點的結合。通知和切點共同定義了切面的全部內容。
總結起來,AOP無非就幹了兩件事情,選擇連接點作爲切點,插入切面對切點進行監聽。當切點滿足通知條件時,通知觸發。
Spring AOP的基本概念到這兒解釋完了,考慮AOP內容還有些,放在下一章節結合示例程序敘述。