动态

详情 返回 返回

聊聊如何通過APT+AST來實現AOP功能 - 动态 详情

前言

如果有使用過spring aop功能的小夥伴,應該都會知道spring aop主要是通過動態代理在運行時,對業務進行切面攔截操作。今天我們就來實現一下如何通過APT+AST在編譯期時實現AOP功能。不過在此之前先科普一下APT和AST相關內容

APT(註解處理器)

apt可以查看我之前寫過的文章聊聊如何運用JAVA註解處理器(APT)

AST(抽象語法樹)

什麼是AST

抽象語法樹(Abstract Syntax Tree,AST),是源代碼語法結構的一種抽象表示。它以樹狀的形式表現編程語言的語法結構,樹上的每個節點都表示源代碼中的一種結構。比如包、類型、修飾符、運算符、接口、返回值都可以是一個語法結構。

示例:

package com.example.adams.astdemo;
public class TestClass {
    int x = 0;
    int y = 1;
    public int testMethod(){
        int z = x + y;
        return z;
    }
}

對應的抽象語法樹如下:

java的編譯過程

重點關注步驟一和步驟二生成AST的過程

步驟一:詞法分析,將源代碼的字符流轉變為 Token 列表。

通過詞法分析器分析源文件中的所有字符,將所有的單詞或字符都轉化成符合規範的Token

規範化的token可以分成一下三種類型:

java關鍵字:public, static, final, String, int等等;
自定義的名稱:包名,類名,方法名和變量名;
運算符或者邏輯運算符等符號:+、-、*、/、&&,|| 等等。

步驟二: 語法分析,根據 Token 流來構造樹形表達式也就是 AST。

語法樹的每一個節點都代表着程序代碼中的一個語法結構,如類型、修飾符、運算符等。經過這個步驟後,編譯器就基本不會再對源碼文件進行操作了,後續的操作都建立在抽象語法樹之上。

AST的應用場景

AST 定義了代碼的結構,通過操作 AST,我們可以精準地定位到聲明語句、賦值語句、運算語句等,實現對源代碼的分析、優化、變更等操作。

注: AST操作屬於編譯器級別,對程序運行完全沒有影響,效率相對其他AOP更高

java抽象語法樹常用API類介紹

JCTree

JCTree 是語法樹元素的基類,包含一個重要的字段 pos,該字段用於指明當前語法樹節點(JCTree)在語法樹中的位置,因此我們不能直接用 new 關鍵字來創建語法樹節點,即使創建了也沒有意義。

重點介紹幾個JCTree的子類:

1、JCStatement:聲明語法樹節點,常見的子類如下

  • JCBlock:語句塊語法樹節點
  • JCReturn:return 語句語法樹節點
  • JCClassDecl:類定義語法樹節點
  • JCVariableDecl:字段 / 變量定義語法樹節點

2、JCMethodDecl:方法定義語法樹節點
3、JCModifiers:訪問標誌語法樹節點
4、JCExpression:表達式語法樹節點,常見的子類如下

  • JCAssign:賦值語句
  • JCAssignOp:+=
  • JCIdent:標識符,可以是變量,類型,關鍵字等等
  • JCLiteral: 字面量表達式,如123, “string”等
  • JCBinary:二元操作符

JCTrees更多API的介紹可以查看如下鏈接

https://blog.csdn.net/u013998373/article/details/90050810

TreeMaker

TreeMaker 用於創建一系列的語法樹節點,我們上面説了創建 JCTree 不能直接使用 new 關鍵字來創建,所以 Java 為我們提供了一個工具,就是 TreeMaker,它會在創建時為我們創建的 JCTree 對象設置 pos 字段,所以必須使用上下文相關的 TreeMaker 對象來創建語法樹節點。

着重介紹一下常用的幾個方法

TreeMaker.Modifiers

TreeMaker.Modifiers 方法用於創建訪問標誌語法樹節點(JCModifiers),源碼如下

public JCModifiers Modifiers(long flags) {
    return Modifiers(flags, List.< JCAnnotation >nil());
}

public JCModifiers Modifiers(long flags,
    List<JCAnnotation> annotations) {
        JCModifiers tree = new JCModifiers(flags, annotations);
        boolean noFlags = (flags & (Flags.ModifierFlags | Flags.ANNOTATION)) == 0;
        tree.pos = (noFlags && annotations.isEmpty()) ? Position.NOPOS : pos;
        return tree;
}
  1. flags:訪問標誌
  2. annotations:註解列表

其中 flags 可以使用枚舉類 com.sun.tools.javac.code.Flags 來表示,例如我們可以這樣用,就生成了下面的訪問標誌了。

示例:
創建訪問修飾符 public

treeMaker.Modifiers(Flags.PUBLIC);

TreeMaker.ClassDef

TreeMaker.ClassDef 用於創建類定義語法樹節點(JCClassDecl), 源碼如下:

public JCClassDecl ClassDef(JCModifiers mods,
    Name name,
    List<JCTypeParameter> typarams,
    JCExpression extending,
    List<JCExpression> implementing,
    List<JCTree> defs) {
        JCClassDecl tree = new JCClassDecl(mods,
                                     name,
                                     typarams,
                                     extending,
                                     implementing,
                                     defs,
                                     null);
        tree.pos = pos;
        return tree;
}
  1. mods:訪問標誌,可以通過 TreeMaker.Modifiers 來創建
  2. name:類名
  3. typarams:泛型參數列表
  4. extending:父類
  5. implementing:實現的接口
  6. defs:類定義的詳細語句,包括字段、方法的定義等等

TreeMaker.MethodDef

TreeMaker.MethodDef 用於創建方法定義語法樹節點(JCMethodDecl),源碼如下

public JCMethodDecl MethodDef(JCModifiers mods,
    Name name,
    JCExpression restype,
    List<JCTypeParameter> typarams,
    List<JCVariableDecl> params,
    List<JCExpression> thrown,
    JCBlock body,
    JCExpression defaultValue) {
        JCMethodDecl tree = new JCMethodDecl(mods,
                                       name,
                                       restype,
                                       typarams,
                                       params,
                                       thrown,
                                       body,
                                       defaultValue,
                                       null);
        tree.pos = pos;
        return tree;
}

public JCMethodDecl MethodDef(MethodSymbol m,
    Type mtype,
    JCBlock body) {
        return (JCMethodDecl)
            new JCMethodDecl(
                Modifiers(m.flags(), Annotations(m.getAnnotationMirrors())),
                m.name,
                Type(mtype.getReturnType()),
                TypeParams(mtype.getTypeArguments()),
                Params(mtype.getParameterTypes(), m),
                Types(mtype.getThrownTypes()),
                body,
                null,
                m).setPos(pos).setType(mtype);
}
  1. mods:訪問標誌
  2. name:方法名
  3. restype:返回類型
  4. typarams:泛型參數列表
  5. params:參數列表
  6. thrown:異常聲明列表
  7. body:方法體
  8. defaultValue:默認方法(可能是 interface 中的哪個 default)
  9. m:方法符號
  10. mtype:方法類型。包含多種類型,泛型參數類型、方法參數類型、異常參數類型、返回參數類型。

注: 返回類型 restype 填寫 null 或者 treeMaker.TypeIdent(TypeTag.VOID) 都代表返回 void 類型

示例

創建方法

    public String getUserName(String userName){
       return userName;
    }
ListBuffer<JCTree.JCStatement> usernameStatement = new ListBuffer<>();
usernameStatement.append(treeMaker.Return(treeMaker.Ident(names.fromString("userName"))));
JCTree.JCBlock usernameBody = treeMaker.Block(0, usernameStatement .toList());

// 生成入參
JCTree.JCVariableDecl param = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER), names.fromString("userName"),treeMaker.Ident(names.fromString("String")), null);
com.sun.tools.javac.util.List<JCTree.JCVariableDecl> parameters = com.sun.tools.javac.util.List.of(param);

JCTree.JCMethodDecl username = treeMaker.MethodDef(
        treeMaker.Modifiers(Flags.PUBLIC), 
        names.fromString("getUserName"), // 方法名
        treeMaker.Ident(names.fromString("String")), // 返回類型
        com.sun.tools.javac.util.List.nil(),
        parameters, // 入參
        com.sun.tools.javac.util.List.nil(),
        usernameBody ,
        null
);

TreeMaker.VarDef

TreeMaker.VarDef 用於創建字段 / 變量定義語法樹節點(JCVariableDecl),源碼如下

public JCVariableDecl VarDef(JCModifiers mods,
    Name name,
    JCExpression vartype,
    JCExpression init) {
        JCVariableDecl tree = new JCVariableDecl(mods, name, vartype, init, null);
        tree.pos = pos;
        return tree;
}

public JCVariableDecl VarDef(VarSymbol v,
    JCExpression init) {
        return (JCVariableDecl)
            new JCVariableDecl(
                Modifiers(v.flags(), Annotations(v.getAnnotationMirrors())),
                v.name,
                Type(v.type),
                init,
                v).setPos(pos).setType(v.type);
}
  1. mods:訪問標誌
  2. name:參數名稱
  3. vartype:類型
  4. init:初始化語句
  5. v:變量符號

示例:

創建變量: private String username = "lyb-geek";

treeMaker.VarDef(treeMaker.Modifiers(Flags.PRIVATE), names.fromString("useranme"), treeMaker.Ident(names.fromString("String"),  treeMaker.Literal("lyb-geek");

TreeMaker.Ident

TreeMaker.Ident 用於創建標識符語法樹節點(JCIdent)可以表示類、變量引用或者方法。源碼如下

public JCIdent Ident(Name name) {
        JCIdent tree = new JCIdent(name, null);
        tree.pos = pos;
        return tree;
}

public JCIdent Ident(Symbol sym) {
        return (JCIdent)new JCIdent((sym.name != names.empty)
                                ? sym.name
                                : sym.flatName(), sym)
            .setPos(pos)
            .setType(sym.type);
}

public JCExpression Ident(JCVariableDecl param) {
        return Ident(param.sym);
}

示例:

創建username的引用

treeMaker.Ident(names.fromString("username"))))

TreeMaker.Return

TreeMaker.Return 用於創建 return 語句(JCReturn),源碼如下

public JCReturn Return(JCExpression expr) {
        JCReturn tree = new JCReturn(expr);
        tree.pos = pos;
        return tree;
}

示例
return this.username

treeMaker.Return(treeMaker.Select(treeMaker.Ident(names.fromString("this")),names.fromString("useranme")));

TreeMaker.Select

TreeMaker.Select 用於創建域訪問 / 方法訪問(這裏的方法訪問只是取到名字,方法的調用需要用 TreeMaker.Apply)語法樹節點(JCFieldAccess),源碼如下

public JCFieldAccess Select(JCExpression selected,
    Name selector) 
{
        JCFieldAccess tree = new JCFieldAccess(selected, selector, null);
        tree.pos = pos;
        return tree;
}

public JCExpression Select(JCExpression base,
    Symbol sym) {
        return new JCFieldAccess(base, sym.name, sym).setPos(pos).setType(sym.type);
}
  1. selected:. 運算符左邊的表達式
  2. selector:. 運算符右邊的表達式

示例

獲取方法logDTO.setArgs()

treeMaker.Select(treeMaker.Ident(getNameFromString("logDTO")),
                        getNameFromString("setArgs")

TreeMaker.NewClass

TreeMaker.NewClass 用於創建 new 語句語法樹節點(JCNewClass), 源碼如下:

public JCNewClass NewClass(JCExpression encl,
    List<JCExpression> typeargs,
    JCExpression clazz,
    List<JCExpression> args,
    JCClassDecl def) {
        JCNewClass tree = new JCNewClass(encl, typeargs, clazz, args, def);
        tree.pos = pos;
        return tree;
}
  1. encl:不太明白此參數的含義,我看很多例子中此參數都設置為 null
  2. typeargs:參數類型列表
  3. clazz:待創建對象的類型
  4. args:參數列表
  5. def:類定義

示例:

創建 List args = new ArrayList();

 JCTree.JCNewClass argsListclass = treeMaker.NewClass(null, null, memberAccess("java.util.ArrayList"), List.nil(), null);

        
        JCTree.JCVariableDecl args = makeVarDef(treeMaker.Modifiers(0),
                memberAccess("java.util.List"),
                "args",
                argsListclass
        );

TreeMaker.Apply

TreeMaker.Apply 用於創建方法調用語法樹節點(JCMethodInvocation),源碼如下:

public JCMethodInvocation Apply(List<JCExpression> typeargs,
    JCExpression fn,
    List<JCExpression> args) {
        JCMethodInvocation tree = new JCMethodInvocation(typeargs, fn, args);
        tree.pos = pos;
        return tree;
}
  1. typeargs:參數類型列表
  2. fn:調用語句
  3. args:參數列表

TreeMaker.Assign
TreeMaker.Assign 用户創建賦值語句語法樹節點(JCAssign),源碼如下:

public JCAssign Assign(JCExpression lhs,
    JCExpression rhs) {
        JCAssign tree = new JCAssign(lhs, rhs);
        tree.pos = pos;
        return tree;
}
  1. lhs:賦值語句左邊表達式
  2. rhs:賦值語句右邊表達式

示例
創建 username = "lyb-geek"

treeMaker.Assign(treeMaker.Ident(names.fromString("username")))), treeMaker.Literal("lyb-geek"))

TreeMaker.Exec

TreeMaker.Exec 用於創建可執行語句語法樹節點(JCExpressionStatement),源碼如下:

public JCExpressionStatement Exec(JCExpression expr) {
        JCExpressionStatement tree = new JCExpressionStatement(expr);
        tree.pos = pos;
        return tree;
}

注: TreeMaker.Apply 以及 TreeMaker.Assign 就需要外面包一層 TreeMaker.Exec 來獲得一個 JCExpressionStatement

示例:

username = “lyb-geek”

treeMaker.Exec(treeMaker.Assign(treeMaker.Ident(names.fromString("username")),treeMaker.Binary(JCTree.Tag.PLUS,treeMaker.Literal("lyb"),treeMaker.Literal("-geek"))))

TreeMaker.Block

TreeMaker.Block 用於創建組合語句的語法樹節點(JCBlock),源碼如下:

public JCBlock Block(long flags,
    List<JCStatement> stats) {
        JCBlock tree = new JCBlock(flags, stats);
        tree.pos = pos;
        return tree;
}
  1. flags:訪問標誌
  2. stats:語句列表

示例

創建代碼塊

List<JCTree.JCStatement> jcStatementList = List.nil();
treeMaker.Block(0, jcStatementList);

TreeMaker更多詳細API可以查看如下鏈接
http://www.docjar.com/docs/api/com/sun/tools/javac/tree/TreeMaker.html

Names

Names封裝了操作標識符的方法,類、方法、參數的名稱都可以通過names來獲取

大家如果對AST感興趣,可以通過https://astexplorer.net/在線體驗一下

實戰

示例主要通過APT+AST實現一個統計方法調用耗時以及記錄日誌的功能

注: 大家可以通過JavaParserJavaParser來簡化對AST的操作。

本示例通過jdk自帶的tools.jar工具類進行操作

1、在pom引入tools.jar gav
  <dependency>
            <groupId>com.sun</groupId>
            <artifactId>tools</artifactId>
            <version>1.8</version>
            <scope>system</scope>
            <systemPath>${java.home}/../lib/tools.jar</systemPath>
        </dependency>
2、自定義註解CostTimeRecoder
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
@Documented
public @interface CostTimeRecoder {
}
3、編寫註解處理器
@AutoService(Processor.class)
@SupportedOptions("debug")
public class CostTimeRecordProcessor extends AbstractComponentProcessor {


    /**
     * 元素輔助類
     */
    private Elements elementUtils;

    /**
     * 日誌輸出工具類
     */
    private Messager meessager;

    /**
     * 抽象語法樹
     */
    private JavacTrees trees;

    /**
     * 封裝了創建或者修改AST節點的一些方法
     */
    private TreeMaker treeMaker;

    /**
     * 封裝了操作標識符的方法
     */
    private Names names;



    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        elementUtils = processingEnv.getElementUtils();
        meessager = processingEnv.getMessager();
        this.trees = JavacTrees.instance(processingEnv);
        Context context = ((JavacProcessingEnvironment)processingEnv).getContext();
        this.treeMaker = TreeMaker.instance(context);
        this.names = Names.instance(context);
    }


    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return Collections.singleton(CostTimeRecoder.class.getName());
    }


    @Override
    protected boolean processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

        if (annotations == null || annotations.isEmpty()) {
            return false;
        }


        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(CostTimeRecoder.class);
        if (elements == null || elements.isEmpty()){
            return false;
        }

        if (!roundEnv.processingOver()) {
            elements.stream() .filter(element -> element instanceof ExecutableElement)
                    .map(element -> (ExecutableElement) element)
                    .forEach(method -> {
                        TypeElement typeElement = (TypeElement)method.getEnclosingElement();
                        JCTree.JCClassDecl tree = trees.getTree(typeElement);
                        JCTree.JCMethodDecl methodDecl = trees.getTree(method);
                        CostTimeRecordAstTranslator costTimeRecordAstTranslator = new CostTimeRecordAstTranslator(treeMaker,names,meessager,tree,methodDecl);
                        costTimeRecordAstTranslator.setTrees(trees);
                        // 導入引用類,如果不配置import,則方法調用,需配置全類路徑,
                        // 比如LogFactory.getLogger(),如果沒導入LogFactory,則方法需寫成com.github.lybgeek.log.factory.LogFactory.getLogger
                        // 配置後,僅需寫成LogFactory.getLogger即可
                        costTimeRecordAstTranslator.addImportInfo(typeElement, LogFactory.class.getPackage().getName(),LogFactory.class.getSimpleName());
                        costTimeRecordAstTranslator.addImportInfo(typeElement,LogDTO.class.getPackage().getName(),LogDTO.class.getSimpleName());
//                        costTimeRecordAstTranslator.addImportInfo(typeElement, LogService.class.getPackage().getName(),LogService.class.getSimpleName());

                        tree.accept(costTimeRecordAstTranslator);

                    });
        }



        return false;
    }


    private String getPackageName(TypeElement typeElement) {
        return elementUtils.getPackageOf(typeElement).getQualifiedName()
                .toString();
    }




}
3、編寫AST TreeTranslator

:省略業務的TreeTranslator,就列出基類,可能對大家比較有用,需要業務的實現方法,直接見下方demo鏈接

public abstract class AbstractTreeTranslator extends TreeTranslator {

    /**
     * 封裝了創建或者修改AST節點的一些方法
     */
    protected TreeMaker treeMaker;

    /**
     * 封裝了操作標識符的方法
     */
    protected Names names;

    /**
     * 日誌輸出工具類
     */
    protected Messager meessager;

    /**
     * 抽象語法樹
     */
    private JavacTrees trees;

    public AbstractTreeTranslator(TreeMaker treeMaker, Names names, Messager meessager) {
        this.treeMaker = treeMaker;
        this.names = names;
        this.meessager = meessager;
    }


    /**
     * 根據字符串獲取Name
     * @param s
     * @return
     */
    public Name getNameFromString(String s) { return names.fromString(s); }


    /**
     * 創建變量語句
     * @param modifiers 訪問修飾符
     * @param name 參數名稱
     * @param varType 參數類型
     * @param init 初始化賦值語句
     * 示例
     *   JCTree.JCVariableDecl var = makeVarDef(treeMaker.Modifiers(0), "xiao", memberAccess("java.lang.String"), treeMaker.Literal("methodName"));
     *   生成語句為:String xiao = "methodName";
     * @return
     */
    public JCTree.JCVariableDecl makeVarDef(JCTree.JCModifiers modifiers, JCTree.JCExpression varType,String name, JCTree.JCExpression init) {
        return treeMaker.VarDef(
                modifiers,
                getNameFromString(name),
                varType,
                init
        );
    }

    /**
     * 創建 域/方法 的多級訪問, 方法的標識只能是最後一個
     * @param components 比如java.lang.System.out.println
     * @return
     */
    public JCTree.JCExpression memberAccess(String components) {
        String[] componentArray = components.split("\\.");
        JCTree.JCExpression expr = treeMaker.Ident(getNameFromString(componentArray[0]));
        for (int i = 1; i < componentArray.length; i++) {
            expr = treeMaker.Select(expr, getNameFromString(componentArray[i]));
        }
        return expr;
    }

    /**
     * 給變量賦值
     * @param lhs
     * @param rhs
     * @return
     * 示例:makeAssignment(treeMaker.Ident(getNameFromString("xiao")), treeMaker.Literal("assignment test"));
     * 生成的賦值語句為:xiao = "assignment test";
     */
    public JCTree.JCExpressionStatement makeAssignment(JCTree.JCExpression lhs, JCTree.JCExpression rhs) {
        return treeMaker.Exec(
                treeMaker.Assign(
                        lhs,
                        rhs
                )
        );
    }

    /**
     * 導入方法依賴的package包
     * @param packageName
     * @param className
     * @return
     */
    public JCTree.JCImport buildImport(String packageName, String className) {
        JCTree.JCIdent ident = treeMaker.Ident(names.fromString(packageName));
        JCTree.JCImport jcImport = treeMaker.Import(treeMaker.Select(
                ident, names.fromString(className)), false);
         meessager.printMessage(Diagnostic.Kind.NOTE,jcImport.toString());
        return jcImport;
    }

    /**
     * 導入方法依賴的package包
     * @param element  class
     * @param packageName
     * @param className
     * @return
     */
    public void addImportInfo(TypeElement element, String packageName, String className) {
        TreePath treePath = getTrees().getPath(element);
        Tree leaf = treePath.getLeaf();
        if (treePath.getCompilationUnit() instanceof JCTree.JCCompilationUnit && leaf instanceof JCTree) {
            JCTree.JCCompilationUnit jccu = (JCTree.JCCompilationUnit) treePath.getCompilationUnit();

            for (JCTree jcTree : jccu.getImports()) {
                if (jcTree != null && jcTree instanceof JCTree.JCImport) {
                    JCTree.JCImport jcImport = (JCTree.JCImport) jcTree;
                    if (jcImport.qualid != null && jcImport.qualid instanceof JCTree.JCFieldAccess) {
                        JCTree.JCFieldAccess jcFieldAccess = (JCTree.JCFieldAccess) jcImport.qualid;
                        try {
                            if (packageName.equals(jcFieldAccess.selected.toString()) && className.equals(jcFieldAccess.name.toString())) {
                                return;
                            }
                        } catch (NullPointerException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
            java.util.List<JCTree> trees = new ArrayList<>();
            trees.addAll(jccu.defs);
            JCTree.JCImport jcImport = buildImport(packageName,className);
            if (!trees.contains(jcImport)) {
                trees.add(0, jcImport);
            }
            jccu.defs = List.from(trees);
        }
    }


    public JavacTrees getTrees() {
        return trees;
    }

    public void setTrees(JavacTrees trees) {
        this.trees = trees;
    }
}
4、測試

編寫測試類

public class HelloService {

    @CostTimeRecoder
    public String sayHello(String username){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "hello : " + username;
    }
}

測試主類

public class AptAstMainTest {

    public static void main(String[] args) {


        System.out.println(new HelloService().sayHello("zhangsan"));


    }
}

運行查看控制枱

會發現多了耗時,以及日誌打印。我們查看HelloService .class文件,會發現多瞭如下內容

public class HelloService {
    public HelloService() {
    }

    public String sayHello(String username) {
        Long startTime = System.currentTimeMillis();

        try {
            TimeUnit.SECONDS.sleep(3L);
        } catch (InterruptedException var7) {
            var7.printStackTrace();
        }

        Long endTime = System.currentTimeMillis();
        Long costTime = endTime - startTime;
        String msg = String.format("costTime = %s(ms)", costTime);
        System.out.println(msg);
        List args = new ArrayList();
        args.add(username);
        this.saveLog(costTime, args);
        return "hello : " + username;
    }

    private void saveLog(Long costTime, List args) {
        LogDTO logDTO = new LogDTO();
        logDTO.setMethodName("sayHello");
        logDTO.setClassName("com.github.lybgeek.test.service.HelloService");
        logDTO.setCostTime(costTime);
        logDTO.setArgs(args);
        LogService logService = LogFactory.getLogger();
        logService.save(logDTO);
    }
}

總結

本文主要重點介紹AST的用法,對AOP的實現基本上是一筆帶過。原因主要是平時除非是對性能有特別要求,我們實現AOP通常會在運行期實現,而非在編譯期實現。其次AST比較偏底層,如果出問題,排查難度會比較高。當然如果團隊有對AST很熟悉的話,能兼顧性能是最好的。

demo鏈接

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-apt-ast

參考鏈接

https://my.oschina.net/u/4030990/blog/3211858
https://blog.csdn.net/a_zhenzhen/article/details/86065063
https://www.jianshu.com/p/ff8ec920f5b9

user avatar lpc63szb 头像 lyflexi 头像
点赞 2 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.