標題:進階 OpenRewrite 技術:使用訊息實現複雜邏輯
描述:如何在 AST 的多個層面上實現協調行動...
標籤:java、openrewrite、訊息傳遞
封面圖片:https://images.pexels.com/photos/260551/pexels-photo-260551.jpeg
系列:Openrewrite
已發布:真實
我已經在本系列的第一篇文章中介紹過 Openrewrite。如果你還沒讀過,我需要複習一下,我建議你收藏這篇文章,以後再回來看。這樣可以嗎?那就繼續吧。
上一篇文章討論的所有用例都涉及一個只需要在LST的一個且僅一個層級上找到資訊的配方。由於一圖勝過千言,而且我感覺我沒有表達清楚,因此這裡有一個可靠的 AST 範例:
到目前為止,範例涉及對方法呼叫的修改,或例如類別重命名。
在上圖中,每個節點對應一個 AST 元素(這裡顯然簡化了)。正如我們已經看到的, OpenRewrite使用訪客模式來遍歷樹。更準確地說,食譜實現了存取者,其介面與所操作的語言相對應。
在這種情況下,操作是在Java上執行的,因此使用的訪客將是JavaIsoVisitor
。
是的,但是如果我的用例需要辨識方法呼叫以對類別中包含它的方法的聲明採取行動怎麼辦?
是的,我明白,這個問題也讓我輾轉反側了好幾天。不過,你看,OpenRewrite 背後的人也是。
因此,他們為我們提供了一個非常令人信服且相當安全的訊息傳遞系統。
假設有人不明智,不,別追問,我不會告訴你是誰。好吧,是我。那麼,假設一下,我剛才說的是,有人不明智地在一個庫中開發了一種記錄方法處理過程的方法:
import static com.github.jtama.toxic.timer.logStart;
import static com.github.jtama.toxic.timer.logEnd;
import static com.github.jtama.acme.Process.longRunningMethod;
public class ManualGearCar {
@Deprecated<4>
public void drift(String param) {
logStart();<1>
longRunningMethod(param);<2>
logEnd();<3>
}
}
記錄方法執行的開始
進行治療
記錄方法執行的結束
稍後會感興趣的隨機註釋
經過深入研究,我們發現了***micrometer***及其註解io.micrometer.core.annotation.Timed
的存在。
因此,作為開發人員,我將辨識Timer#logStart()
方法的呼叫,如果找到它:
刪除它
如果存在Timer#logEnd()
方法呼叫,則刪除它
在drift
方法宣告中加入@Timed
註解
這正是我們將在食譜中實現的邏輯。
本例中,我們僅介紹訪客模式的實現,而非完整的方案。不過,一切程式碼均可在GitHub 倉庫中找到。
private static class ReplaceCompareVisitor extends JavaIsoVisitor<ExecutionContext> {
private final MethodMatcher logStartInvocaMatcher = new MethodMatcher("com.github.jtama.toxic.Timer logStart()");<1>
private final MethodMatcher logEndInvocaMatcher = new MethodMatcher("com.github.jtama.toxic.Timer logEnd()");
private final AnnotationMatcher logStartMatcher = new AnnotationMatcher("@io.micrometer.core.annotation.Timed");
private final JavaTemplate annotationTemplate = JavaTemplate.builder("@Timed")
.imports("io.micrometer.core.annotation.Timed")
.javaParser(
JavaParser.fromJavaVersion()
.classpath(JavaParser.runtimeClasspath()))
.build();
public ReplaceCompareVisitor() {
maybeRemoveImport("com.github.jtama.toxic.Timer");
}
@Override
public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) {<3>
J.MethodDeclaration md = super.visitMethodDeclaration(method, ctx);
Cursor cursor = getCursor();
if (cursor.getMessage("appendAnnotation", false)) {
if (md.getLeadingAnnotations().stream()
.noneMatch(logStartMatcher::matches)) {
maybeAddImport("io.micrometer.core.annotation.Timed");
md = annotationTemplate.apply(cursor, method.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName)));
}
}
return md;
}
@Override
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {<2>
J.MethodInvocation mi = super.visitMethodInvocation(method, ctx);
if (logStartInvocaMatcher.matches(mi) || logEndInvocaMatcher.matches(mi)) {
getCursor().putMessageOnFirstEnclosing(J.MethodDeclaration.class, "appendAnnotation", true);
return null;
}
return mi;
}
}
[方法呼叫訪客](#方法visitMethodInvocation
( visitMethodInvocation ))
[方法宣告訪客](#方法visitMethodDeclaration
( visitMethodDeclaration ))
本節使用了兩個 OpenRewrite 工具。 MethodMatcher 可以精確辨識目標MethodMatcher
呼叫,例如logStart()
和logEnd()
。
AnnotationMatcher
用於檢查方法上是否存在@Timed
註解。
最後, JavaTemplate
可以更輕鬆地將註解插入原始程式碼並處理必要的導入。
這些實用程式簡化了 AST 的操作並準備在訪客中應用的修改。
visitMethodInvocation
)該訪客遍歷原始程式碼中的每個方法呼叫。
當遇到對logStart()
或logEnd()
的呼叫時,它會新增一則訊息來表示稍後需要新增註解。
一則訊息實際上只是一個元組<clef;valeur>
,但這裡有趣的是,我們指定了訊息的可存取範圍,以避免污染整個上下文。
在這種情況下,只有包含相關呼叫的方法才能夠存取該訊息( putMessageOnFirstEnclosing(J.MethodDeclaration.class...
)。
在這種情況下傳回的null
值具有刪除目前存取的節點的效果,即呼叫該方法。
visitMethodDeclaration
)這個確實比較簡單。
如果我收到一條訊息並且該方法尚未註釋(如果開發人員想要放置皮帶和吊帶......¯\_(ツ)_/¯ ),我會加入@Timed
註釋。
因為一張圖片勝過千言萬語。
接下來將介紹更複雜的例子,請記住,所有程式碼都可以在Openrewrite 重構程式碼儲存庫中找到。