Spring---Spring的AOP(三)
1、Spring的AOP簡介
1.1 什麼是AOP
AOP為Aspect Oriented Programming的縮寫,是一種面向切面的程式設計,是基於動態代理的,對原有程式碼毫無入侵性,把和主業務無關的事情放到程式碼外面去做。所以當你下次發現某一行程式碼經常在你的Controller例出現時,就要考慮用AOP來精簡程式碼了。
簡而言之:在執行時,動態地將程式碼切入到類的指定方法、指定位置上的程式設計思想,就是面向切面的程式設計。
AOP是OOP的延續,是軟體開發中的熱點,也是Spring框架中的一個重要內容,是函數語言程式設計的一種衍生泛型。
1.2 AOP 的作用及優勢
AOP的作用:
利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高開發的效率。
AOP的優勢:
減少重複程式碼,提高開發效率,維護方便
1.3 AOP 的底層實現
AOP的底層原理是通過Spring 提供的動態代理技術實現的。在執行期間,Spring通過動態代理技術動態的生成代理物件,代理物件方法執行時增進功能的介入,再去呼叫目標物件的方法,從而增強功能。
1.4 AOP 的動態代理技術
Spring的AOP的底層實現用到了兩種代理機制:
JDK的動態代理:基於介面的動態代理技術
cglib的動態代理:基於父類的動態代理技術
什麼是動態代理:
代理是一種設計模式,提供了目標對目標物件的訪問方式,即是通過代理物件訪問目標物件,可以在目標物件的基礎上,增加額外的功能,拓展目標物件的功能。
1.4.1 JDK 的動態代理
1、目標類介面及目標類
public interface TargetInterface {
public void save();
}
public class Target implements TargetInterface{
@Override
public void save() {
System.out.println("Save Running.....");
}
}
2、物件增強程式碼
public class Enhance { public void before(){ System.out.println("前置增強...."); } public void after(){ System.out.println("後置增強...."); } }
3、動態代理程式碼
當呼叫介面的任何方法時,代理物件的程式碼都不需要修改
1 public class proxyTest {
2 public static void main(String[] args) {
3
4 final Target target = new Target(); //建立目標物件
5 final Enhance enhance = new Enhance(); // 獲得增強物件
6
7 //返回值就是動態生成的代理物件
8 TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(
9 //目標物件載入器
10 target.getClass().getClassLoader(),
11 target.getClass().getInterfaces(),
12 new InvocationHandler() {
13 //呼叫代理物件的任何方法,實際上執行的都是invoke方法
14 @Override
15 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
16 enhance.before(); //前置增強
17 //執行目標方法
18 Object invoke = method.invoke(target, args);
19 enhance.after(); // 後置增強
20 return invoke;
21 }
22 }
23
24 );
25
26 //呼叫代理物件方法
27 proxy.save();
28
29 }
30 }
1.4.2 cglib 的動態代理
1、目標類
public class Target{
public void save() {
System.out.println("Save Running.....");
}
}
2、物件增強程式碼
public class Enhance {
public void before(){
System.out.println("前置增強....");
}
public void after(){
System.out.println("後置增強....");
}
}
3、動態代理程式碼
1 public class proxyTest {
2 public static void main(String[] args) {
3
4 final Target target = new Target(); //建立目標物件
5 final Enhance enhance = new Enhance(); // 獲得增強物件
6
7 //返回值就是動態生成的代理物件 基於cglib
8 /*
9 * 1. 建立增強器
10 * 2。設定目標類(父類)
11 * 3. 設定回撥
12 * 4. 建立代理物件
13 * */
14
15 Enhancer enhancer = new Enhancer();
16 enhancer.setSuperclass(Target.class);
17 //設定回撥
18 enhancer.setCallback(new MethodInterceptor() {
19 @Override
20 public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
21 //執行前置
22 enhance.before();
23 Object invoke = method.invoke(target, args); //執行目標物件
24 //執行後置
25 enhance.after();
26 return invoke;
27 }
28 });
29 // 建立代理物件
30 Target proxy = (Target) enhancer.create();
31
32 proxy.save();
33 }
34 }
1.5 AOP相關術語
Spring的AOP實現底層就是對上面的動態代理的程式碼進行了封裝,封裝之後只需要對需要關注的部分進行程式碼編寫,並通過配置的方式完成指定的目標方法增強。
我們必須理解的常用的AOP相關術語:
Target(目標物件) | 代理的目標物件 |
---|---|
Proxy(代理) | 一個類被AOP織入增強後,就產生一個結果代理類 |
Joinpoint(連線點) | 所謂連線點就是指那些被攔截的點。在Spring中,這些點指的是方法 因為Spring只支援方法型別的連線點 |
Pointcut(切入點) | 所謂切入點是指我們對那些Joinpoint進行攔截的定義 |
Advice(通知/增強) | 通知是指攔截到Joinpoint 之後所要做的事情 |
Aspect(切面) | 是切入點和通知的結合 |
Weaving(織入) | 是指把增強應用到目標物件來建立的代理物件的過程。 Spring採用動態代理織入,而Aspect採用編譯期織入和類裝載期織入 |
1.6 AOP開發明確的事項
1、需要編寫的內容
-
編寫核心業務程式碼(目標類的目標方法)
-
編寫切面類,切面類中有通知(增強功能方法)
-
在配置檔案中,配置織入關係,即將那些通知與連線點進行結合
2、AOP技術實現的內容
Spring框架監控切入點方法的執行。一旦監控到切入點方法被執行,使用代理機制,動態建立目標物件的代理物件,根據通知類別,在代理物件的對應位置,將通知對應的功能織入,完成完整的程式碼邏輯。
3、AOP底層使用哪種代理方式
在Spring中,框架會根據目標類是否實現介面來決定採用哪種動態代理方式。
1.7 知識要點總結
-
AOP:面向切面程式設計
-
AOP底層實現:基於JDK的動態代理和基於Cglib的動態代理
-
AOP的重要概念:
Pointcut(切入點): 被增強方法
Advice(通知/增強):封裝增強業務邏輯的方法
Aspect(切面):切點+通知
Weaving(織入):將切點與通知結合的過程
-
開發明確事項:
誰是切點(切點表示式配置)
誰是通知(切面類中的增強方法)
將切點和通知進行織入配置
2. 基於xml的AOP開發
2.1 快速入門
1、匯入AOP相關座標
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.16</version>
</dependency>
<!-- aspectj的織入-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
2、建立目標介面和目標類(內部有切點)
public interface TargetInterface {
public void save();
}
public class Target implements TargetInterface{
@Override
public void save() {
System.out.println("Save Running.....");
}
}
3、建立切面類(內部有增強方法)
public class MyAspect {
public void before(){
System.out.println("前置增強....");
}
}
4、將目標類和切面類的物件建立權交給Spring
<!-- 目標物件-->
<bean id="target" class="com.ntect.aop.Target"></bean>
<!-- 切面-->
<bean id="myAspect" class="com.ntect.aop.MyAspect"></bean>
5、在applicationContext.xml中配置織入關係
先匯入aop名稱空間
配置織入通知
<!-- 配置織入:告訴Spring哪些方法(切點)需要進行增強(前置、後置...)-->
<aop:config>
<!-- 宣告切面-->
<aop:aspect ref="myAspect">
<!-- 切面:切點+通知 -->
<aop:before method="before" pointcut="execution(public void com.ntect.aop.Target.save())"/>
</aop:aspect>
</aop:config>
6、測試程式碼
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
@Autowired
private TargetInterface target;
@Test
public void testAop(){
target.save();
}
}
7、測試結果
2.2 XML配置AOP詳解
1、切點表示式的寫法
表示式語法:
execution[修飾符]返回值型別 包名.類名.方法名(引數)
/*
*1. 訪問修飾符可以省略
*2. 返回值型別、包名、類名、方法名可以使用 * 代表任意
*3. 包名與類名之間一個點. 代表當前包下的類,兩個點 .. 表示當前包及其子包下的類
*4. 引數列表可以使用兩個點 .. 表示任意個數,任意型別的引數列表
*/
例如:
execution(public void com.ntect.aop.Target.method())
execution(public void com.ntect.aop.Target.*(..))
execution(public void com.ntect.aop.*.*(..)) //常用形式
execution(public void com.ntect.aop..*.*(..))
execution(* *..*.*(..))
2、通知的型別
通知的配置語法:
<aop:通知型別 method="切面類中方法名" pointcut="切點表示式"></aop:通知型別>
名稱 | 標籤 | 說明 |
---|---|---|
前置通知 | <aop:before> | 用於配置前置通知,指定增強方法在切入點方法之前執行 |
後置通知 | <aop:after-returnning> | 用於配置後置通知,指定增強的方法在切入點方法之後執行 |
環繞通知 | <aop:around> | 用於配置環繞通知,指定增強的方法在切入點 方法之之後都執行 |
異常丟擲通知 | <aop:throwing> | 用於配置異常丟擲通知,指定增強的方法在出現異常時執行 |
最終通知 | <aop:after> | 用於配置最終通知,無論增強方式執行是否異常都會執行 |
1 public class MyAspect {
2
3 public void before(){
4 System.out.println("前置增強....");
5 }
6
7 public void afterReturning(){
8 System.out.println("後置增強.....");
9 }
10
11 //Proceeding JoinPoint : 正在執行的連線點====切點
12 public Object around(ProceedingJoinPoint pjp) throws Throwable {
13 System.out.println("環繞前增強....");
14 Object proceeding = pjp.proceed(); // 切點方法
15 System.out.println("環繞後增強....");
16 return proceeding;
17 }
18
19 public void afterThrowing(){
20 System.out.println("異常丟擲增強....");
21 }
22
23 public void after(){
24 System.out.println("最終增強....");
25 }
26 }
<aop:config>
<!-- 宣告切面-->
<aop:aspect ref="myAspect">
<!-- 切面:切點+通知 -->
<aop:before method="before" pointcut="execution(public void com.ntect.aop.Target.save())"/>
<aop:after-returning method="afterReturning" pointcut="execution(* com.ntect.aop.*.*(..))"/>
<aop:around method="around" pointcut="execution(* com.ntect.aop.*.*(..))"/>
<aop:after-throwing method="afterThrowing" pointcut="execution(* com.ntect.aop.*.*(..))"/>
<aop:after method="after" pointcut="execution(* com.ntect.aop.*.*(..))"/>
</aop:aspect>
</aop:config>
3、切點表示式的抽取
當多個增強的切點表示式相同時,可以將切點表示式進行抽取,在增強中使用pointcut-ref屬性代替pointcut屬性來引用抽取後的切點表示式。
<!-- 抽取切點表示式-->
<aop:config>
<!-- 引用MyAspect的Bean為切面物件-->
<aop:aspect ref="myAspect">
<aop:pointcut id="myPointcut" expression="execution(* com.ntect.aop.*.*(..))"/>
<aop:before method="before" pointcut-ref="myPointcut"/>
<aop:around method="around" pointcut-ref="myPointcut"/>
<aop:after method="after" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
2.3 知識要點
-
aop織入的配置
<aop:config>
<aop:aspect ref="切面類">
<aop:after method="通知方法名稱" pointcut="切點表示式"/>
</aop:aspect>
</aop:config>
-
通知的型別:前置通知、後置通知、環繞通知、異常丟擲通知、最終通知
-
切點表示式的寫法:
execution([修飾符] 返回值型別 包名.類名.方法名(引數))
3. 基於註解的AOP開發
3.1 快速入門
開發步驟:
1、建立目標介面和目標類(內部有切點)
2、建立切面類(內部有增強方法)
3、將目標類和切面類的物件建立權交給Spring
@Component("target")
public class Target implements TargetInterface {
public void save() {
System.out.println("Save Running.....");
}
}
@Component("myAspect")
public class MyAspect {
public void before(){
System.out.println("前置增強....");
}
4、在切面類中使用註解配置織入關係
@Component("myAspect")
@Aspect //標註當前MyAspeect是一個切面類
public class MyAspect {
//配置前置增強
@Before("execution(* com.ntect.anno.*.*(..))")
public void before(){
System.out.println("前置增強....");
}
5、在配置檔案中開啟元件掃描和AOP的自動代理
<!-- 開啟元件掃描-->
<context:component-scan base-package="com.ntect.anno"/>
<!-- aop自動代理-->
<aop:aspectj-autoproxy/>
6、測試程式碼
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext-anno.xml")
public class annoTest {
@Autowired
private TargetInterface target;
@Test
public void testAop(){
target.save();
}
}
7、測試結果
3.2 註解配置AOP
1、註解通知的型別
通知的配置語法:@通知註解(“切點表示式”)
名稱 | 註解 | 說明 |
前置通知 | @Before | 用於配置前置通知。指定增強的方法在切入點方法之前執行 |
後置通知 | @AfterReturning | 用於配置後置通知。指定增強的方法在切入點方法之後執行 |
環繞通知 | @Around | 用於配置環繞通知。指定的增強方法在切入點方法之前和之後執行 |
異常丟擲通知 | @AfterThrowing | 用於配置異常丟擲通知。指定增強的方法在出現異常時執行 |
最終通知 | @After | 用於配置最終通知。無論增強方式執行是否有異常都會執行 |
1 @Component("myAspect")
2 @Aspect //標註當前MyAspeect是一個切面類
3 public class MyAspect {
4
5 //配置前置增強
6 @Before("execution(* com.ntect.anno.*.*(..))")
7 public void before(){
8 System.out.println("前置增強....");
9 }
10
11 @AfterReturning("execution(* com.ntect.anno.*.*(..))")
12 public void afterReturning(){
13 System.out.println("後置增強.....");
14 }
15
16 @Around("execution(* com.ntect.anno.*.*(..))")
17 //Proceeding JoinPoint : 正在執行的連線點====切點
18 public Object around(ProceedingJoinPoint pjp) throws Throwable {
19 System.out.println("環繞前增強....");
20 Object proceeding = pjp.proceed(); // 切點方法
21 System.out.println("環繞後增強....");
22 return proceeding;
23 }
24
25 @AfterThrowing("execution(* com.ntect.anno.*.*(..))")
26 public void afterThrowing(){
27 System.out.println("異常丟擲增強....");
28 }
29
30 @After("execution(* com.ntect.anno.*.*(..))")
31 public void after(){
32 System.out.println("最終增強....");
33 }
34 }
2、切點表示式的抽取
同 xml配置aop一樣時,我們可以將切點表示式抽取,具體如下:
1 @Component("myAspect")
2 @Aspect //標註當前MyAspeect是一個切面類
3 public class MyAspect {
4
5 //定義切點表示式
6 @Pointcut("execution(* com.ntect.anno.*.*(..))")
7 public void pointCut(){}
8 //配置前置增強
9 @Before("MyAspect.pointCut()")
10 public void before(){
11 System.out.println("前置增強....");
12 }
13
14 @AfterReturning("MyAspect.pointCut()")
15 public void afterReturning(){
16 System.out.println("後置增強.....");
17 }
18
19 @Around("MyAspect.pointCut()")
20 //Proceeding JoinPoint : 正在執行的連線點====切點
21 public Object around(ProceedingJoinPoint pjp) throws Throwable {
22 System.out.println("環繞前增強....");
23 Object proceeding = pjp.proceed(); // 切點方法
24 System.out.println("環繞後增強....");
25 return proceeding;
26 }
27
28 @AfterThrowing("MyAspect.pointCut()")
29 public void afterThrowing(){
30 System.out.println("異常丟擲增強....");
31 }
32
33 @After("MyAspect.pointCut()")
34 public void after(){
35 System.out.println("最終增強....");
36 }
37 }
測試結果:
3、總結
-
註解開發步驟:
1、使用@Aspect標註切面
2、使用@通知註解標註通知方法
3、在配置檔案中配置AOP自動代理<aop:aspectj-autoproxy/>