手動實現一個迷你版的 AOP(實戰增強版)

在正式進行 aop 模塊的介紹之前,我們需要先弄懂一些基本的術語概念。

在軟件業,AOP 爲 Aspect Oriented Programming 的縮寫,意爲:面向切面編程,通過預編譯方式和運行期間動態代理實現程序功能的統一維護的一種技術。AOP 是 OOP 的延續,是軟件開發中的一個熱點,也是 Spring 框架中的一個重要內容,是函數式編程的一種衍生範型。

利用 AOP 可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。

AOP 可以用於解決什麼問題?

代碼複用,公共函數抽取,簡化開發,業務之間的解耦;最典型的例子就是日誌功能,因爲日誌功能往往橫跨系統中的每個業務模塊,使用 AOP 可以很好的將日誌功能抽離出來。

目前已有的幾款 AOP 框架技術:

AOP 基本概念

在介紹 AOP 技術之前,我們先來理清幾個基本概念點:

Aspect(切面)

可以理解爲是將業務代碼抽出來的一個類,例如:

@Aspect
public class LogAspect {
  /** ... 這裏面是相關方法的部分 省略大部分內容 ... **/
}

JoinPoint(連接點)

攔截點其實可以理解爲下邊這個參數:

Spring 框架目前只支持方法級別的攔截,其實 aop 的連接點還可以有多種方式,例如說參數,構造函數等等。

PointCut(切入點)

可以理解爲對各個連接點進行攔截對一個定義。

Advice(通知)

指攔截到連接點之後需要執行的代碼。

分爲了前置,後置,異常,環繞,最終

具體表現如下:

目標對象

代理的目標對象

織入(weave)

將切面應用到目標對象,並且導致代理對象創建的過程。weave 是一個操作過程。

引入(introduction)

在不修改代碼的前提下,引入可以在運行的時候動態天津一些方法或者字段。

Cglib 如何實現接口調用的代理

首先我們定義一個基本的業務代碼對象:

package org.idea.spring.aop.cglib;
/**
 * @Author linhao
 * @Date created in 3:56 下午 2021/5/6
 */
public class MyBis {
    void doBus(){
        System.out.println("this is do bis");
    }
}

接着是目標對象的攔截:

package org.idea.spring.aop.cglib;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
 * 整體的一個調用流程其實就是:
 * @Author linhao
 * @Date created in 3:57 下午 2021/5/6
 */
public class TargetInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("==== intercept before ====");
        //從代理實例的方法調用返回的值。
        Object result = methodProxy.invokeSuper(o,objects);
        System.out.println("==== intercept after ====");
        return result;
    }
}

最後是測試代碼的執行。

package org.idea.spring.aop.cglib;
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
/**
 * @Author linhao
 * @Date created in 3:59 下午 2021/5/6
 */
public class TestCglib {
    public static void main(String[] args) throws InterruptedException {
        //將cglib生成的字節碼文件存放到這個目錄下邊,查看下會有什麼東西
    System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"/Users/idea/IdeaProjects/framework-project/spring-framework/spring-core/spring-aop/cglib-class");
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(MyBis.class);
        enhancer.setCallback(new TargetInterceptor());
        MyBis myBis = (MyBis) enhancer.create();
        myBis.doBus();
    }
}

執行結果:上邊代碼中的 TargetInterceptor 中的 intercept 方法會在目標函數被調用之後自動進行回調操作,從而實現代理調用的效果。

cglib 和 jdk 代理

cglib 的代理模式和 jdk 實現的代理技術本質還是會有較大差異性,jdk 要求被代理對象需要實現 jdk 內部的 InvocationHandler 接口才能進行接口回調操作,但是 cglib 對是否實現接口方面沒有強制要求,而且其性能也比 JDK 自帶的代理要高效許多。

cglib 代理的原理

關於 cglib 的原理我只能簡單地介紹一下,仔細看了下里面的內容點實在是太多了,如果一下子深入挖掘容易掉進坑,所以這裏打算用些大白話的方式來介紹好了。

cglib 實現代理的基本思路

  1. 對被調用對象進行一層包裝,並且對方法建立索引。

  2. 當調用目標方法的時候,通過索引值去尋找並調用函數。

這裏面詳細細節點可以參考這篇博客:

https://www.cnblogs.com/cruze/p/3865180.html

根據這篇博客介紹的思路,我自己也簡單實現了一個 cglib 類似的代理工具。代碼地址見文末

難點:

如何給方法建立索引?如何根據索引調用函數?

這裏貼出我自己的一些思考和輸出。

對調用對方法名稱取出 hashcode,然後通過 switch 關鍵字判斷需要調用對函數名稱:使用起來差不多,不過很多細節方面沒有做得特別完善:使用 cglib 實現代理功能,主要目的就是希望在執行某些函數之前去調用某些方法。爲了實現這種方式,其實藉助反射也是可以達成目的的。但是反射在多次調用的時候性能開銷比較大。cglib 在這塊所做的優化主要是對調用方法做了一次索引的包裝,生產新的字節碼,實現性能上的提升。

相關實現對代碼倉庫地址可以見文末。

Cglib 底層是如何生成字節碼文件的

ASM

對於需要手動操縱字節碼的需求,可以使用 ASM,它可以直接生產 .class 字節碼文件,也可以在類被加載入 JVM 之前動態修改類行爲。ASM 的應用場景有 AOP(Cglib 就是基於 ASM)、熱部署、修改其他 jar 包中的類等。

整體的操作流程圖如下所示:

先通過 ClassReader 讀取編譯好的. class 文件

其通過訪問者模式(Visitor)對字節碼進行修改,常見的 Visitor 類有:對方法進行修改的 MethodVisitor,或者對變量進行修改的 FieldVisitor 等

通過 ClassWriter 重新構建編譯修改後的字節碼文件、或者將修改後的字節碼文件輸出到文件中

如何自己實現一個簡單版本的 AOP

首先需要定義相關的註解:

package org.idea.spring.aop.version1.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * @Author linhao
 * @Date created in 3:49 下午 2021/5/6
 */
@Retention(value = RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Pointcut {
    String value() default "";
}
package org.idea.spring.aop.version1.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * @Author linhao
 * @Date created in 3:42 下午 2021/5/6
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Before {
    String value();
}
package org.idea.spring.aop.version1.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * @Author linhao
 * @Date created in 3:43 下午 2021/5/6
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface After {
    String value();
}
package org.idea.spring.aop.version1.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * @Author linhao
 * @Date created in 3:41 下午 2021/5/6
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Aspect {
    String value() default "";
}

接下來爲自己定義的這些註解添磚加瓦,組合使用到一個 Aspect 的切面當中去:

package org.idea.spring.aop.version1.aspect;
import org.idea.spring.aop.version1.annotation.After;
import org.idea.spring.aop.version1.annotation.Aspect;
import org.idea.spring.aop.version1.annotation.Before;
import org.idea.spring.aop.version1.annotation.Pointcut;
import java.lang.reflect.Method;
/**
 * 切面
 *
 * @Author linhao
 * @Date created in 3:43 下午 2021/5/6
 */
@Aspect
public class MyAspect {
    @Pointcut("org.idea.spring.aop.version1.test.*.*(..)")
    public void pointCut(){
    }
    @Before("pointCut()")
    public void doBefore(Method method, Object object){
        System.out.println("doBefore");
    }
    @After("pointCut()")
    public void doAfter(Method method, Object object){
        System.out.println("doAfter");
    }
}

同時補充一個測試使用的方法

package org.idea.spring.aop.version1.test;
/**
 * @Author linhao
 * @Date created in 3:44 下午 2021/5/6
 */
public class TestMethod {
    public void doTest(){
        System.out.println("do test");
    }
}

最後是一個核型的 AspectLoader 加載器代碼

package org.idea.spring.aop.version1;
import com.google.common.collect.ImmutableSet;
import com.google.common.reflect.ClassPath;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.idea.spring.aop.version1.annotation.After;
import org.idea.spring.aop.version1.annotation.Aspect;
import org.idea.spring.aop.version1.annotation.Before;
import org.idea.spring.aop.version1.annotation.Pointcut;
import org.idea.spring.aop.version1.test.TestMethod;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * @Author linhao
 * @Date created in 3:51 下午 2021/5/6
 */
public class AspectLoader {
    /**
     * 配置掃描aop的aspect基礎包路徑
     */
    public static final String PACKAGE_NAME = "org.idea.spring.aop";
    /**
     * 模擬ioc容器
     */
    public Map<String, Object> beanContainer = new HashMap<>();
    public AspectLoader() {
        this.beanContainer.put("TestMethod", new TestMethod());
    }
    public static void main(String[] args) {
        AspectLoader aspectLoader = new AspectLoader();
        aspectLoader.init();
        TestMethod testMethod = (TestMethod) aspectLoader.beanContainer.get("TestMethod");
        testMethod.doTest();
    }
    /**
     * 初始化aop的配置相關
     */
    private void init() {
        try {
            //獲取切面點aspect
            List<Class> targetsWithAspectJAnnotationList = this.getAspectClass();
            for (Class targetsWithAspectJAnnotation : targetsWithAspectJAnnotationList) {
                Method beforeMethod = this.getBeforeMethod(targetsWithAspectJAnnotation);
                Pointcut pointcut = (Pointcut) this.getMethodAnnotation(targetsWithAspectJAnnotation, Pointcut.class);
                Method afterMethod = this.getAfterMethod(targetsWithAspectJAnnotation);

                List<Class> classList = this.getClassFromPackage(AspectLoader.class, pointcut.value().substring(0, pointcut.value().indexOf("*") - 1));
                for (Class sourceClass : classList) {
                    Object aspectObject = targetsWithAspectJAnnotation.newInstance();
                    Enhancer enhancer = new Enhancer();
                    enhancer.setSuperclass(sourceClass);
                    enhancer.setCallback(new MethodInterceptor() {
                        @Override
                        public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                            beforeMethod.invoke(aspectObject, method, obj);
                            methodProxy.invokeSuper(obj, objects);
                            afterMethod.invoke(aspectObject,method,obj);
                            return obj;
                        }
                    });
                    Object proxyObj = enhancer.create();
                    this.beanContainer.put(sourceClass.getSimpleName(), proxyObj);
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }

    private List<Class> getAspectClass() throws ClassNotFoundException, IOException {
        final ClassPath classPath = ClassPath.from(AspectLoader.class.getClassLoader());
        List<Class> aspectClass = new ArrayList<>();
        ImmutableSet<ClassPath.ClassInfo> clazz = classPath.getAllClasses();
        List<ClassPath.ClassInfo> list = clazz.asList();
        for (ClassPath.ClassInfo classInfo : list) {
            if (classInfo.getName() != null && classInfo.getPackageName().contains(PACKAGE_NAME)) {
                Class clazzTemp = Class.forName(classInfo.getName());
                if (clazzTemp.isAnnotationPresent(Aspect.class)) {
                    aspectClass.add(clazzTemp);
                }
            }
        }
        return aspectClass;
    }

    /**
     * 獲取指定包名下邊的所有類
     *
     * @param source
     * @param packageName
     * @return
     * @throws Exception
     */
    private List<Class> getClassFromPackage(Class source, String packageName) {
        List<Class> classList = new ArrayList<>();
        final ClassPath classPath;
        try {
            classPath = ClassPath.from(source.getClassLoader());
            ImmutableSet<ClassPath.ClassInfo> clazz = classPath.getAllClasses();
            List<ClassPath.ClassInfo> list = clazz.asList();
            for (ClassPath.ClassInfo classInfo : list) {
                if (classInfo.getName() != null && classInfo.getPackageName().contains(packageName)) {
                    classList.add(Class.forName(classInfo.getName()));
                }
            }
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return classList;
    }

    private Annotation getMethodAnnotation(Class source, Class annotationClass) {
        Method[] methods = source.getDeclaredMethods();
        for (Method method : methods) {
            if (method.isAnnotationPresent(annotationClass)) {
                Annotation[] beforeArr = method.getAnnotationsByType(annotationClass);
                if (beforeArr.length > 0) {
                    return beforeArr[0];
                }
            }
        }
        return null;
    }
    private Method getBeforeMethod(Class source) {
        Method[] methods = source.getDeclaredMethods();
        for (Method method : methods) {
            if (method.isAnnotationPresent(Before.class)) {
                return method;
            }
        }
        return null;
    }
    private Method getAfterMethod(Class source) {
        Method[] methods = source.getDeclaredMethods();
        for (Method method : methods) {
            if (method.isAnnotationPresent(After.class)) {
                return method;
            }
        }
        return null;
    }
}

本文相關代碼地址:

https://gitee.com/IdeaHome_admin/spring-framework-learn/tree/master/spring-aop

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/5XwH4X4xUjA0n_ymGuewrQ