前言:
前面我們簡單介紹了Spring的IOC容器,其實(shí),本篇要講述的是Spring中另外一個(gè)常用的模塊AOP。在看本篇之前,要確保已經(jīng)有代理模式的基本知識(shí),這樣看起來才不會(huì)費(fèi)勁!
1.AOP簡介
AOP(面向切面編程)是對(duì)OOP中遇到的一些問題的補(bǔ)充,是OOP的擴(kuò)展,在不改變原有代碼的情況下增加新的功能,比如我們常見的日志功能,權(quán)限,異常處理,緩存等公共業(yè)務(wù)(即與軟件的開發(fā)業(yè)務(wù)沒有關(guān)聯(lián))。
舉一個(gè)簡單的例子:
public class UserService{
//添加用戶的方法
public void add() {
//加一個(gè)輸出日志
log();
//代碼邏輯
}
public void delete() {
//加一個(gè)輸出日志
log();
//代碼邏輯
}
}
我們可以看到如果按照我們以前的編寫代碼的方式的話,每個(gè)方法都有寫重復(fù)的代碼,而且log()這個(gè)方法還不是我們主要的業(yè)務(wù)邏輯,這樣編碼的話充滿耦合性,這時(shí)候我們就要利用Spring的AOP技術(shù)將log()這一塊抽取出來
Spring的AOP底層實(shí)現(xiàn)
Spring的AOP底層用到了我們兩種動(dòng)態(tài)代理模式JDK的動(dòng)態(tài)代理(針對(duì)實(shí)現(xiàn)了接口的類產(chǎn)生代理),Cglib的動(dòng)態(tài)代理(針對(duì)沒有實(shí)現(xiàn)接口的類產(chǎn)生代理,應(yīng)用的是底層的字節(jié)碼增強(qiáng)的技術(shù),生成當(dāng)前的子類對(duì)象)
2.AOP開發(fā)中的相關(guān)術(shù)語
關(guān)注點(diǎn):重復(fù)代碼就叫做關(guān)注點(diǎn)即公共業(yè)務(wù)。
Joinpoint(連接點(diǎn)):所謂連接點(diǎn)是指那些被攔截到的點(diǎn)。在Spring中,這些點(diǎn)指的是方法,因?yàn)镾pring只支持方法類型的連接點(diǎn)。
Pointcut(切入點(diǎn)):所謂切入點(diǎn)是指我們要對(duì)哪些Joinpoint(方法)進(jìn)行攔截的定義。
Advice(通知/增強(qiáng)):所謂通知是指攔截到Joinpoint(方法)之后所要做的事情就是通知。通知分為前置通知、后置通知、異常通知、最終通知和環(huán)繞通知(切面要完成的功能)。
Aspect(切面):是切入點(diǎn)和通知的結(jié)合。
Target(目標(biāo)對(duì)象):代理的目標(biāo)對(duì)象(要增強(qiáng)的類)
Weaving(織入):是指把增強(qiáng)應(yīng)用到目標(biāo)對(duì)象來創(chuàng)建新的代理對(duì)象的過程。Spring采用動(dòng)態(tài)代理織入,而AspectJ采用編譯期織入和類裝載期織入。
Proxy(代理):一個(gè)類被AOP織入增強(qiáng)后,就產(chǎn)生一個(gè)結(jié)果代理類。
Introduction(引介):引介是一種特殊的通知在不修改類代碼的前提下,Introduction可以在運(yùn)行期為類動(dòng)態(tài)地添加一些方法或Field。
我們還是用上面的例子解釋一下這些術(shù)語:連接點(diǎn):就是里面的三個(gè)方法
切入點(diǎn):假設(shè)里面的add()方法加入了日志的功能,這個(gè)就add()方法就是切入點(diǎn)
通知/增強(qiáng):比如增強(qiáng)UserService類里面的add方法,在add方法中添加了日志功能,這個(gè)日志功能就稱為增強(qiáng)。
通知類型:前置通知:在增強(qiáng)的方法執(zhí)行之前進(jìn)行操作。
后置通知:在增強(qiáng)的方法執(zhí)行之后進(jìn)行操作。
環(huán)繞通知:在增強(qiáng)的方法執(zhí)行之前和執(zhí)行之后進(jìn)行操作。
最終通知:增強(qiáng)了兩個(gè)方法,執(zhí)行第一個(gè)方法,執(zhí)行第二個(gè)方法,在第二個(gè)方法執(zhí)行之后進(jìn)行操作。
也可理解為后置通知后面執(zhí)行的通知或者無論目標(biāo)方法是否出現(xiàn)異常,最終通知都會(huì)執(zhí)行。
異常通知:程序出現(xiàn)異常之后執(zhí)行的通知。切面:假設(shè)有個(gè)日志功能,把日志功能加到add()方法上面的過程
目標(biāo)對(duì)象:UserService
織入:把日志功能增加到目標(biāo)對(duì)象(UserService)的過程
3.使用SpringAOP開發(fā)步驟(基于AspectJXML方式)
注意對(duì)切面的配置有兩種方式:一種是傳統(tǒng)的aop聯(lián)盟制定的aop通知的配置,一種是spring支持的AspectJ框架的切面

1.引入相關(guān)的jar文件
spring-aop-3.2.5.RELEASE.jar 【spring3.2源碼】
aopalliance.jar 【spring2.5源碼/lib/aopalliance】
aspectjweaver.jar 【spring2.5源碼/lib/aspectj】或【aspectj-1.8.2\lib】
aspectjrt.jar 【spring2.5源碼/lib/aspectj】或【aspectj-1.8.2\lib】
2.bean.xml中引入aop名稱空間
<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.xsd">
3.創(chuàng)建業(yè)務(wù)功能代碼和公共代碼
public class TestAop {
public void sayHello(String name) {
System.out.println("sayHello方法被執(zhí)行");
}
}
public class Log {
//方法執(zhí)行前
public void before() {
System.out.println("方法執(zhí)行前" );
}
//方法執(zhí)行后
public void after() {
System.out.println("方法執(zhí)行后" );
}
}
4.配置bean.xml
<!--配置業(yè)務(wù)Bean-->
<bean id="testAop" class="test.TestAop"></bean>
<!--配置切面Bean-->
<bean id="log" class="test.Log"></bean>
<!--配置切面-->
<aop:config>
<!--定義切入表達(dá)式,要攔截什么方法-->
<aop:pointcut id="pointCut" expression="execution(* test.TestAop.*(..))"></aop:pointcut>
<!--指定切面是那個(gè)類-->
<aop:aspect ref="log">
<!--指定來攔截的時(shí)候執(zhí)行切面類的哪些方法-->
<!-- 配置前置通知 -->
<aop:before method="before" pointcut-ref="pointCut" ></aop:before>
<!-- 配置后通知 -->
<aop:after method="after" pointcut-ref="pointCut" />
</aop:aspect>
</aop:config>
結(jié)果

切入點(diǎn)表達(dá)式
通過execution函數(shù),可以定義切點(diǎn)的方法切入。
語法為:execution(<訪問修飾符>?<返回類型><方法名>(<參數(shù)>)<異常>)。
例如:
- 匹配所有類的public方法:execution(public .(..))
- 匹配指定包下所有類的方法:execution(* cn.itcast.dao.*(..)),但不包含子包
- execution(* cn.itcast.dao..(..)),..表示包、子孫包下所有類。
- 匹配指定類所有方法:execution(* cn.itcast.service.UserService.*(..))
- 匹配實(shí)現(xiàn)特定接口的所有類的方法:execution(* cn.itcast.dao.GenericDAO+.*(..))
- 匹配所有save開頭的方法:execution(* save*(..))
- 匹配所有類里面的所有的方法:execution(* .(..))
表達(dá)式有如下形式:
<!-- 【攔截所有public方法】 -->
<!--<aop:pointcut expression="execution(public * *(..))" id="pt"/>-->
<!-- 【攔截所有save開頭的方法 】 -->
<!--<aop:pointcut expression="execution(* save*(..))" id="pt"/>-->
<!-- 【攔截指定類的指定方法, 攔截時(shí)候一定要定位到方法】 -->
<!--<aop:pointcut expression="execution(public * cn.itcast.g_pointcut.OrderDao.save(..))" id="pt"/>-->
<!-- 【攔截指定類的所有方法】 -->
<!--<aop:pointcut expression="execution(* cn.itcast.g_pointcut.UserDao.*(..))" id="pt"/>-->
<!-- 【攔截指定包,以及其自包下所有類的所有方法】 -->
<!--<aop:pointcut expression="execution(* cn..*.*(..))" id="pt"/>-->
<!-- 【多個(gè)表達(dá)式】 -->
<!--<aop:pointcut expression="execution(* cn.itcast.g_pointcut.UserDao.save()) || execution(* cn.itcast.g_pointcut.OrderDao.save())" id="pt"/>-->
<!--<aop:pointcut expression="execution(* cn.itcast.g_pointcut.UserDao.save()) or execution(* cn.itcast.g_pointcut.OrderDao.save())" id="pt"/>-->
<!-- 下面2個(gè)且關(guān)系的,沒有意義 -->
<!--<aop:pointcut expression="execution(* cn.itcast.g_pointcut.UserDao.save()) && execution(* cn.itcast.g_pointcut.OrderDao.save())" id="pt"/>-->
<!--<aop:pointcut expression="execution(* cn.itcast.g_pointcut.UserDao.save()) and execution(* cn.itcast.g_pointcut.OrderDao.save())" id="pt"/>-->
<!-- 【取非值】 -->
<!--<aop:pointcut expression="!execution(* cn.itcast.g_pointcut.OrderDao.save())" id="pt"/>-->
4.使用注解的形式
在實(shí)際開發(fā)中,如果我們可以用注解形式的話,還是推薦使用注解的形式
1.在bean.xml中開啟AOP注解方式
<!-- 開啟aop注解方式 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
切面類
@Aspect//指定為切面類
public class Log {
//方法執(zhí)行前
@Before("execution(* test.TestAop.*(..))")
public void before() {
System.out.println("方法執(zhí)行前");
}
//方法執(zhí)行后
@After("execution(* test.TestAop.*(..))")
public void after() {
System.out.println("方法執(zhí)行后" );
}
}

優(yōu)化:
@Aspect//指定為切面類
public class Log {
// 指定切入點(diǎn)表達(dá)式,攔截哪個(gè)類的哪些方法
// 指定切入點(diǎn)表達(dá)式,攔截哪個(gè)類的哪些方法
@Pointcut("execution(* test.TestAop.*(..))")
public void pt() {
}
//方法執(zhí)行前
@Before ("pt()")
public void before() {
System.out.println("方法執(zhí)行前");
}
//方法執(zhí)行后
@After( "pt()")
public void after() {
System.out.println("方法執(zhí)行后" );
}
}