Spring第9天Aop簡介,Aopxml開發,Aop註解開發

嘻嘻碩發表於2022-04-17

1. Spring的AOP簡介

1.1 什麼是AOP?

AOP 為 Aspect Oriented Programming 的縮寫, 意思為面向切面程式設計,

是通過 預編譯方式 和 執行期動態代理 實現程式功能的統一維護的一種技術。

AOP是OOP(Ojebct Oriented Programming)的延續, 是軟體開發中的一個熱點, 是Spring框架的一個重要內容部分,是函數語言程式設計的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。

OOP的侷限性

在業務程式碼中通常有日誌控制,效能統計,安全控制,事務控制,異常處理等等操作, 儘管使用OOP可以通過封裝或繼承的方式達到程式碼的重用,但任然有同樣的程式碼分散各個方法中,採用OOP處理日誌等增強了開發者工作量,而且提高了升級維護困難

這一條條橫線彷彿切開了 OOP 的樹狀結構,猶如一個大蛋糕被切開多層,每一層都會執行相同的輔助邏輯,所以大家將這些輔助邏輯稱為層面或者切面。

1.2 AOP的優勢

作用: 在程式執行期間, 在不修改原始碼的情況下對方法進行功能加強

優勢: 減少重複程式碼, 提高開發效率,便於維護。

1.3 AOP的底層實現

實際上,AOP 的底層是通過 Spring 提供的的動態代理技術實現的。在執行期間,Spring通過動態代理技術動態 的生成代理物件,代理物件方法執行時進行增強功能的介入,在去呼叫目標物件的方法,從而完成功能的增強

1.4 AOP的代理技術

常用的動態代理技術

  • JDK 代理 :基於介面的動態代理技術
  • cglib 代理 :基於父類的動態代理技術

常用的動態代理技術

  • JDK 代理 : 基於介面的動態代理技術
  • cglib 代理:基於父類的動態代理技術

image-20220325155728208

1.5 JDK的動態代理

  1. 目標類介面
public interface TargetInterface{
    void save();
}

​ 2. 目標類

public class Target implements TargetInterface{
    
    public void save(){
       System.out.println("save running....");
    }
    
}
  1. 增加類
public class Advice{

    public void before(){
        System.out.println("前置增強");
    }
    
    public void afterReturning(){
        System.out.println("後置增強...");
    }
    
}
  1. 動態代理程式碼
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 InvocationTargetException, IllegalAccessException{
            advkce.before(); // 前置增加
           Object invoke = method.invoke(target,args) //執行目標方法
            advice.afterReturning(); //後置增加
            return invoke; // 返回值就是目標物件 -> null也行(儘量別)
            }
        }
    );
        
    // 呼叫代理物件方法
        proxy.save();
    // (呼叫介面的任意方法時,代理物件的程式碼都無需修改)
    }
}

輸出結果 :

 前置增加
 save running.....
 後置增加 

1.6 cglib的動態代理

1)目標類

public class Target {
    public void save() {
        System.out.println("save running.....");
    }
}

2)增強類物件

public class Advice {

    public void before(){
        System.out.println("前置增強....");
    }

    public void afterReturning(){
        System.out.println("後置增強....");
    }

}

3)動態代理程式碼

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.7 AOP的相關概念

Spring 的 AOP 實現底層就是對上面的動態代理的程式碼進行了封裝,封裝後我們只需要對需要關注的部分進行程式碼編 寫,並通過配置的方式完成指定目標的方法增強。
AOP 常用的相關術語如下:

  • Target(目標物件) : 代理的目標物件
  • Proxy (代理) :一個類被AOP織入增強後,就會產生一個結果代理類
  • Joinpoint(連線點): 所謂連線點是指那些被攔截到的點。在spring中,這些點指的是方法,因為spring只支援方法型別的連線點
  • Pointcut(切入點):所謂的切入點是指我們要對那些 JoinPoint 進行攔截的定義
  • Advice(通知/增加):所謂通知是 攔截到 Joinpoint之後所要做的是事情就是通知
  • Aspect(切面):是切入點和通知(引介)的結合
  • Weaving(織入):是指把增強應用到目標物件來建立新的代理物件的過程。spring採用動態代理織入,而AspectJ採用編譯期織入和類裝載期織入
Spring AOP術語:
連線點和切點的區別
  1. 連線點(Join point) : 連線點是應用執行過程中能夠插入切面(Aspect) 的一個點, 這些點可以是呼叫方法時、甚至修改一個欄位時。
  2. 切點(Pointcut): 切點時指通知(Adivce) 所要織入的地方的具體位置

連線點:連線點時一個虛擬的概念,他可以理解為所有滿足切點條件的所有時機。

具體舉個例子: 比如開車經過一條高速公路,這條高速公路有很多個路口(連線點),但是我們不會每個出口都會出去,指揮選擇我們需要的那個出口(切點) 開出去。

簡單可以理解為,每個出口都是連線點但是我們使用的那個出口才是切點,每個應用有多個位置適合織入通知,這些都是連線點,但是隻有我們選擇的那個具體的位置才是切點

小總結: 所有方法都需要連線點,切點是我們對連線點的實現,通過切點確定那些連線點需要被處理。

參考:https://blog.csdn.net/weixin_...

AOP 開發明確開發及總結

需要編寫的內容
  1. 編寫目標類的目標方)
  2. 編寫切面類(內含有增強方法)
  3. 在配置檔案中,配置織入關係,以及那些通知連線點集合

總結

aop的重點概念

​ Pointcut(切入點):被增強的方法

​ Advice(通知/ 增強):封裝增強業務邏輯的方法

​ Aspect(切面):切點+通知

​ Weaving(織入):將切點與通知結合的過程

開發者步驟

​ 誰是切點(切點表示式)

​ 誰是通知(切面類的增強方法)

​ 將切點和通知進行織入配置

基於XML的AOP開發

2.1快速入門

  1. maven AOP 的座標
  2. 目標介面 目標類(內部有切點)
  3. 切面類
  4. 在applicationContext.xml配置織入等
  5. 測試
1.導包
<dependencies>
        <!-- 匯入spirng的context座標,context依賴aop -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <!-- aop小框架 aspectj的織入 -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.6</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!-- 這是一個spring自帶的測試等等我會演示 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
    </dependencies>
2.目標介面目標類(內部有切點)
package com.xxx.aop; // 目標介面
public interface TargetInterface {
    public void save();
}


package com.xxx.aop; // 目標類
public class Target implements TargetInterface {

    public void save() {
//        int i = 1/0;
        System.out.println("save running.....");
    }

}
3.切面類(內部有增強方法)
package com.xxx.aop;

import org.aspectj.lang.ProceedingJoinPoint;

public class MyAspect {

    public void before(){
        System.out.println("前置增強......");
    }

}
4.xml進行配置
  • aop名稱空間及約束空間

xml

xml程式碼

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context" 
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-4.2.xsd 
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-4.2.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-4.2.xsd 
">
    <!-- 目標物件 -->
    <bean id="target" class="com.xxx.aop.Target"></bean>

    <!-- 切面物件 -->
    <bean id="myAspect" class="com.xxx.aop.MyAspect"></bean>

    <!-- 配置織入: 告訴spring框架 那些方法(切點)需要進行那些增強(前置,後置..)-->
    <aop:config>
        <!-- 宣告切面 -->
        <aop:aspect ref="myAspect">
            <!-- 配置Target的save方法執行時要進行myAspect的before方法前置增強 -->
            
            <!--切面: 切點+通知(增加)   pointcut切點 -->
            <aop:before method="before" pointcut="execution(public void com.xxx.aop.Target.())"/>
        </aop:aspect>
    </aop:config>
    
</beans>
5.測試
@RunWith(SpringJUnit4ClassRunner.class) // spring測試類
@ContextConfiguration("classpath:applicationContext.xml") // 指定測試的檔案 
public class AopTest {

    @Autowired // 把要測試的物件 注入進來
    private TargetInterface target;

    @Test
    public void test1() {
        target.save();
    }   
    
}

輸出結果:

​ 前置增強......
​ save running.....

xml配置AOP詳解

1) 切點表示式的寫法

表示式語法

execution([修飾符] 返回值型別 包名.類名.方法名(引數))
  • 修飾符可以省略
  • 返回值型別、包名、類名、方法名可以使用星號* 代表任意
  • 包名與類名之間一個點 . 代表當前包下的類,兩個點 .. 表示當前包及其子包下的類
  • 引數列表可以使用兩個點 .. 表示任意個數,任意型別的引數列表
# 全
execution(public void com.xxx.aop.Target.method()) 
# 任意方法任意引數
execution(void com.xxx.aop.Target.*(..)) 
# 任意返回值com.xxx.aop包下任意類任意方法任意引數
execution(* com.xxx.aop.*.*(..)) 
# 任意返回值com.xxx.aop下以其子包下任意類任意引數
execution(* com.xxx.aop..*.*(..))
# 你故意找茬是吧
execution(* *..*.*(..))

小總結: 
修飾符可以不寫 
第一個 * = 返回值 
第二個 * = 包 
第三個 * = 類
第四個 * = 方法
括號外.. = 自己包括子包下所有類的xxx
括號內的 .. = 任意引數 

2)通知的型別

通知的配置語法:

<aop:通知型別 method="切面類中方法名" pointcut="切點表示式"></aop:通知型別>
名稱標籤說明
前置通知<aop:before>用於配置前置通知。指定增強的方法在切入點方法之前執行
後置通知<aop:after-returning>用於配置後置通知。指定增強的方法在切入點方法之後執行
環繞通知<aop:around>於配置環繞通知。指定增強的方法在切入點方法之前和之後都 執行
異常丟擲通知<aop:throwing>用用於配置異常丟擲通知。指定增強的方法在出現異常時執行
最終通知<aop:after>用於配置最終通知。無論增強方式執行是否有異常都會執行

3)切點表示式的抽取

當多個增強的切點表示式相同時,可以將切點表示式進行抽取,在增強中使用 pointcut-ref 屬性代替 pointcut 屬性來引用抽取後的切點表示式。

<aop:config>
    <!--引用myAspect的Bean為切面物件-->
    <aop:aspect ref="myAspect">
        <!--抽取切點表示式-->
        <aop:pointcut id="myPointcut" expression="execution(* com.itheima.aop.*.*(..))"></aop:pointcut>
        <aop:around method="around" pointcut-ref="myPointcut"/>
        <aop:after method="after" pointcut-ref="myPointcut"/>
    </aop:aspect>
</aop:config>

通知方法存在於切面類中,根據切點表示式,對切點增強。

3. 基於註解的AOP開發

3.1 快速入門

步驟:
1、建立目標介面和目標類(開啟註解掃描)
2、建立切面類 (開啟註解掃描 配置織入關係)
3、xml開啟啟元件掃描和 AOP 的自動代理
4、測試

1、建立目標介面和目標類(內部有切點)

public interface TargetInterface {
    public void save();
}
@Component
public class Target implements TargetInterface {
    public void save() {
        System.out.println("save running.....");
    }
}

2、建立切面類(內部有增強方法)

@Component("myAspect")
@Aspect //標籤當前MyAspect是一個切面類
public class MyAspect {

    // 配置前置增強
    // @Before(value = "execution( * com.xxx.anno.*.*(..))")
    public void before(){
        System.out.println("前置增強......");
    }

  
}    

5、在配置檔案中開啟元件掃描和 AOP 的自動代理

<!--元件掃描-->
<context:component-scan base-package="com.itheima.anno"/>

<!--aop自動代理-->
<aop:aspectj-autoproxy/>

6、測試

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext-anno.xml")
public class AnnoTest {

    @Autowired
    private TargetInterface target;

    @Test
    public void test1() {
        target.save();
    }
}

輸出:

​ 前置增強......

​ save running.....

3.2 註解配置 AOP 詳解

1)註解通知的型別

通知的配置語法:@通知註解("切點表示式")

名稱註解說明
前置通知@Before用於配置前置通知。指定增強的方法在切入點方法之前執行
後置通知@AfterReturning用於配置後置通知。指定增強的方法在切入點方法之後執行
環繞通知@Around用於配置環繞通知。指定增強的方法在切入點方法之前和之後都執行
異常丟擲通知@AfterThrowing用於配置異常丟擲通知。指定增強的方法在出現異常時執行
最終通知@After用於配置最終通知。無論增強方式執行是否有異常都會執行

2)切點表示式的抽取

通xml 配置 aop 一樣,我們可以將切點表示式抽取。抽取方式是在切面內定義方法,在該方法上使用@Pointcut 註解定義切點表示式,然後在在增強註解中進行引用。具體如下:

@Component("myAspect")
@Aspect //標註當前MyAspect是一個切面類
public class MyAspect {

    //Proceeding JoinPoint:  正在執行的連線點===切點
    //@Around("execution(* com.xxx.anno.*.*(..))")
    @Around("pointcut()")
    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("異常丟擲增強..........");
    }

    //@After("execution(* com.xxx.anno.*.*(..))")
    @After("MyAspect.pointcut()")
    public void after() {
        System.out.println("最終增強..........");
    }

    //定義切點表示式
    @Pointcut("execution(* com.xxx.anno.*.*(..))")
    public void pointcut() {
    }
}

相關文章