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(){}
以上就是Spring的AOP的概念及其使用方法,我會不斷的學習,也會不斷更新我的學習文章,主要有java和數據結構兩個方面,有想要一起學習的伙伴可以私信或則關注我,共勉

我的博客即將同步至騰訊雲+社區,邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=2ppmo0x1rxk48
