學習筆記--Spring AOP
1.什麼是AOP
AOP就是Aspect Oritented Programming的縮寫,意思為面向切面程式設計,是通過預編譯和執行期動態代理實現程式功能的統一維護的一種技術。
切面:目標方法和增強功能結合在一起稱為一個切面。
AOP是OOP(面向物件程式設計)的延續,是軟體開發的一個熱點,也是Spring框架中的一個重要內容,是函數語言程式設計的一種衍生範型。
利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。
1.2 AOP的作用及其優勢
作用:在程式執行期間,在不修改原始碼的情況下對方法進行功能增強。
優勢:減少重複程式碼,提高開發效率,並且便於維護。
1.3 AOP的底層實現
AOP通過動態代理技術完成功能增強。
1.4 AOP的動態代理技術
常用的動態代理技術
JDK代理:基於介面的動態代理技術
cglib代理:基於父類的動態代理技術
JDK代理:
因為JDK是基於介面的動態代理技術,所以要寫一個目標介面類。
然後用目標類實現這個介面。
public class Target implements TargetInterface { public void save() { System.out.println("save running....."); } }
接下來建立一個增強類,用來對這個目標類的方法進行功能上的增強。
public class Advice { public void before(){ System.out.println("前置增強...."); } public void afterReturning(){ System.out.println("後置增強...."); } }
在主方法進行如下的配置來使用動態代理技術:
public class ProxyTest { public static void main(String[] args) { //目標物件 final Target target = new Target(); //增強物件 final Advice advice = new Advice(); //返回值 就是動態生成的代理物件 TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance( target.getClass().getClassLoader(), //目標物件類載入器 target.getClass().getInterfaces(), //目標物件相同的介面位元組碼物件陣列 new InvocationHandler() { //呼叫代理物件的任何方法 實質執行的都是invoke方法 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { advice.before(); //前置增強 Object invoke = method.invoke(target, args);//執行目標方法 advice.afterReturning(); //後置增強 return invoke; } } ); //呼叫代理物件的方法 proxy.save(); } }
用Proxy.newProxyInstance來new一個新的代理物件,這個方法有3個引數:
1.目標物件的類載入器。
2.目標物件相同的介面位元組碼物件陣列。
3.呼叫處理函式,呼叫代理物件的任何方法,實質執行的都是invoke方法。
cglib代理:
這種代理方式不需要介面,只需要增強類和目標類。
public class Advice { public void before(){ System.out.println("前置增強...."); } public void afterReturning(){ System.out.println("後置增強...."); } }
package com.itheima.proxy.cglib; public class Target { public void save() { System.out.println("save running...."); } }
在主方法中進行如下配置:
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 ProxyTest { public static void main(String[] args) { final Target target = new Target(); //增強物件 final Advice advice = new Advice(); //返回值 動態生成的代理物件 基於cglib //1、建立增強器 Enhancer enhancer = new Enhancer(); //2.設定父類(目標) enhancer.setSuperclass(Target.class); //3.設定回撥 enhancer.setCallback(new MethodInterceptor() { public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { //執行前置 advice.before(); //執行目標 Object invoke = method.invoke(target, args); //執行後置 advice.afterReturning(); return invoke; } }); //4.建立代理物件 Target proxy = (Target)enhancer.create(); proxy.save(); } }
1.5 AOP相關術語的概念
Target(目標物件):代理的目標物件。
Proxy(代理物件):一個類被AOP織入增強後,就產生一個結果代理類。
JoinPoint(連線點):連線點就是被攔截到可以被增強的方法。攔截到方法,才能進行增強。
PointCut(切入點):所謂切入點是指我們要對哪些Joinpoint進行攔截的定義。(真正被增強了的連線點)。
Advice(通知/增強):所謂通知是指攔截到JointPoint之後要做的事情就是通知。(增強方法)。
Aspect(切面):是切入點和通知的結合。
Weaving(織入):是指把增強應用到目標物件來建立新的代理物件過程。(切點+通知的過程)。
1.6 AOP開發明確的事項
1.需要編寫的內容
編寫核心業務程式碼(切點)。
編寫切面類,切面類中有通知(增強方法)。
在配置檔案中,配置織入關係,即將哪些通知與哪些連線點進行結合。
2.AOP技術實現的內容
Spring框架監控切入點方法的執行。一旦監控到切入點方法被執行,使用代理機制,動態建立目標物件的代理物件。
根據通知類別(前置方法、後置方法等),在代理物件的對應位置,將對應的功能織入,完成完整的程式碼邏輯執行。
3.AOP底層使用哪種代理方式
在Spring中,框架會根據目標類是否實現了介面來決定採用哪種動態代理的方式。
2. 基於XML的AOP開發
2.1 快速實現
1)匯入AOP相關座標
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.7</version> </dependency>
2)建立目標介面和目標類(內部有切點)
3)建立切面類(內部有增強方法)
4)將目標類和切面類的物件建立權交給Spring
<!-- 目標物件 --> <bean id="target" class="com.itheima.aop.Target"/> <!-- 切面物件 --> <bean id="myAspect" class="com.itheima.aop.MyAspect"/>
5)在applicationContext.xml中配置織入關係
<!-- 配置織入 告訴Spring框架哪些方法(切點)需要進行哪些增強(前置、後置) --> <aop:config> <!-- 宣告切面 --> <aop:aspect ref="myAspect"> <!-- 抽取切點表示式 --> <!-- 切點+通知 --> <!-- <aop:before method="before" pointcut="execution(public void com.itheima.aop.Target.save())"/>--> <!-- <aop:before method="before" pointcut="execution(public * com.itheima.aop.*.*(..))"/>--> <!-- <aop:after-returning method="afterReturning" pointcut="execution(public * com.itheima.aop.*.*(..))"/>--> <aop:around method="around" pointcut="execution(public * com.itheima.aop.*.*(..))"/> <aop:after-throwing method="afterThrowing" pointcut="execution(public * com.itheima.aop.*.*(..))"/> <aop:after method="after" pointcut="execution(public * com.itheima.aop.*.*(..))"/> </aop:aspect> </aop:config>
6)測試程式碼
2.2 XML配置AOP詳解
切點表示式的寫法
execution([修飾符] 返回值型別 包名、類名、方法名(引數))
訪問修飾符可以省略
返回值型別、包名、型別、方法名可以使用星號*代表任意。
包名與類名之間一個點,代表當前包下的類,兩個點...表示當前包及其子包下的類。
引數列表可以使用兩個點..表示任意個數,任意型別的引數列表。
//Target類下的所有沒有返回值的方法都是切點 execution(void com.xc.aop.Target.*(..) //不限定返回型別,aop包下的所有類的方法,不限定引數 execution(* com.xc.aop.*.*(..)) //不限定返回型別,aop包及其子包的所有類的方法,不限定引數 execution(* com.xc.aop.**.*(..))
通知的配置語法:
<aop:通知型別 method="切面類中的方法名" pointcut="切點表示式"/>
前置通知:before 後置通知 after-returning
環繞通知:around (切入點之前之後都執行)異常丟擲通知 throwing(出現異常執行)
最終通知:after(執行完了之後通知)
切點表示式的抽取:
當多個增強的切點表示式相同時,可以將切點表示式進行抽取,在增強中用pointcut-ref屬性來引用抽取後的切點表示式。
<!-- 抽取切點表示式 --> <aop:pointcut id="myPointcut" expression="execution(public * com.itheima.aop.*.*(..))"/>
使用:
<aop:around method="around" pointcut-ref="myPointcut"/> <aop:after method="after" pointcut-ref="myPointcut"/>
切點表示式可以用pointcut-ref來引用,這樣的好處是可以將所有一個配置的切點抽離出來,集中更改。
3.基於註解的AOP開發
3.1快速實現
1.建立目標介面和目標類
2.建立切面類
3.將目標類和切面類的物件建立權交給Spring
主要是給目標類和切面類加上@Component標籤
4.在切面類中使用註解配置織入關係
@Component("myAspect") @Aspect //標註當前MyAspect是一個切面類 public class MyAspect { //配置前置通知 @Before("execution(public * com.itheima.anno.*.*(..))") public void before(){ System.out.println("前置增強...."); } public void afterReturning(){ System.out.println("後置增強...."); } //ProceedingJoinPoint 正在執行的連線點->切點 public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("環繞前增強..."); Object proceed = pjp.proceed(); System.out.println("環繞後增強..."); return proceed; } public void afterThrowing(){ System.out.println("異常丟擲增強..."); } public void after(){ System.out.println("最終增強..."); } }
5.在配置檔案中開啟元件掃描和AOP自動代理
<!-- 元件掃描 --> <context:component-scan base-package="com.itheima.anno"/> <!-- aop自動代理 --> <aop:aspectj-autoproxy/>
6.測試
3.2註解配置AOP
通知的配置語法:@通知註解(“切點表示式”)
@Before("execution(public * com.itheima.anno.*.*(..))")
切點表示式的抽取:
先寫一個空方法,在空方法上寫註解,註解裡寫表示式。
@Pointcut("execution(public * com.itheima.anno.*.*(..))") public void pointcut(){}
在原來的切點表示式的位置上寫
@Around("pointcut()")