Java面試知識點(八十三)仿照 Spring 實現簡單的 AOP

原文鏈接:https://segmentfault.com/a/1190000010745705

一、AOP相關概念

代理模式是 AOP 實現的基礎。在介紹 AOP 的實現步驟之前,先引入 Spring AOP 中的一些概念,接下來我們會用到這些概念。

1.通知(Advice)

通知定義了要織入目標對象的邏輯,以及執行時機

Spring 中對應了 5 種不同類型的通知:

  • 前置通知(Before):在目標方法執行前,執行通知
  • 後置通知(After):在目標方法執行後,執行通知,此時不關係目標方法返回的結果是什麼
  • 返回通知(After-returning):在目標方法執行後,執行通知
  • 異常通知(After-throwing):在目標方法拋出異常後執行通知
  • 環繞通知(Around): 目標方法被通知包裹,通知在目標方法執行前和執行後都被會調用

2.切點(Pointcut)

如果說通知定義了在何時執行通知,那麼切點就定義了在何處執行通知。所以切點的作用就是通過匹配規則查找合適的連接點(Joinpoint),AOP 會在這些連接點上織入通知。

3.切面(Aspect)

切面包含了通知和切點,通知和切點共同定義了切面是什麼,在何時,何處執行切面邏輯。

二、AOP實現

簡單 AOP 實現的步驟。這裏 AOP 是基於 JDK 動態代理實現的,只需 3 步即可完成:

  • 定義一個包含切面邏輯的對象,這裏假設叫 logMethodInvocation
  • 定義一個 Advice 對象(實現了 InvocationHandler 接口),並將上面的logMethodInvocation 和 目標對象傳入
  • 將上面的 Adivce 對象和目標對象傳給 JDK 動態代理方法,爲目標對象生成代理

上面步驟比較簡單,不過在實現過程中,還是有一些難度的,這裏要引入一些輔助接口才能實現。接下來就來介紹一下簡單 AOP 的代碼結構:

MethodInvocation 接口  // 實現類包含了切面邏輯
Advice 接口        // 繼承了 InvocationHandler 接口
BeforeAdvice 類    // 實現了 Advice 接口,是一個前置通知
Main      // 測試環境
HelloService 接口   // 目標對象接口
HelloServiceImpl   // 目標對象

【主業務代碼】

package learn.spring.aop;

// 主業務接口
public interface HelloService {
    void sayHelloWorld();
}
------------------------------------------------------
package learn.spring.aop;

// 主業務實現類
public class HelloServiceImpl implements HelloService {
    @Override
    public void sayHelloWorld() {
        System.out.println("hello world !");
    }
}

【切面邏輯接口】

package learn.spring.aop;

/**
 * 切面邏輯的抽象接口,
 * 即在主業務之外的附加業務的抽象接口
 * */
public interface MethodInvocation {
    void invoke();
}
	

【整合主業務和附加業務,實現代理】

package learn.spring.aop;


import java.lang.reflect.InvocationHandler;

/**
 * 通知抽象接口,繼承代理接口InvocationHandler
 * 對於需要在執行主業務之前加通知的類,都需要實現該接口
 * */
public interface Advice extends InvocationHandler {
}
--------------------------------------------------

package learn.spring.aop;

import java.lang.reflect.Method;

/**
 * 通知邏輯具體實現類,
 * 需要在執行之前加通知的對象,作爲構造器參數傳入
 * */
public class BeforeAdvice implements Advice {

    // 主業務對象
    private Object bean;
    // 切面邏輯的對象,即在主業務之外的附加業務
    private MethodInvocation methodInvocation;

    public BeforeAdvice(Object bean, MethodInvocation methodInvocation) {
        this.bean = bean;
        this.methodInvocation = methodInvocation;
    }

    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
        // 在主業務執行先執行 MethodInvocation中的方法
        methodInvocation.invoke();
        // 執行主業務方法
        return method.invoke(bean,objects);
    }
}

【測試環境】

package learn.spring.aop;

import java.lang.reflect.Proxy;

// 測試環境
public class Main {
    public static void main(String[] args) {
        // 切面邏輯具體實現,這裏使用的是java Lambda語法
        MethodInvocation logTask =
                () -> System.out.println("log task start");

        // 主業務實現
        HelloServiceImpl helloService = new HelloServiceImpl();

        // 創建切面,確定通知和切點
        Advice beforeAdvice = new BeforeAdvice(helloService,logTask);

        // 爲目標對象生成代理
        HelloService proxy = (HelloService) Proxy.newProxyInstance(
                helloService.getClass().getClassLoader(),
                helloService.getClass().getInterfaces(),
                beforeAdvice);

        // 執行方法
        proxy.sayHelloWorld();

    }
}

【運行結果】

log task start
hello world !
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章