1. 程式人生 > 實用技巧 >Spring 的 AOP 簡介

Spring 的 AOP 簡介

1.Spring 的 AOP 簡介

1.1 什麼是 AOP

AOP 為 Aspect Oriented Programming 的縮寫,意思為面向切面程式設計,是通過預編譯方式和執行期動態代理實現程式功能的統一維護的一種技術。

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

1.2 AOP 的作用及其優勢

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

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

1.3 AOP 的底層實現

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

1.4 AOP 的動態代理技術

常用的動態代理技術

JDK 代理 : 基於介面的動態代理技術

cglib 代理:基於父類的動態代理技術

1.5 JDK 的動態代理

①目標類介面

public interface TargetInterface {
   public void method();
}

②目標類

public class Target implements TargetInterface {
    @Override
    public void method() {
        System.out.println("Target running....");
    }
}

③動態代理程式碼

Target target = new Target(); //建立目標物件
//建立代理物件
TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(target.getClass()
.getClassLoader(),target.getClass().getInterfaces(),new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) 
            throws Throwable {
                System.out.println("前置增強程式碼...");
                Object invoke = method.invoke(target, args);
                System.out.println("後置增強程式碼...");
                return invoke;
            }
        }
);

④ 呼叫代理物件的方法測試

// 測試,當呼叫介面的任何方法時,代理物件的程式碼都無序修改
proxy.method();

1.6 cglib 的動態代理

①目標類

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

②動態代理程式碼

Target target = new Target(); //建立目標物件
Enhancer enhancer = new Enhancer();   //建立增強器
enhancer.setSuperclass(Target.class); //設定父類
enhancer.setCallback(new MethodInterceptor() { //設定回撥
    @Override
    public Object intercept(Object o, Method method, Object[] objects, 
    MethodProxy methodProxy) throws Throwable {
        System.out.println("前置程式碼增強....");
        Object invoke = method.invoke(target, objects);
        System.out.println("後置程式碼增強....");
        return invoke;
    }
});
Target proxy = (Target) enhancer.create(); //建立代理物件

③呼叫代理物件的方法測試

//測試,當呼叫介面的任何方法時,代理物件的程式碼都無序修改
proxy.method();

1.7 AOP 相關概念

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

在正式講解 AOP 的操作之前,我們必須理解 AOP 的相關術語,常用的術語如下:

  • Target(目標物件):代理的目標物件

  • Proxy (代理):一個類被 AOP 織入增強後,就產生一個結果代理類

  • Joinpoint(連線點):所謂連線點是指那些被攔截到的點。在spring中,這些點指的是方法,因為spring只支援方法型別的連線點

  • Pointcut(切入點):所謂切入點是指我們要對哪些 Joinpoint 進行攔截的定義

  • Advice(通知/ 增強):所謂通知是指攔截到 Joinpoint 之後所要做的事情就是通知

  • Aspect(切面):是切入點和通知(引介)的結合

  • Weaving(織入):是指把增強應用到目標物件來建立新的代理物件的過程。spring採用動態代理織入,而AspectJ採用編譯期織入和類裝載期織入

1.8 AOP 開發明確的事項

1.8 AOP 開發明確的事項

1.8 AOP 開發明確的事項

1)需要編寫的內容
  • 編寫核心業務程式碼(目標類的目標方法)

  • 編寫切面類,切面類中有通知(增強功能方法)

  • 在配置檔案中,配置織入關係,即將哪些通知與哪些連線點進行結合

2)AOP 技術實現的內容

Spring 框架監控切入點方法的執行。一旦監控到切入點方法被執行,使用代理機制,動態建立目標物件的代理物件,根據通知類別,在代理物件的對應位置,將通知對應的功能織入,完成完整的程式碼邏輯執行。

3)AOP 底層使用哪種代理方式

在 spring 中,框架會根據目標類是否實現了介面來決定採用哪種動態代理的方式。

1.9 知識要點

  • aop:面向切面程式設計

  • aop底層實現:基於JDK的動態代理 和 基於Cglib的動態代理

  • aop的重點概念:

    Pointcut(切入點):被增強的方法
    
    Advice(通知/ 增強):封裝增強業務邏輯的方法
    
    Aspect(切面):切點+通知
    
    Weaving(織入):將切點與通知結合的過程
    
  • 開發明確事項:

    誰是切點(切點表示式配置)
    
    誰是通知(切面類中的增強方法)
    
    將切點和通知進行織入配置
    

2. 基於 XML 的 AOP 開發

2.1 快速入門

①匯入 AOP 相關座標

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

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

④將目標類和切面類的物件建立權交給 spring

⑤在 applicationContext.xml 中配置織入關係

⑥測試程式碼

①匯入 AOP 相關座標

<!--匯入spring的context座標,context依賴aop-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.0.5.RELEASE</version>
</dependency>
<!-- aspectj的織入 -->
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.8.13</version>
</dependency>

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

public interface TargetInterface {
    public void method();
}

public class Target implements TargetInterface {
    @Override
    public void method() {
        System.out.println("Target running....");
    }
}

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

public class MyAspect {
    //前置增強方法
    public void before(){
        System.out.println("前置程式碼增強.....");
    }
}

④將目標類和切面類的物件建立權交給 spring

<!--配置目標類-->
<bean id="target" class="com.itheima.aop.Target"></bean>
<!--配置切面類-->
<bean id="myAspect" class="com.itheima.aop.MyAspect"></bean>

⑤在 applicationContext.xml 中配置織入關係

匯入aop名稱空間

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

⑤在 applicationContext.xml 中配置織入關係

配置切點表示式和前置增強的織入關係

<aop:config>
    <!--引用myAspect的Bean為切面物件-->
    <aop:aspect ref="myAspect">
        <!--配置Target的method方法執行時要進行myAspect的before方法前置增強-->
        <aop:before method="before" pointcut="execution(public void com.itheima.aop.Target.method())"></aop:before>
    </aop:aspect>
</aop:config>

⑥測試程式碼

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
    @Autowired
    private TargetInterface target;
    @Test
    public void test1(){
        target.method();
    }
}

⑦測試結果

2.2 XML 配置 AOP 詳解

1) 切點表示式的寫法

表示式語法:

execution([修飾符] 返回值型別 包名.類名.方法名(引數))
  • 訪問修飾符可以省略

  • 返回值型別、包名、類名、方法名可以使用星號* 代表任意

  • 包名與類名之間一個點 . 代表當前包下的類,兩個點 .. 表示當前包及其子包下的類

  • 引數列表可以使用兩個點 .. 表示任意個數,任意型別的引數列表

例如:

execution(public void com.itheima.aop.Target.method())	
execution(void com.itheima.aop.Target.*(..))
execution(* com.itheima.aop.*.*(..))
execution(* com.itheima.aop..*.*(..))
execution(* *..*.*(..))
2) 通知的型別

通知的配置語法:

<aop:通知型別 method=“切面類中方法名” pointcut=“切點表示式"></aop:通知型別>

3) 切點表示式的抽取

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

<aop:config>
    <!--引用myAspect的Bean為切面物件-->
    <aop:aspect ref="myAspect">
        <aop:pointcut id="myPointcut" expression="execution(* com.itheima.aop.*.*(..))"/>
        <aop:before method="before" pointcut-ref="myPointcut"></aop:before>
    </aop:aspect>
</aop:config>

2.3 知識要點

  • aop織入的配置
<aop:config>
    <aop:aspect ref=“切面類”>
        <aop:before method=“通知方法名稱” pointcut=“切點表示式"></aop:before>
    </aop:aspect>
</aop:config>
  • 通知的型別:前置通知、後置通知、環繞通知、異常丟擲通知、最終通知
  • 切點表示式的寫法:
execution([修飾符] 返回值型別 包名.類名.方法名(引數))

3.基於註解的 AOP 開發

3.1 快速入門

基於註解的aop開發步驟:

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

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

③將目標類和切面類的物件建立權交給 spring

④在切面類中使用註解配置織入關係

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

⑥測試

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

public interface TargetInterface {
    public void method();
}

public class Target implements TargetInterface {
    @Override
    public void method() {
        System.out.println("Target running....");
    }
}

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

public class MyAspect {
    //前置增強方法
    public void before(){
        System.out.println("前置程式碼增強.....");
    }
}

③將目標類和切面類的物件建立權交給 spring

@Component("target")
public class Target implements TargetInterface {
    @Override
    public void method() {
        System.out.println("Target running....");
    }
}
@Component("myAspect")
public class MyAspect {
    public void before(){
        System.out.println("前置程式碼增強.....");
    }
}

④在切面類中使用註解配置織入關係

@Component("myAspect")
@Aspect
public class MyAspect {
    @Before("execution(* com.itheima.aop.*.*(..))")
    public void before(){
        System.out.println("前置程式碼增強.....");
    }
}

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

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

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

⑥測試程式碼

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
    @Autowired
    private TargetInterface target;
    @Test
    public void test1(){
        target.method();
    }
}

⑦測試結果

3.2 註解配置 AOP 詳解

1) 註解通知的型別

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

2) 切點表示式的抽取

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

@@Component("myAspect")
@Aspect
public class MyAspect {
    @Before("MyAspect.myPoint()")
    public void before(){
        System.out.println("前置程式碼增強.....");
    }
    @Pointcut("execution(* com.itheima.aop.*.*(..))")
    public void myPoint(){}
}

3.3 知識要點

  • 註解aop開發步驟

①使用@Aspect標註切面類

②使用@通知註解標註通知方法

③在配置檔案中配置aop自動代理aop:aspectj-autoproxy/

  • 通知註解型別