lombok自定義擴(kuò)展實(shí)踐

lombok是一款能夠在java代碼編譯階段改變代碼的插件欺殿。比如生成setter和getter方法再沧,生成log類變量等饶囚,能夠簡(jiǎn)化一些特定的模版式代碼军援。本文將以實(shí)現(xiàn)一個(gè)基于特定注解生成日志代碼的方式腿短,簡(jiǎn)單介紹在lombok基礎(chǔ)上自定義擴(kuò)展的方式屏箍。

1、實(shí)現(xiàn)功能

基于自定義注解橘忱,將下面的代碼塊1變成代碼塊2赴魁,自動(dòng)生成日志代碼:

    //代碼塊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)境準(zhǔn)備

首先搭建lombok工程钝诚,git地址:https://github.com/rzwitserloot/lombok颖御,并安裝ant環(huán)境,lombok需要使用ant編譯凝颇,并下載openjdk(可選)潘拱,使用openjdk有助于理解javac的源碼,因?yàn)槟J(rèn)jkd是沒(méi)有javac的源碼的祈噪。
關(guān)于這些環(huán)境泽铛,自己想辦法百度去搞定吧!

3辑鲤、核心實(shí)現(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";
}

這個(gè)注解是用來(lái)作用在方法上盔腔,來(lái)表示需要在方法第一行增加代碼,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;
                            }
                        }
                    }
                    // 沒(méi)有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ù)表達(dá)式
     */
    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插件在編譯時(shí)遇到LogBefore時(shí)會(huì)掉調(diào)用這個(gè)handler來(lái)處理舀透。這里使用javac的一些相關(guān)方法,比較難理解决左,會(huì)將我們的java代碼抽象成一顆語(yǔ)法樹(shù)愕够,我們?cè)谶@顆樹(shù)上進(jìn)行相關(guān)的處理動(dòng)作,關(guān)于javac的文檔比較少佛猛,好在其方法名都比較直接惑芭,基本可以通過(guò)方法名來(lái)理解其作用,使用openjdk在使用的時(shí)候會(huì)更舒服一些继找。

4遂跟、編譯使用

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

自定義注解使用
書(shū)寫(xiě)代碼如下:

@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、難點(diǎn)在于handler的邏輯處理哄尔,里面的javac的api不太容易掌握假消,容易出錯(cuò),本文也只是簡(jiǎn)單實(shí)現(xiàn)岭接,可能還有很多潛在的情況沒(méi)有考慮到置谦。
2、基于這種在javac期間改變代碼的方式亿傅,可以在模版代碼比較多的時(shí)候考慮使用。我后面還寫(xiě)了使用打樁的方式瘟栖,在代碼里放入特定的空方法葵擎,再編譯期間動(dòng)態(tài)替換的代碼,也是一種減少模版代碼的方式半哟。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末酬滤,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子寓涨,更是在濱河造成了極大的恐慌盯串,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件戒良,死亡現(xiàn)場(chǎng)離奇詭異体捏,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)糯崎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén)几缭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人沃呢,你說(shuō)我怎么就攤上這事年栓。” “怎么了薄霜?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵某抓,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我惰瓜,道長(zhǎng)否副,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任鸵熟,我火速辦了婚禮副编,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己痹届,他們只是感情好呻待,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著队腐,像睡著了一般蚕捉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上柴淘,一...
    開(kāi)封第一講書(shū)人閱讀 49,764評(píng)論 1 290
  • 那天迫淹,我揣著相機(jī)與錄音,去河邊找鬼为严。 笑死敛熬,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的第股。 我是一名探鬼主播应民,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼夕吻!你這毒婦竟也來(lái)了诲锹?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤涉馅,失蹤者是張志新(化名)和其女友劉穎归园,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體稚矿,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡庸诱,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了晤揣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片偶翅。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖碉渡,靈堂內(nèi)的尸體忽然破棺而出聚谁,到底是詐尸還是另有隱情,我是刑警寧澤滞诺,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布形导,位于F島的核電站,受9級(jí)特大地震影響习霹,放射性物質(zhì)發(fā)生泄漏朵耕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一淋叶、第九天 我趴在偏房一處隱蔽的房頂上張望阎曹。 院中可真熱鬧,春花似錦、人聲如沸处嫌。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)熏迹。三九已至檐薯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間注暗,已是汗流浹背坛缕。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留捆昏,地道東北人赚楚。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像骗卜,于是被迫代替她去往敵國(guó)和親直晨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

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