Spring系列之aop aop是什麼?+xml方式實現aop+註解方式實現aop
什麼是AOP?
AOP為Aspect Oriented Programming 的縮寫,意識為面向切面的程式設計,是通過預編譯和執行期動態代理實現程式功能的統一維護的一種技術
AOP是OOP(Object Oriented Programmin 物件導向程式設計)的延續,是軟體開發中的一個熱點,也是框架中的一個重要內容,是函數語言程式設計的一種衍生範型,利用AOP可以對業務邏輯的各個部分進行隔離,也使業務邏輯各部分的耦合性降低,提高程式的可重用性,同時提高了開發的效率
我先來講講什麼是切面
把一塊蛋糕切成兩塊,這個切口就是切面,;炒飯的時候,鍋和鍋鏟就是切面;web層級設計中,web層->閘道器層->服務層->資料層,每一層之間也是一個切面。程式設計中,對與物件之間,方法與方法之間,模組與模組之間都是一個個切面。
我們使用一個銀行管理系統來說說為什麼要使用面向切面程式設計。
如圖銀行的取款業務和查詢餘額業務有交叉的業務邏輯(所謂交叉業務邏輯是與主業務無關的程式碼,比如安全檢查,事務,日誌等等),這裡指的是驗證使用者的業務。這會導致程式碼糾纏,交叉業務邏輯與主業務邏輯混合在一起,這會導致業務邏輯的混合不清,這時候就要用到AOP
使用AOP可以幫助我們簡化程式碼,我們在寫程式碼的時候可不寫這個驗證使用者的業務,可以在另一個地方寫好驗證使用者的程式碼,然後告訴Spring那幾個地方需要這些程式碼,讓Spring加過去即可,如果有多個控制流的話,會大大的減少時間,而AOP不會把程式碼加入到原始檔中但是他會正確的影響最後的機器程式碼
上面那個 驗證使用者 的方框,我們可以把它當成一塊板子,在這塊板子上插入一些控制流程,這塊板子就可以當成是 AOP 中的一個切面。所以 AOP 的本質是在一系列的縱向的控制流程中,把那些相同的子流程提取成一個橫向的面,把縱向流程畫成一條直線,而 AOP 相當於把相同的地方連起來了(這幅圖是真的形象,好好體會一下應該不難),這個驗證使用者的子流程 就成了一條直線,也可以理解成一個切面,這裡只插了三個流程,如果其他流程也需要這個子流程,也可以插到其他地方去。
AOP的優勢與作用
作用:在不修改原始碼的情況下對方法進行增強
優勢:提高程式碼的可複用性,提高開發效率,便於維護
AOP的底層實現
AOP的底層是通過Spring動態代理技術實現的,在執行期間通過動態代理,獲取代理物件,代理方法執行時增強功能介入,在去呼叫目標物件的方法,從而完成功能增強。
AOP的動態代理技術
jdk代理
jdk代理:基於介面的動態代理技術
cglib代理:基於父類的動態代理技術
我們來逐一講解這兩個代理方式的差別
jdk代理
demo內容:user類實現一個userImp介面,對user類進行動態代理
user類程式碼
package com.pjh.user;
public interface user {
public void save();
}
userImp程式碼
package com.pjh.user.Imp;
import com.pjh.user.user;
public class userImp implements user {
public void save() {
System.out.println("save run....");
}
}
對save方法進行增強
這裡使用兩種方式
方式一匿名內部類:即InvocationHandler直接使用匿名內部類的方式來建立
package com.pjh.test;
import com.pjh.user.Imp.userImp;
import com.pjh.user.user;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class main {
public static void main(String[] args) {
//建立目標物件
final userImp userImp=new userImp();
//呼叫proxy類的靜態方法來建立代理物件
//Proxy.newProxyInstance(類載入器,獲取目標物件的介面,實現動態代理介面)
user userproxy = (user)Proxy.newProxyInstance(userImp.class.getClassLoader(),userImp.getClass().getInterfaces(), new InvocationHandler() {
//invoke(代理類,被代理的方法,引數)
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前置增強程式碼");
//當代理物件呼叫真實物件的方法時,其會自動的跳轉到代理物件關聯的handler物件的invoke方法來進行呼叫
Object invoke = method.invoke(userImp);
System.out.println("後置增強程式碼");
return invoke;
}
});
userproxy.save();
}
}
執行結果
成功對方法進行了增強
方法二使用一個類繼承自InvocationHandler來實現
編寫InvocationHandler實現類
package com.pjh.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class InvocationHandlerImp implements InvocationHandler {
//所有類均繼承自object類
private Object object;
//寫一個帶參構造的方法,來引入目標物件
public InvocationHandlerImp(Object object) {
this.object = object;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("增強前");
Object invoke = method.invoke(object, args);
System.out.println("執行後的方法");
return invoke;
}
}
編寫測試類
package com.pjh.test;
import com.pjh.proxy.InvocationHandlerImp;
import com.pjh.user.Imp.userImp;
import com.pjh.user.user;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) {
//建立目標物件,即代理的真實物件
userImp person = new userImp();
//獲取處理器實現類InvocationHandlerImp
InvocationHandlerImp invocationHandlerImp = new InvocationHandlerImp(person);
//獲取代理物件
user o = (user)Proxy.newProxyInstance(userImp.class.getClassLoader(),
person.getClass().getInterfaces(),
invocationHandlerImp);
//呼叫方法
o.save();
}
}
執行結果
放這張表情包的目的是想提醒大家休息一下想必大家都看了很久的電腦了,可以開窗看看外面,休息休息
Cglib的動態代理
這裡就簡單的講一下流程
目標類
這裡僅僅是一個類沒有實現任何介面
package com.pjh.user;
public class person {
public void save(){
System.out.println("save");
}
}
主函式
package com.pjh.test;
import com.pjh.user.person;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class test2 {
public static void main(String[] args) {
//設定目標物件
final person one = new person();
//建立增強器
Enhancer enhancer = new Enhancer();
//設定父類
enhancer.setSuperclass(person.class);
//設定回撥
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("前置增強程式碼");
Object invoke = method.invoke(one, objects);
System.out.println("後置增強");
return invoke;
}
});
//獲取代理物件
person oneproxy = (person)enhancer.create();
//呼叫增強後的方法
oneproxy.save();
}
}
AOP相關概念
String 的AOP實現底層就是對上面的動態代理進行了封裝,封裝後我們只需要對關注的部分進行程式碼進行編寫,並通過配置的方式完成對指定目標的方法增強
AOP的部分術語
Target(目標物件):代理的目標物件
Proxy(代理):一個類被AOP織入增強後,就產生一個結果代理類
Joinpoint(連線點):所謂連線點指那些被攔截到的點,在spring中這些點指的是方法,因為spring是隻支援方法型別的連線點
Pointcut(切入點):所謂切入點是指我們要對哪些Joinpoint進行攔截的定義,即被增強的方法
Jointpoint不一定是Pointcut但是Pointcut一定是Joinpoint
Advice(通知/增強):攔截到jointpoint之後要做的事情就是通知,封裝增強業務邏輯的方法
Aspect(切面):是切入點和通知的結合
Weaving(織入):是指把增強應用到目標物件來建立新的代理物件的過程,spring採用動態織入代理,而Aspect採用編譯織入和類裝載期織入,切點與通知結合的過程
AOP的實現內容
Spring框架監控切入點方法的執行,只要檢測到切入點被執行,就會使用代理機制,建立代理物件,根據通知類別,在代理物件的對應位置,將通知對應的功能織入,完成完整的程式碼邏輯執行
AOP底層使用什麼代理機制
在spring中,框架會根據是否實現了介面來選擇使用那種動態代理方式
基於XML的AOP開發
快速入門
1.匯入AOP的相關配置座標
2.建立目標介面和目標類(內有切入點)
3.建立切面類(內部有增強方法)
4.將目標類和切面類的物件建立權交給spring
5.在applicationContext.xml中配置織入關係
6.測試程式碼
1.匯入AOP的相關座標
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
</dependencies>
建立介面與實現類
介面
package com.pjh.user;
public interface userInterface {
public void save();
}
實現類
package com.pjh.user;
public class user implements userInterface{
public void save() {
System.out.println("save run...");
}
}
建立切面類
package com.pjh.enhance;
public class enhance {
public void enhance(){
System.out.println("這是增強程式碼!!!!");
}
}
將目標類和切面類的物件建立權交給spring
<bean id="daoImp" class="com.pjh.dao.Imp.daoImp"/>
<bean id="aspect" class="com.pjh.aspect.aspect"/>
引入名稱空間與約束路徑
<?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.xsd">
</beans>
配置切點表示式和前置增強之間的關係
切點表示式的配置語法
excution(【修飾符】返回值型別 包名.類名.方法名(引數))
通知的配置語法
<aop:通知型別 method=“切面類中方法名” pointcut=“切點表示式"></aop:通知型別>
這裡先寫個簡單的格式後面再給大家深入講
<aop:config >
<!--要切入的類-->
<aop:aspect ref="enhance">
<!--切入後的增強方法-->
<!--這是一個前置增強-->
<!--method切入後的增強方法-->
<!--pointcut對什麼類方法執行的時候進行增強-->
<aop:before
method="enhance" pointcut="execution(public void com.pjh.user.user.save())"></aop:before>
</aop:aspect>
</aop:config>
測試程式碼類
import com.pjh.user.userInterface;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class test {
/*如果是繼承自介面的一定要使用介面進行定義否則會報錯*/
@Autowired
private userInterface user;
@Test
public void test1(){
user.save();
}
}
結果
**
切點表示式的花樣寫法
**
表示式語法
excution(【修飾符】 返回值型別 包名.類名.方法名(引數))
返回值的修飾符可省略
返回值的類名,包名,方法名可以使用“ * ”星號代表任意
包名與類名之間的一個點" . "代表當前包下的所有類,兩個點“ .. ”代表當前包及其子包下的所有類
引數列表可以使用兩個點 " . . " 表示任意個數,任意型別的引數列表
//user類下的save方法增強
execution(public void com.pjh.one.user.save())
//對user類下的所有放回值為void的方法進行增強
execution(public void com.pjh.one.user.*(..))
//one包下所有類的所有方法進行增強
execution(* com.pjh.*.*.*(..))
//one包包括其子包下所有類的所有方法進行增強
execution(* com.pjh..*.*.*(..))
//任何包任何類任何方法
execution(* *.*..*.*. * (..))
切點表示式的抽取
當多個增強切點表示式相同時,可以將切點表示式進行抽取。在增強中使用pointcut-ref屬性替代pointcut屬性來引用切點表示式
<aop:config >
<aop:aspect ref="enhance">
<aop:pointcut id="myPointcut" expression="execution(public void com.pjh.user.user.save())"/>
<aop:before
method="enhance" pointcut-ref="myPointcut"></aop:before>
</aop:aspect>
</aop:config>
**
通知的型別
**
通知的配置語法
<aop:通知的型別 method=“切面中的方法名” pointcut=“切點表示式”/>
簡單的小總結
<aop:config >
<aop:aspect ref="切入類的名稱>
<aop:before
method="切入方法的名稱e" pointcut="切點表示式"></aop:before>
</aop:aspect>
</aop:config>
通知的型別:前置通知、後置通知、環繞通知、異常丟擲通知、最終通知
切點表示式的寫法:
excution(【修飾符】返回值型別 包名.類名.方法名(引數))
下面我們再來講講更加簡單的方法,即使用註解的方式
基於註解的AOP開發
註解aop的開發步驟
1.使用@Aspect標註切面類
2.使用@通知註解標註通知方法
3.在配置檔案中配置aop自動代理<aop:aspectj-autoproxy>
標註為一個切面類@Aspect
@Aspect
public class enhance {
}
使用註解來抽取切點表示式
@Pointcut(”註解表示式“)
/切點表示式方法的抽取,抽取方法是在切點內定義方法,
在方法內使用 @Pointcut註解切點表示式,然後在增強註解中進行引用/
@Pointcut("execution(public void com.pjh.user.user.save())")
public void mypoint(){}