lombok自定義擴展實踐

lombok是一款能夠在java代碼編譯階段改變代碼的插件。比如生成setter和getter方法,生成log類變量等,能夠簡化一些特定的模版式代碼。本文將以實現(xiàn)一個基于特定注解生成日志代碼的方式,簡單介紹在lombok基礎(chǔ)上自定義擴展的方式。

1、實現(xiàn)功能

基于自定義注解,將下面的代碼塊1變成代碼塊2,自動生成日志代碼:

    //代碼塊1
    static void m1(Map<String, String> req) {
        System.out.println("m1 running");
    }
    //代碼塊2
    static void m1(Map<String, String> req) {
        log.info("Application.m1 req:{}", JSON.toJSONString(req));
        System.out.println("m1 running");
    }

2、環(huán)境準備

首先搭建lombok工程,git地址:https://github.com/rzwitserloot/lombok,并安裝ant環(huán)境,lombok需要使用ant編譯,并下載openjdk(可選),使用openjdk有助于理解javac的源碼,因為默認jkd是沒有javac的源碼的。
關(guān)于這些環(huán)境,自己想辦法百度去搞定吧!

3、核心實現(xiàn)

自定義注解:

package lombok.extern.youzan;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 *
 * Date: 2018/5/26
 * @author xuzhiyi
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface LogBefore {
    String level() default "info";
}

這個注解是用來作用在方法上,來表示需要在方法第一行增加代碼,log方法傳入的參數(shù);

自定義注解解析:

package lombok.javac.handlers;

import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Name;

import org.mangosdk.spi.ProviderFor;

import java.util.logging.Logger;

import lombok.core.AST;
import lombok.core.AnnotationValues;
import lombok.core.HandlerPriority;
import lombok.extern.youzan.LogBefore;
import lombok.javac.JavacAnnotationHandler;
import lombok.javac.JavacNode;
import lombok.javac.JavacTreeMaker;

/**
 * Date: 2018/5/26
 *
 * @author xuzhiyi
 */
@ProviderFor(JavacAnnotationHandler.class)
@HandlerPriority(20)
public class HandleLogBefore extends JavacAnnotationHandler<LogBefore> {

    private static Logger logger = Logger.getLogger(HandleLogBefore.class.getName());

    @Override
    public void handle(AnnotationValues<LogBefore> annotation, JCTree.JCAnnotation ast, JavacNode annotationNode) {
        JavacNode methodNode = annotationNode.up();
        switch (methodNode.getKind()) {
            case METHOD:
                JCTree.JCMethodDecl methodDecl = (JCTree.JCMethodDecl) methodNode.get();
                String methodName = methodDecl.getName().toString();
                String logLevel = annotation.getInstance().level();

                if (logLevel == null) {
                    logLevel = "info";
                }

                String logFieldName = "log";
                String logMethodName = logFieldName + "." + logLevel;

                String className = null;
                String logTypeName = null;
                Name logVarName = null;

                JavacNode typeNode = methodNode.up();
                if (AST.Kind.TYPE == typeNode.getKind()) {
                    JCTree.JCClassDecl classDecl = (JCTree.JCClassDecl) typeNode.get();
                    className = classDecl.getSimpleName().toString();
                    // 遍歷類,尋找是否有l(wèi)og類變量
                    for (JCTree def : classDecl.defs) {
                        if (def instanceof JCTree.JCVariableDecl) {
                            JCTree.JCVariableDecl variableDecl = (JCTree.JCVariableDecl) def;
                            if (variableDecl.name.toString().equals(logFieldName)) {
                                logVarName = variableDecl.name;
                                logTypeName = variableDecl.getType().toString();
                                break;
                            }
                        }
                    }
                    // 沒有l(wèi)og類變量,則直接返回
                    if (logVarName == null) {
                        return;
                    }
                }

                JCTree.JCBlock block = methodDecl.getBody();
                List<JCTree.JCStatement> statements = block.stats;

                JavacTreeMaker maker = annotationNode.getTreeMaker();

                JCTree.JCExpression logMethod = JavacHandlerUtil.chainDotsString(typeNode, logMethodName);
                JCTree.JCExpression logType = JavacHandlerUtil.chainDotsString(typeNode, logTypeName);

                List<JCTree.JCVariableDecl> parameters = methodDecl.getParameters();

                JCTree.JCExpression apply = maker.Apply(List.<JCTree.JCExpression>of(logType), logMethod,
                                                        generateLogArgs(parameters, className, methodName, maker, typeNode));

                ListBuffer<JCTree.JCStatement> listBuffer = new ListBuffer<JCTree.JCStatement>();
                listBuffer.append(maker.Exec(apply));

                for (JCTree.JCStatement stat : statements) {
                    listBuffer.append(stat);
                }
                methodDecl.body.stats = listBuffer.toList();
                annotationNode.getAst().setChanged();

                break;
            default:
                annotationNode.addError("@LogBefore is legal only on types.");
                break;
        }
    }

    /**
     * 生成log的參數(shù)表達式
     */
    public static List<JCTree.JCExpression> generateLogArgs(List<JCTree.JCVariableDecl> parameters, String className, String methodName, JavacTreeMaker maker, JavacNode typeNode) {
        JCTree.JCExpression[] argsArray = new JCTree.JCExpression[parameters.size() + 1];

        StringBuilder stringBuilder = new StringBuilder(className).append(".").append(methodName);
        if (parameters.size() > 0) {
            stringBuilder.append(" ");
            for (JCTree.JCVariableDecl variableDecl : parameters) {
                stringBuilder.append(variableDecl.getName()).append(":{},");
            }
            stringBuilder.deleteCharAt(stringBuilder.length() - 1);
        } else {
            stringBuilder.append(" begin");
        }

        argsArray[0] = maker.Literal(stringBuilder.toString());

        JCTree.JCExpression jsonStringMethod = JavacHandlerUtil.chainDotsString(typeNode, "com.alibaba.fastjson.JSON.toJSONString");

        for (int i = 0; i < parameters.size(); i++) {
            argsArray[i + 1] = maker.Apply(List.<JCTree.JCExpression>nil(), jsonStringMethod, List.<JCTree.JCExpression>of(maker.Ident(parameters.get(i))));
        }

        return List.<JCTree.JCExpression>from(argsArray);
    }
}

這是自定義的處理LogBefore的handler,lombok插件在編譯時遇到LogBefore時會掉調(diào)用這個handler來處理。這里使用javac的一些相關(guān)方法,比較難理解,會將我們的java代碼抽象成一顆語法樹,我們在這顆樹上進行相關(guān)的處理動作,關(guān)于javac的文檔比較少,好在其方法名都比較直接,基本可以通過方法名來理解其作用,使用openjdk在使用的時候會更舒服一些。

4、編譯使用

lombok jar 包生成:
在lombok代碼當前目錄使用ant打包,當前目錄cmd下輸入ant回車就可以了,jar包會出現(xiàn)在dist目錄下。然后用產(chǎn)生的jar包替換掉當前maven倉庫中的jar包。

自定義注解使用
書寫代碼如下:

@Slf4j
public class Application {

    @LogBefore
    static void m1(Map<String, String> req) {
        System.out.println("m1 running");
    }
}

使用mvn clean package打包編譯后代碼如下:

public class Application {
    private static final Logger log = LoggerFactory.getLogger(Application.class);

    public Application() {
    }

    static void m1(Map<String, String> req) {
        log.info("Application.m1 req:{}", JSON.toJSONString(req));
        System.out.println("m1 running");
    }
}

總結(jié):
1、難點在于handler的邏輯處理,里面的javac的api不太容易掌握,容易出錯,本文也只是簡單實現(xiàn),可能還有很多潛在的情況沒有考慮到。
2、基于這種在javac期間改變代碼的方式,可以在模版代碼比較多的時候考慮使用。我后面還寫了使用打樁的方式,在代碼里放入特定的空方法,再編譯期間動態(tài)替換的代碼,也是一種減少模版代碼的方式。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,268評論 6 342
  • ANT build.xml文件詳解(一) Ant的概念 可能有些讀者并不連接什么是Ant以及入可使用它,但只要使用...
    SkTj閱讀 4,160評論 0 2
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,983評論 25 709
  • 《流星》 你在等我嗎 我摘下頭發(fā) 順著爬上屋頂 月亮說 今晚就睡在你的閃閃發(fā)光里 昨晚的頭發(fā)讓我著迷 我在等她 ...
    半余閱讀 256評論 0 0
  • 三個 login 界面的臨摹設(shè)計。
    Puddinnng閱讀 341評論 0 0

友情鏈接更多精彩內(nèi)容