AOP爲Aspect Oriented Programming 的縮寫,意識爲面向切面的編程,是經過預編譯和運行期動態代理實現程序功能的統一維護的一種技術
AOP是OOP(Object Oriented Programmin 面向對象編程)的延續,是軟件開發中的一個熱點,也是框架中的一個重要內容,是函數式編程的一種衍生範型,利用AOP能夠對業務邏輯的各個部分進行隔離,也使業務邏輯各部分的耦合性下降,提升程序的可重用性,同時提升了開發的效率
我先來說講什麼是切面
把一塊蛋糕切成兩塊,這個切口就是切面,;炒飯的時候,鍋和鍋鏟就是切面;web層級設計中,web層->網關層->服務層->數據層,每一層之間也是一個切面。編程中,對與對象之間,方法與方法之間,模塊與模塊之間都是一個個切面。java
如圖銀行的取款業務和查詢餘額業務有交叉的業務邏輯(所謂交叉業務邏輯是與主業務無關的代碼,好比安全檢查,事務,日誌等等),這裏指的是驗證用戶的業務。這會致使代碼糾纏,交叉業務邏輯與主業務邏輯混合在一塊兒,這會致使業務邏輯的混合不清,這時候就要用到AOPweb
使用AOP能夠幫助咱們簡化代碼,咱們在寫代碼的時候可不寫這個驗證用戶的業務,能夠在另外一個地方寫好驗證用戶的代碼,而後告訴Spring那幾個地方須要這些代碼,讓Spring加過去便可,若是有多個控制流的話,會大大的減小時間,而AOP不會把代碼加入到源文件中可是他會正確的影響最後的機器代碼spring
上面那個 驗證用戶 的方框,咱們能夠把它當成一塊板子,在這塊板子上插入一些控制流程,這塊板子就能夠當成是 AOP 中的一個切面。因此 AOP 的本質是在一系列的縱向的控制流程中,把那些相同的子流程提取成一個橫向的面,把縱向流程畫成一條直線,而 AOP 至關於把相同的地方連起來了(這幅圖是真的形象,好好體會一下應該不難),這個驗證用戶的子流程 就成了一條直線,也能夠理解成一個切面,這裏只插了三個流程,若是其餘流程也須要這個子流程,也能夠插到其餘地方去。express
做用:在不修改源碼的狀況下對方法進行加強
優點:提升代碼的可複用性,提升開發效率,便於維護編程
AOP的底層是經過Spring動態代理技術實現的,在運行期間經過動態代理,獲取代理對象,代理方法執行時加強功能介入,在去調用目標對象的方法,從而完成功能加強。安全
jdk代理:基於接口的動態代理技術bash
cglib代理:基於父類的動態代理技術數據結構
咱們來逐一講解這兩個代理方式的差異
jdk代理
demo內容:user類實現一個userImp接口,對user類進行動態代理app
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(); } }
運行結果
放這張表情包的目的是想提醒你們休息一下想必你們都看了好久的電腦了,能夠開窗看看外面,休息休息
這裏就簡單的講一下流程
目標類
這裏僅僅是一個類沒有實現任何接口
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(); } }
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中,框架會根據是否實現了接口來選擇使用那種動態代理方式
快速入門
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的開發步驟
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(){}