Spring 概述
Spring 框架是什么
Spring 是于 2003 年興起的一個輕量級的 Java 開發(fā)框架蚁鳖,它是為了解決企業(yè)應(yīng)用開發(fā)的復(fù)雜性而創(chuàng)建的磺芭。Spring 的核心是控制反轉(zhuǎn)(IoC)和面向切面編程(AOP)。Spring 是可以在 Java SE/EE 中使用的輕量級開源框架醉箕。
Spring 的主要作用就是為代碼“解耦”钾腺,降低代碼間的耦合度。就是讓對象和對象(模塊和模塊)之間關(guān)系不是使用代碼關(guān)聯(lián)讥裤,而是通過配置來說明放棒。即在 Spring 中說明對象(模塊)的關(guān)系。
Spring 根據(jù)代碼的功能特點己英,使用 Ioc 降低業(yè)務(wù)對象之間耦合度间螟。IoC 使得主業(yè)務(wù)在相互調(diào)用過程中,不用再自己維護關(guān)系了损肛,即不用再自己創(chuàng)建要使用的對象了厢破。而是由 Spring容器統(tǒng)一管理,自動“注入”,注入即賦值治拿。 而 AOP 使得系統(tǒng)級服務(wù)得到了最大復(fù)用摩泪,且不用再由程序員手工將系統(tǒng)級服務(wù)“混雜”到主業(yè)務(wù)邏輯中了,而是由 Spring 容器統(tǒng)一完成“織入”忍啤。
官網(wǎng):https://spring.io/
Spring 優(yōu)點蔼紧?
Spring 是一個框架,是一個半成品的軟件雄右。有 20 個模塊組成厕怜。它是一個容器管理對象,容器是裝東西的未檩,Spring 容器不裝文本戴尸,數(shù)字。裝的是對象冤狡。Spring 是存儲對象的容器孙蒙。
(1) 輕量
Spring 框架使用的 jar 都比較小项棠,一般在 1M 以下或者幾百 kb。Spring 核心功能的所需的 jar 總共在 3M 左右挎峦。
Spring 框架運行占用的資源少香追,運行效率高。不依賴其他 jar
(2) 針對接口編程坦胶,解耦合
Spring 提供了 Ioc 控制反轉(zhuǎn)透典,由容器管理對象,對象的依賴關(guān)系顿苇。原來在程序代碼中的對象創(chuàng)建方式峭咒,現(xiàn)在由容器完成。對象之間的依賴解耦合纪岁。
(3) AOP 編程的支持
通過 Spring 提供的 AOP 功能凑队,方便進行面向切面的編程,許多不容易用傳統(tǒng) OOP 實現(xiàn)的功能可以通過 AOP 輕松應(yīng)付
在 Spring 中幔翰,開發(fā)人員可以從繁雜的事務(wù)管理代碼中解脫出來漩氨,通過聲明式方式靈活地進行事務(wù)的管理,提高開發(fā)效率和質(zhì)量导匣。
(4) 方便集成各種優(yōu)秀框架
Spring 不排斥各種優(yōu)秀的開源框架才菠,相反 Spring 可以降低各種框架的使用難度,Spring提供了對各種優(yōu)秀框架(如Struts,Hibernate贡定、MyBatis)等的直接支持赋访。簡化框架的使用。Spring 像插線板一樣缓待,其他框架是插頭蚓耽,可以容易的組合到一起。需要使用哪個框架旋炒,就把這個插頭放入插線板步悠。不需要可以輕易的移除。
Spring 體系結(jié)構(gòu)
Spring 由 20 多個模塊組成瘫镇,它們可以分為數(shù)據(jù)訪問/集成(Data Access/Integration)鼎兽、Web、面向切面編程(AOP, Aspects)铣除、提供JVM的代理(Instrumentation)谚咬、消息發(fā)送(Messaging)、
核心容器(Core Container)和測試(Test)尚粘。
IoC 控制反轉(zhuǎn)
控制反轉(zhuǎn)(IoC择卦,Inversion of Control),是一個概念,是一種思想秉继。指將傳統(tǒng)上由程序代碼直接操控的對象調(diào)用權(quán)交給容器祈噪,通過容器來實現(xiàn)對象的裝配和管理∩屑控制反轉(zhuǎn)就是對對象控制權(quán)的轉(zhuǎn)移辑鲤,從程序代碼本身反轉(zhuǎn)到了外部容器。通過容器實現(xiàn)對象的創(chuàng)建腌巾,屬性賦值遂填,依賴的管理铲觉。
IoC 是一個概念澈蝙,是一種思想,其實現(xiàn)方式多種多樣撵幽。當前比較流行的實現(xiàn)方式是依賴注入灯荧。應(yīng)用廣泛。
依賴:classA 類中含有 classB 的實例盐杂,在 classA 中調(diào)用 classB 的方法完成功能逗载,即 classA對 classB 有依賴。
Ioc 的實現(xiàn):
依賴注入:DI(Dependency Injection)链烈,程序代碼不做定位查詢厉斟,這些工作由容器自行完成。
依賴注入 DI 是指程序運行過程中强衡,若需要調(diào)用另一個對象協(xié)助時擦秽,無須在代碼中創(chuàng)建被調(diào)用者,而是依賴于外部容器漩勤,由外部容器創(chuàng)建后傳遞給程序感挥。
Spring 的依賴注入對調(diào)用者與被調(diào)用者幾乎沒有任何要求,完全支持對象之間依賴關(guān)系的管理越败。
Spring 框架使用依賴注入(DI)實現(xiàn) IoC触幼。
Spring 容器是一個超級大工廠,負責創(chuàng)建究飞、管理所有的 Java 對象置谦,這些 Java 對象被稱為 Bean。Spring 容器管理著容器中 Bean 之間的依賴關(guān)系亿傅,Spring 使用“依賴注入”的方式來管理 Bean 之間的依賴關(guān)系媒峡。使用 IoC 實現(xiàn)對象之間的解耦和。
開發(fā)工具準備
開發(fā)工具:idea2017 以上
依賴管理:maven3 以上
jdk:1.8 以上
需要設(shè)置 maven 本機倉庫:
Spring 的第一個程序
舉例:01-primay
創(chuàng)建 maven 項目:
引入 maven 依賴 pom.xml:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
插件
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
定義接口與實體類:
public interface SomeService {
void doSome();
}
public class SomeServiceImpl implements SomeService {
public SomeServiceImpl() {
super();
System.out.println("SomeServiceImpl無參數(shù)構(gòu)造方法");
}
@Override
public void doSome() {
System.out.println("====業(yè)務(wù)方法doSome()===");
}
}
創(chuàng)建 Spring 配置文件:
在 src/main/resources/目錄現(xiàn)創(chuàng)建一個 xml 文件袱蜡,文件名可以隨意丝蹭,但 Spring 建議的名稱為 applicationContext.xml。
spring 配置中需要加入約束文件才能正常使用,約束文件是 xsd 擴展名奔穿。
<bean />:用于定義一個實例對象镜沽。一個實例對應(yīng)一個 bean 元素。
id:該屬性是 Bean 實例的唯一標識贱田,程序通過 id 屬性訪問 Bean缅茉,Bean 與 Bean 間的依賴關(guān)系也是通過 id 屬性關(guān)聯(lián)的。
class:指定該 Bean 所屬的類男摧,注意這里只能是類蔬墩,不能是接口。
定義測試類:
使用 spring 創(chuàng)建非自定義類對象:
spring 配置文件加入 java.util.Date 定義:
<bean id="myDate" class="java.util.Date" />
MyTest 測試類中:
調(diào)用 getBean(“myDate”); 獲取日期類對象耗拓。
容器接口和實現(xiàn)類:
ApplicationContext 接口(容器)
ApplicationContext 用于加載 Spring 的配置文件拇颅,在程序中充當“容器”的角色。其實現(xiàn)類有兩個乔询。
A樟插、配置文件在類路徑下
若 Spring 配置文件存放在項目的類路徑下,則使用 ClassPathXmlApplicationContext 實現(xiàn)類進行加載竿刁。
B黄锤、 ApplicationContext 容器中對象的裝配時機
ApplicationContext 容器,會在容器對象初始化時食拜,將其中的所有對象一次性全部裝配好鸵熟。以后代碼中若要使用到這些對象,只需從內(nèi)存中直接獲取即可负甸。執(zhí)行效率較高流强。但占用內(nèi)存。
C惑惶、 使用 spring 容器創(chuàng)建的 java 對象
基于 XML 的 DI
舉例:項目 di-xml
注入分類:
bean 實例在調(diào)用無參構(gòu)造器創(chuàng)建對象后煮盼,就要對 bean 對象的屬性進行初始化。初始化是由容器自動完成的带污,稱為注入僵控。
根據(jù)注入方式的不同,常用的有兩類:set 注入鱼冀、構(gòu)造注入报破。
(1) set 注入(掌握)
set 注入也叫設(shè)值注入是指,通過 setter 方法傳入被調(diào)用者的實例千绪。這種注入方式簡單充易、直觀,因而在 Spring 的依賴注入中大量使用荸型。
A盹靴、簡單類型
創(chuàng)建 java.util.Date 并設(shè)置初始的日期時間:
Spring 配置文件:
測試方法:
B、 引用類型
當指定 bean 的某屬性值為另一 bean 的實例時,通過 ref 指定它們間的引用關(guān)系稿静。ref的值必須為某 bean 的 id 值梭冠。
對于其它 Bean 對象的引用,使用<bean/>標簽的 ref 屬性
測試方法:
(2) 構(gòu)造注入(理解)
構(gòu)造注入是指改备,在構(gòu)造調(diào)用者實例的同時控漠,完成被調(diào)用者的實例化。即悬钳,使用構(gòu)造器設(shè)置依賴關(guān)系盐捷。
舉例 1:
<constructor-arg />標簽中用于指定參數(shù)的屬性有:
name:指定參數(shù)名稱。
index:指明該參數(shù)對應(yīng)著構(gòu)造器的第幾個參數(shù)默勾,從 0 開始碉渡。不過,該屬性不要也行灾测,但要注意爆价,若參數(shù)類型相同,或之間有包含關(guān)系媳搪,則需要保證賦值順序要與構(gòu)造器中的參數(shù)順序一致。
舉例 2:
使用構(gòu)造注入創(chuàng)建一個系統(tǒng)類 File 對象
測試類:
引用類型屬性自動注入
對于引用類型屬性的注入骤宣,也可不在配置文件中顯示的注入秦爆。可以通過為<bean/>標簽設(shè)置 autowire 屬性值憔披,為引用類型屬性進行隱式自動注入(默認是不自動注入引用類型屬 性)等限。根據(jù)自動注入判斷標準的不同,可以分為兩種:
byName:根據(jù)名稱自動注入
byType: 根據(jù)類型自動注入
(1) byName 方式自動注入
當配置文件中被調(diào)用者 bean 的 id 值與代碼中調(diào)用者 bean 類的屬性名相同時芬膝,可使用byName 方式望门,讓容器自動將被調(diào)用者 bean 注入給調(diào)用者 bean。容器是通過調(diào)用者的 bean類的屬性名與配置文件的被調(diào)用者 bean 的 id 進行比較而實現(xiàn)自動注入的锰霜。
舉例:
(2) byType 方式自動注入
使用 byType 方式自動注入筹误,要求:配置文件中被調(diào)用者 bean 的 class 屬性指定的類,要與代碼中調(diào)用者 bean 類的某引用類型屬性類型同源癣缅。即要么相同厨剪,要么有 is-a 關(guān)系(子類,或是實現(xiàn)類)友存。但這樣的同源的被調(diào)用 bean 只能有一個祷膳。多于一個,容器就不知該匹配哪一個了屡立。
舉例:
為應(yīng)用指定多個 Spring 配置文件
在實際應(yīng)用里直晨,隨著應(yīng)用規(guī)模的增加,系統(tǒng)中 Bean 數(shù)量也大量增加,導致配置文件變得非常龐大勇皇、臃腫奕巍。為了避免這種情況的產(chǎn)生,提高配置文件的可讀性與可維護性儒士,可以將Spring 配置文件分解成多個配置文件的止。
包含關(guān)系的配置文件:
多個配置文件中有一個總文件,總配置文件將各其它子文件通過<import/>引入着撩。在 Java代碼中只需要使用總配置文件對容器進行初始化即可诅福。
舉例:
代碼:
Spring 配置文件:
也可使用通配符。但拖叙,此時要求父配置文件名不能滿足所能匹配的格式氓润,否則將出現(xiàn)循環(huán)遞歸包含。就本例而言薯鳍,父配置文件不能匹配 spring-*.xml 的格式咖气,即不能起名為spring-total.xml。
測試代碼:
基于注解的 DI
舉例:di-annotation 項目
對于 DI 使用注解挖滤,將不再需要在 Spring 配置文件中聲明 bean 實例崩溪。Spring 中使用注解,需要在原有 Spring 運行環(huán)境基礎(chǔ)上再做一些改變斩松。
需要在 Spring 配置文件中配置組件掃描器伶唯,用于在指定的基本包中掃描注解。
指定多個包的三種方式:
1)使用多個 context:component-scan 指定不同的包路徑
2)指定 base-package 的值使用分隔符
分隔符可以使用逗號(惧盹,)分號(乳幸;)還可以使用空格,不建議使用空格钧椰。
逗號分隔:
分號分隔:
3)base-package 是指定到父包名
base-package 的值表是基本包粹断,容器啟動會掃描包及其子包中的注解,當然也會掃描到子包下級的子包嫡霞。所以 base-package 可以指定一個父包就可以瓶埋。
或者最頂級的父包
但不建議使用頂級的父包,掃描的路徑比較多秒际,導致容器啟動時間變慢悬赏。指定到目標包和合適的。也就是注解所在包全路徑娄徊。例如注解的類在 com.bjpowernode.beans 包中
定義 Bean 的注解@Component(掌握)
需要在類上使用注解@Component闽颇,該注解的 value 屬性用于指定該 bean 的 id 值。
舉例:di01
另外寄锐,Spring 還提供了 3 個創(chuàng)建對象的注解:
@Repository 用于對 DAO 實現(xiàn)類進行注解
@Service 用于對 Service 實現(xiàn)類進行注解
@Controller 用于對 Controller 實現(xiàn)類進行注解
這三個注解與@Component 都可以創(chuàng)建對象兵多,但這三個注解還有其他的含義尖啡,@Service創(chuàng)建業(yè)務(wù)層對象,業(yè)務(wù)層對象可以加入事務(wù)功能剩膘,@Controller 注解創(chuàng)建的對象可以作為處理器接收用戶的請求衅斩。
@Repository,@Service怠褐,@Controller 是對@Component 注解的細化畏梆,標注不同層的對象。即持久層對象奈懒,業(yè)務(wù)層對象奠涌,控制層對象。
@Component 不指定 value 屬性磷杏,bean 的 id 是類名的首字母小寫溜畅。
簡單類型屬性注入@Value(掌握)
需要在屬性上使用注解@Value,該注解的 value 屬性用于指定要注入的值极祸。
使用該注解完成屬性注入時慈格,類中無需 setter。當然遥金,若屬性有 setter浴捆,則也可將其加到 setter 上。
舉例:
byType 自動注入@Autowired(掌握)
需要在引用屬性上使用注解@Autowired汰规,該注解默認使用按類型自動裝配 Bean 的方式汤功。
使用該注解完成屬性注入時,類中無需 setter溜哮。當然,若屬性有 setter色解,則也可將其加到 setter 上茂嗓。
舉例:
byName 自動注入@Autowired 與@Qualifier(掌握)
需要在引用屬性上聯(lián)合使用注解@Autowired 與@Qualifier。@Qualifier 的 value 屬性用于指定要匹配的 Bean 的 id 值科阎。類中無需 set 方法述吸,也可加到 set 方法上。
舉例:
@Autowired 還有一個屬性 required锣笨,默認值為 true蝌矛,表示當匹配失敗后,會終止程序運行错英。若將其值設(shè)置為 false入撒,則匹配失敗,將被忽略椭岩,未匹配的屬性值為 null茅逮。
JDK 注解@Resource 自動注入(掌握)
Spring提供了對 jdk中@Resource注解的支持璃赡。@Resource 注解既可以按名稱匹配Bean,也可以按類型匹配 Bean献雅。默認是按名稱注入碉考。使用該注解,要求 JDK 必須是 6 及以上版本挺身。@Resource 可在屬性上侯谁,也可在 set 方法上。
(1) byType 注入引用類型屬性
@Resource 注解若不帶任何參數(shù)章钾,采用默認按名稱的方式注入墙贱,按名稱不能注入 bean, 則會按照類型進行 Bean 的匹配注入伍玖。
舉例:
(2) byName 注入引用類型屬性
@Resource 注解指定其 name 屬性嫩痰,則 name 的值即為按照名稱進行匹配的 Bean 的 id。
舉例:
注解與 XML 的對比
注解優(yōu)點是:
? 方便
? 直觀
? 高效(代碼少窍箍,沒有配置文件的書寫那么復(fù)雜)串纺。
其弊端也顯而易見:以硬編碼的方式寫入到 Java 代碼中,修改是需要重新編譯代碼的椰棘。
XML 方式優(yōu)點是:
? 配置和代碼是分離的
? 在 xml 中做修改纺棺,無需編譯代碼,只需重啟服務(wù)器即可將新的配置加載邪狞。
xml 的缺點是:編寫麻煩祷蝌,效率低,大型項目過于復(fù)雜帆卓。
AOP 面向切面編程
不使用 AOP 的開發(fā)方式(理解)
Step1:項目 aop_leadin1
先定義好接口與一個實現(xiàn)類巨朦,該實現(xiàn)類中除了要實現(xiàn)接口中的方法外,還要再寫兩個非業(yè)務(wù)方法剑令。非業(yè)務(wù)方法也稱為交叉業(yè)務(wù)邏輯:
doTransaction():用于事務(wù)處理
doLog():用于日志處理
然后糊啡,再使接口方法調(diào)用它們。接口方法也稱為主業(yè)務(wù)邏輯吁津。
接口:
Step2:項目 aop_leadin2
當然棚蓄,也可以有另一種解決方案:將這些交叉業(yè)務(wù)邏輯代碼放到專門的工具類或處理類中,由主業(yè)務(wù)邏輯調(diào)用碍脏。
Step3:項目 aopleadin3
以上的解決方案梭依,還是存在弊端:交叉業(yè)務(wù)與主業(yè)務(wù)深度耦合在一起。當交叉業(yè)務(wù)邏輯較多時典尾,在主業(yè)務(wù)代碼中會出現(xiàn)大量的交叉業(yè)務(wù)邏輯代碼調(diào)用語句役拴,大大影響了主業(yè)務(wù)邏輯的可讀性,降低了代碼的可維護性急黎,同時也增加了開發(fā)難度扎狱。
所以侧到,可以采用動態(tài)代理方式。在不修改主業(yè)務(wù)邏輯的前提下淤击,擴展和增強其功能匠抗。
功能增強:
AOP 概述
AOP 簡介
AOP(Aspect Orient Programming),面向切面編程污抬。面向切面編程是從動態(tài)角度考慮程序運行過程汞贸。
AOP 底層,就是采用動態(tài)代理模式實現(xiàn)的印机。采用了兩種代理:JDK 的動態(tài)代理矢腻,與 CGLIB的動態(tài)代理。
AOP 為 Aspect Oriented Programming 的縮寫射赛,意為:面向切面編程多柑,可通過運行期動態(tài)代理實現(xiàn)程序功能的統(tǒng)一維護的一種技術(shù)。AOP 是 Spring 框架中的一個重要內(nèi)容楣责。利用 AOP可以對業(yè)務(wù)邏輯的各個部分進行隔離竣灌,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低,提高程序的可重用性秆麸,同時提高了開發(fā)的效率初嘹。
面向切面編程,就是將交叉業(yè)務(wù)邏輯封裝成切面沮趣,利用 AOP 容器的功能將切面織入到主業(yè)務(wù)邏輯中屯烦。所謂交叉業(yè)務(wù)邏輯是指,通用的房铭、與主業(yè)務(wù)邏輯無關(guān)的代碼驻龟,如安全檢查、事務(wù)缸匪、日志迅脐、緩存等。
若不使用 AOP豪嗽,則會出現(xiàn)代碼糾纏,即交叉業(yè)務(wù)邏輯與主業(yè)務(wù)邏輯混合在一起豌骏。這樣龟梦,會使主業(yè)務(wù)邏輯變的混雜不清。
例如窃躲,轉(zhuǎn)賬计贰,在真正轉(zhuǎn)賬業(yè)務(wù)邏輯前后,需要權(quán)限控制蒂窒、日志記錄躁倒、加載事務(wù)荞怒、結(jié)束事務(wù)等交叉業(yè)務(wù)邏輯完慧,而這些業(yè)務(wù)邏輯與主業(yè)務(wù)邏輯間并無直接關(guān)系匈庭。但,它們的代碼量所占比重能達到總代碼量的一半甚至還多饼记。它們的存在象迎,不僅產(chǎn)生了大量的“冗余”代碼荧嵌,還大大干擾了主業(yè)務(wù)邏輯---轉(zhuǎn)賬。
面向切面編程對有什么好處砾淌?
1.減少重復(fù)啦撮;
2.專注業(yè)務(wù);
注意:面向切面編程只是面向?qū)ο缶幊痰囊环N補充汪厨。
使用 AOP 減少重復(fù)代碼赃春,專注業(yè)務(wù)實現(xiàn):
AOP 編程術(shù)語(掌握)
(1) 切面(Aspect)
切面泛指交叉業(yè)務(wù)邏輯。上例中的事務(wù)處理劫乱、日志處理就可以理解為切面织中。常用的切面是通知(Advice)。實際就是對主業(yè)務(wù)邏輯的一種增強要拂。
(2) 連接點(JoinPoint)
連接點指可以被切面織入的具體方法抠璃。通常業(yè)務(wù)接口中的方法均為連接點。
(3) 切入點(Pointcut)
切入點指聲明的一個或多個連接點的集合脱惰。通過切入點指定一組方法搏嗡。
被標記為 final 的方法是不能作為連接點與切入點的。因為最終的是不能被修改的拉一,不能被增強的采盒。
(4) 目標對象(Target)
目 標 對 象 指 將 要 被 增 強 的 對 象 。 即 包 含 主 業(yè) 務(wù) 邏 輯 的 類 的 對 象 蔚润。 上 例 中 的StudentServiceImpl 的對象若被增強磅氨,則該類稱為目標類,該類對象稱為目標對象嫡纠。當然烦租,不被增強,也就無所謂目標不目標了除盏。
(5) 通知(Advice)
通知表示切面的執(zhí)行時間叉橱,Advice 也叫增強。上例中的 MyInvocationHandler 就可以理解為是一種通知者蠕。換個角度來說窃祝,通知定義了增強代碼切入到目標代碼的時間點,是目標方法執(zhí)行之前執(zhí)行踱侣,還是之后執(zhí)行等粪小。通知類型不同大磺,切入時間不同。
切入點定義切入的位置探膊,通知定義切入的時間杠愧。
AspectJ 對 AOP 的實現(xiàn)(掌握)
對于 AOP 這種編程思想,很多框架都進行了實現(xiàn)突想。Spring 就是其中之一殴蹄,可以完成面向切面編程。然而猾担,AspectJ 也實現(xiàn)了 AOP 的功能袭灯,且其實現(xiàn)方式更為簡捷,使用更為方便绑嘹,而且還支持注解式開發(fā)稽荧。所以,Spring 又將 AspectJ 的對于 AOP 的實現(xiàn)也引入到了自己的框架中工腋。
在 Spring 中使用 AOP 開發(fā)時姨丈,一般使用 AspectJ 的實現(xiàn)方式。
AspectJ 簡介
AspectJ 是一個優(yōu)秀面向切面的框架擅腰,它擴展了 Java 語言蟋恬,提供了強大的切面實現(xiàn)。
官網(wǎng)地址:http://www.eclipse.org/aspectj/
AspetJ 是 Eclipse 的開源項目趁冈,官網(wǎng)介紹如下:
a seamless aspect-oriented extension to the Javatm programming language(一種基于 Java 平臺的面向切面編程的語言)
Java platform compatible(兼容 Java 平臺歼争,可以無縫擴展)
easy to learn and use(易學易用)
AspectJ 的通知類型(理解)
AspectJ 中常用的通知有五種類型:
(1)前置通知
(2)后置通知
(3)環(huán)繞通知
(4)異常通知
(5)最終通知
AspectJ 的切入點表達式(掌握)
AspectJ 定義了專門的表達式用于指定切入點。表達式的原型是:
execution(modifiers-pattern? ret-type-pattern
declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)
解釋:
modifiers-pattern] 訪問權(quán)限類型
ret-type-pattern 返回值類型
declaring-type-pattern 包名類名
name-pattern(param-pattern) 方法名(參數(shù)類型和參數(shù)個數(shù))
throws-pattern 拋出異常類型
渗勘?表示可選的部分
以上表達式共 4 個部分沐绒。
execution(訪問權(quán)限 方法返回值 方法聲明(參數(shù)) 異常類型)
切入點表達式要匹配的對象就是目標方法的方法名。所以旺坠,execution 表達式中明顯就是方法的簽名乔遮。注意,表達式中黑色文字表示可省略部分取刃,各部分間用空格分開蹋肮。在其中可以使用以下符號:
舉例:
execution(public * *(..))
指定切入點為:任意公共方法。
execution(* set*(..))
指定切入點為:任何一個以“set”開始的方法璧疗。
execution(* com.xyz.service.*.*(..))
指定切入點為:定義在 service 包里的任意類的任意方法括尸。
execution(* com.xyz.service..*.*(..))
指定切入點為:定義在 service 包或者子包里的任意類的任意方法〔≌保“..”出現(xiàn)在類名中時,后面必須跟“*”屁柏,表示包啦膜、子包下的所有類有送。
execution(* *..service.*.*(..))
指定所有包下的 serivce 子包下所有類(接口)中所有方法為切入點
execution(* *.service.*.*(..))
指定只有一級包下的 serivce 子包下所有類(接口)中所有方法為切入點
execution(* *.ISomeService.*(..))
指定只有一級包下的 ISomeSerivce 接口中所有方法為切入點
execution(* *..ISomeService.*(..))
指定所有包下的 ISomeSerivce 接口中所有方法為切入點
execution(* com.xyz.service.IAccountService.*(..))
指定切入點為:IAccountService 接口中的任意方法。
execution(* com.xyz.service.IAccountService+.*(..))
指定切入點為:IAccountService 若為接口僧家,則為接口中的任意方法及其所有實現(xiàn)類中的任意方法雀摘;若為類,則為該類及其子類中的任意方法八拱。
execution(* joke(String,int)))
指定切入點為:所有的 joke(String,int)方法阵赠,且 joke()方法的第一個參數(shù)是 String,第二個參數(shù)是 int肌稻。如果方法中的參數(shù)類型是 java.lang 包下的類清蚀,可以直接使用類名,否則必須使用全限定類名爹谭,如 joke( java.util.List, int)枷邪。
execution(* joke(String,*)))
指定切入點為:所有的 joke()方法,該方法第一個參數(shù)為 String诺凡,第二個參數(shù)可以是任意類型东揣,如joke(String s1,String s2)和joke(String s1,double d2)都是,但joke(String s1,double d2,String
s3)不是腹泌。
execution(* joke(String,..)))
指定切入點為:所有的 joke()方法嘶卧,該方法第一個參數(shù)為 String,后面可以有任意個參數(shù)且參數(shù)類型不限凉袱,如 joke(String s1)芥吟、joke(String s1,String s2)和 joke(String s1,double d2,String s3)
都是。
execution(* joke(Object))
指定切入點為:所有的 joke()方法绑蔫,方法擁有一個參數(shù)运沦,且參數(shù)是 Object 類型。joke(Object ob)是配深,但携添,joke(String s)與 joke(User u)均不是。
execution(* joke(Object+)))
指定切入點為:所有的 joke()方法篓叶,方法擁有一個參數(shù)烈掠,且參數(shù)是 Object 類型或該類的子類。不僅 joke(Object ob)是缸托,joke(String s)和 joke(User u)也是左敌。
AspectJ 的開發(fā)環(huán)境(掌握)
(1) maven 依賴
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring- context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
插件
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
(2) 引入 AOP 約束
在 AspectJ 實現(xiàn) AOP 時,要引入 AOP 的約束俐镐。配置文件中使用的 AOP 約束中的標簽矫限,均是 AspectJ 框架使用的,而非 Spring 框架本身在實現(xiàn) AOP 時使用的。
AspectJ 對于 AOP 的實現(xiàn)有注解和配置文件兩種方式叼风,常用是注解方式取董。
AspectJ 基于注解的 AOP 實現(xiàn)(掌握)
AspectJ 提供了以注解方式對于 AOP 的實現(xiàn)。
(1) 實現(xiàn)步驟
A无宿、Step1:定義業(yè)務(wù)接口與實現(xiàn)類
B茵汰、 Step2:定義切面類
類中定義了若干普通方法,將作為不同的通知方法孽鸡,用來增強功能蹂午。
C、 Step3:聲明目標對象切面類對象
D彬碱、Step4:注冊 AspectJ 的自動代理
在定義好切面 Aspect 后豆胸,需要通知 Spring 容器,讓容器生成“目標類+ 切面”的代理對象堡妒。這個代理是由容器自動生成的配乱。只需要在 Spring 配置文件中注冊一個基于 aspectj 的自動代理生成器,其就會自動掃描到@Aspect 注解皮迟,并按通知類型與切入點搬泥,將其織入,并生成代理伏尼。
<aop:aspectj-autoproxy/>的底層是由 AnnotationAwareAspectJAutoProxyCreator 實現(xiàn)的忿檩。從其類名就可看出,是基于 AspectJ 的注解適配自動代理生成器爆阶。
其工作原理是燥透,<aop:aspectj-autoproxy/>通過掃描找到@Aspect 定義的切面類,再由切面類根據(jù)切入點找到目標類的目標方法辨图,再由通知類型找到切入的時間點班套。
E、 Step5:測試類中使用目標對象的 id
(2) [掌握]@Before 前置通知-方法有 JoinPoint 參數(shù)
在目標方法執(zhí)行之前執(zhí)行故河。被注解為前置通知的方法吱韭,可以包含一個 JoinPoint 類型參數(shù)。該類型的對象本身就是切入點表達式鱼的。通過該參數(shù)理盆,可獲取切入點表達式、方法簽名凑阶、目標對象等猿规。
不光前置通知的方法,可以包含一個 JoinPoint 類型參數(shù)宙橱,所有的通知方法均可包含該參數(shù)姨俩。
(3) [掌握]@AfterReturning 后置通知-注解有 returning 屬性
在目標方法執(zhí)行之后執(zhí)行蘸拔。由于是目標方法之后執(zhí)行,所以可以獲取到目標方法的返回值哼勇。該注解的 returning 屬性就是用于指定接收方法返回值的變量名的都伪。所以,被注解為后置通知的方法积担,除了可以包含 JoinPoint 參數(shù)外,還可以包含用于接收返回值的變量猬仁。該變量最好為 Object 類型帝璧,因為目標方法的返回值可能是任何類型。
接口增加方法:
實現(xiàn)方法:
定義切面:
(4) [掌握]@Around 環(huán)繞通知-增強方法有 ProceedingJoinPoint參數(shù)
在目標方法執(zhí)行之前之后執(zhí)行湿刽。被注解為環(huán)繞增強的方法要有返回值的烁,Object 類型。并且方法可以包含一個 ProceedingJoinPoint 類型的參數(shù)诈闺。接口 ProceedingJoinPoint 其有一個proceed()方法渴庆,用于執(zhí)行目標方法。若目標方法有返回值雅镊,則該方法的返回值就是目標方法的返回值襟雷。最后,環(huán)繞增強方法將其返回值返回仁烹。該增強方法實際是攔截了目標方法的執(zhí)行耸弄。
接口增加方法:
接口方法的實現(xiàn):
定義切面:
(5)[了解]@AfterThrowing 異常通知-注解中有 throwing 屬性
在目標方法拋出異常后執(zhí)行。該注解的 throwing 屬性用于指定所發(fā)生的異常類對象卓缰。當然计呈,被注解為異常通知的方法可以包含一個參數(shù) Throwable,參數(shù)名稱為 throwing 指定的名稱征唬,表示發(fā)生的異常對象捌显。
增加業(yè)務(wù)方法:
方法實現(xiàn):
定義切面:
(6) [了解]@After 最終通知
無論目標方法是否拋出異常,該增強均會被執(zhí)行总寒。
增加方法:
方法實現(xiàn):
定義切面:
(7) @Pointcut 定義切入點
當較多的通知增強方法使用相同的 execution 切入點表達式時扶歪,編寫、維護均較為麻煩偿乖。AspectJ 提供了@Pointcut 注解击罪,用于定義 execution 切入點表達式。
其用法是贪薪,將@Pointcut 注解在一個方法之上媳禁,以后所有的 execution 的 value 屬性值均可使用該方法名作為切入點。代表的就是@Pointcut 定義的切入點画切。這個使用@Pointcut 注解的方法一般使用 private 的標識方法竣稽,即沒有實際作用的方法。