在自定義注解與攔截器實現(xiàn)不規(guī)范sql攔截(攔截器實現(xiàn)篇)中提到過,寫了一個idea插件來輔助對Mapper接口中的方法添加自定義注解,這邊記錄一下插件的實現(xiàn)。
需求簡介
在上一篇中,定義了一個自定義注解對需要經(jīng)過where判斷的Mapper sql方法進行修飾。那么,現(xiàn)在想使用一個idea插件來輔助進行自定義注解的增加,需要做到以下幾點:
- 支持在接口名帶Mapper的編輯頁面中,右鍵菜單,顯示增加注解信息的選項
- 鼠標(biāo)移動到該選項,支持顯示可選的需要新增的注解名稱
- 點擊增加,對當(dāng)前Mapper中的所有方法增加對應(yīng)注解;同時,沒有import的文件中需要增加對應(yīng)的包導(dǎo)入。
具體實現(xiàn)
插件開發(fā)所需前置
第一點就是需要gradle進行打包,所以需要配置gradle項目和對應(yīng)的配置文件;第二點就是在Project Structure中,將SDK設(shè)置為IDEA的sdk,從而導(dǎo)入支持對idea界面和編輯內(nèi)容進行處理的api。idea大多數(shù)版本本身就會提供plugin開發(fā)專用的project,對應(yīng)的配置文件會在project模板中初始化,直接用就行。
插件配置文件
plugin.xml,放在reources的META-INF元數(shù)據(jù)文件夾下,自動進行插件基本信息的讀取:
<!-- Plugin Configuration File. Read more: https://plugins.jetbrains.com/docs/intellij/plugin-configuration-file.html -->
<idea-plugin>
<!-- Unique identifier of the plugin. It should be FQN. It cannot be changed between the plugin versions. -->
<id>com.huiluczp.checkAnnocationPlugin</id>
<!-- Public plugin name should be written in Title Case.
Guidelines: https://plugins.jetbrains.com/docs/marketplace/plugin-overview-page.html#plugin-name -->
<name>CheckAnnocationPlugin</name>
<!-- A displayed Vendor name or Organization ID displayed on the Plugins Page. -->
<vendor email="970921331@qq.com" url="https://www.huiluczp.com">huiluczP</vendor>
<!-- Description of the plugin displayed on the Plugin Page and IDE Plugin Manager.
Simple HTML elements (text formatting, paragraphs, and lists) can be added inside of <![CDATA[ ]]> tag.
Guidelines: https://plugins.jetbrains.com/docs/marketplace/plugin-overview-page.html#plugin-description -->
<description>Simple annotation complete plugin used for mybatis mapping interface.</description>
<!-- Product and plugin compatibility requirements.
Read more: https://plugins.jetbrains.com/docs/intellij/plugin-compatibility.html -->
<depends>com.intellij.modules.platform</depends>
<depends>com.intellij.modules.lang</depends>
<depends>com.intellij.modules.java</depends>
<!-- Extension points defined by the plugin.
Read more: https://plugins.jetbrains.com/docs/intellij/plugin-extension-points.html -->
<extensions defaultExtensionNs="com.intellij">
</extensions>
<actions>
<group id="add_annotation_group" text="Add Self Annotation" popup="true">
<!-- EditorPopupMenu是文件中右鍵會顯示的菜單 -->
<add-to-group group-id="EditorPopupMenu" anchor="last"/>
<action id="plugin.demoAction" class="com.huiluczp.checkannotationplugin.AnnotationAdditionAction" text="@WhereConditionCheck"
description="com.huiluczP.annotation.WhereConditionCheck">
</action>
</group>
</actions>
</idea-plugin>
對插件功能實現(xiàn)來說,主要需要關(guān)注的是actions部分,其中,設(shè)置了一個名為add_annotation_group的菜單組,在這個標(biāo)簽中,使用add-to-group標(biāo)簽將其插入EditorPopupMenu
中,也就是右鍵展開菜單。最后,在我們定義的菜單組中,增加一個action,也就是點擊后會進行對應(yīng)功能處理的單元,在class中設(shè)置具體的實現(xiàn)類,并用text設(shè)置需要顯示的信息。
功能類實現(xiàn)
將所有功能都塞到了AnnotationAdditionAction類中。
public class AnnotationAdditionAction extends AnAction {
private Project project;
private Editor editor;
private String annotationStr;
private AnActionEvent event;
private String fullAnnotationStr;
@Override
// 主方法,增加對應(yīng)的注解信息
public void actionPerformed(AnActionEvent event) {
project = event.getData(PlatformDataKeys.PROJECT);
editor = event.getRequiredData(CommonDataKeys.EDITOR);
// 獲取注解名稱
annotationStr = event.getPresentation().getText();
fullAnnotationStr = event.getPresentation().getDescription();
// 獲取
// 獲取所有類
PsiClass[] psiClasses = getAllClasses(event);
// 對類中所有滿足條件的類增加Annotation
for(PsiClass psiClass:psiClasses){
// 滿足條件
List<String> methodNames = new ArrayList<>();
if(checkMapperInterface(psiClass)) {
PsiMethod[] psiMethods = psiClass.getMethods();
for (PsiMethod psiMethod : psiMethods) {
PsiAnnotation[] psiAnnotations = psiMethod.getAnnotations();
boolean isExist = false;
System.out.println(psiMethod.getName());
for (PsiAnnotation psiAnnotation : psiAnnotations) {
// 注解已存在
if (psiAnnotation.getText().equals(annotationStr)){
isExist = true;
break;
}
}
// 不存在,增加信息
if(!isExist){
System.out.println("add annotation "+annotationStr + ", method:" + psiMethod.getName());
methodNames.add(psiMethod.getName());
}
}
}
// 創(chuàng)建線程進行編輯器內(nèi)容的修改
// todo 考慮同名,還需要考慮對方法的參數(shù)判斷,有空再說吧
WriteCommandAction.runWriteCommandAction(project, new TextChangeRunnable(methodNames, event));
}
}
實現(xiàn)類需要繼承AnAction
抽象類,并通過actionPerformed
方法來執(zhí)行具體的操作邏輯。通過event
對象,可以獲取idea定義的project項目信息和editor當(dāng)前編輯窗口的信息。通過獲取當(dāng)前窗口的類信息,并編輯對應(yīng)文本,最終實現(xiàn)對所有滿足條件的方法增加自定義注解的功能。
// 獲取對應(yīng)的method 并插入字符串
class TextChangeRunnable implements Runnable{
private final List<String> methodNames;
private final AnActionEvent event;
public TextChangeRunnable(List<String> methodNames, AnActionEvent event) {
this.methodNames = methodNames;
this.event = event;
}
@Override
public void run() {
String textNow = editor.getDocument().getText();
StringBuilder result = new StringBuilder();
// 考慮import,不存在則增加import信息
PsiImportList psiImportList = getImportList(event);
if(!psiImportList.getText().contains(fullAnnotationStr)){
result.append("import ").append(fullAnnotationStr).append(";\n");
}
// 對所有的方法進行定位,增加注解
// 粗暴一點,直接找到public的位置,前面增加注解+\n
String[] strList = textNow.split("\n");
for(String s:strList){
boolean has = false;
for(String methodName:methodNames) {
if (s.contains(methodName)){
has = true;
break;
}
}
if(has){
// 獲取當(dāng)前行的縮進
int offSet = calculateBlank(s);
result.append(" ".repeat(Math.max(0, offSet)));
result.append(annotationStr).append("\n");
}
result.append(s).append("\n");
}
editor.getDocument().setText(result);
}
// 找到字符串第一個非空字符前空格數(shù)量
private int calculateBlank(String str){
int length = str.length();
int index = 0;
while(index < length && str.charAt(index) == ' '){
index ++;
}
if(index >= length)
return -1;
return index;
}
}
需要注意的是,在插件中對文本進行編輯,需要新建線程進行處理。TextChangeRunnable
線程類對當(dāng)前編輯的每一行進行分析,保留對應(yīng)的縮進信息并增加public方法的自定義注解修飾。同時,判斷import包信息,增加對應(yīng)注解的import。
@Override
// 當(dāng)文件為接口,且名稱中包含Mapper信息時,才顯示對應(yīng)的右鍵菜單
public void update(@NotNull AnActionEvent event) {
super.update(event);
Presentation presentation = event.getPresentation();
PsiFile psiFile = event.getData(PlatformDataKeys.PSI_FILE);
presentation.setEnabledAndVisible(false); // 默認不可用
if(psiFile != null){
VirtualFile virtualFile = psiFile.getVirtualFile();
FileType fileType = virtualFile.getFileType();
// 首先滿足為JAVA文件
if(fileType.getName().equals("JAVA")){
// 獲取當(dāng)前文件中的所有類信息
PsiClass[] psiClasses = getAllClasses(event);
// 只允許存在一個接口類
if(psiClasses.length!=1)
return;
for(PsiClass psiClass:psiClasses){
// 其中包含Mapper接口即可
boolean isOk = checkMapperInterface(psiClass);
if(isOk){
presentation.setEnabledAndVisible(true);
break;
}
}
}
}
}
重寫update方法,當(dāng)前右鍵菜單顯示時,判斷是否為接口名帶Mapper的情況,若不是則進行自定義注解增加功能的隱藏。
// 獲取當(dāng)前文件中所有類
private PsiClass[] getAllClasses(AnActionEvent event){
PsiFile psiFile = event.getData(PlatformDataKeys.PSI_FILE);
assert psiFile != null;
FileASTNode node = psiFile.getNode();
PsiElement psi = node.getPsi();
PsiJavaFile pp = (PsiJavaFile) psi;
return pp.getClasses();
}
// 獲取所有import信息
private PsiImportList getImportList(AnActionEvent event){
PsiFile psiFile = event.getData(PlatformDataKeys.PSI_FILE);
assert psiFile != null;
FileASTNode node = psiFile.getNode();
PsiElement psi = node.getPsi();
PsiJavaFile pp = (PsiJavaFile) psi;
return pp.getImportList();
}
// 判斷是否為名稱Mapper結(jié)尾的接口
private boolean checkMapperInterface(PsiClass psiClass){
if(psiClass == null)
return false;
if(!psiClass.isInterface())
return false;
String name = psiClass.getName();
if(name == null)
return false;
return name.endsWith("Mapper");
}
最后是幾個工具方法,通過psiFile來獲取對應(yīng)的psiJavaFile,從而得到對應(yīng)的類信息。
插件打包
因為使用了gradle,直接使用gradle命令進行打包。
gradlew build
之后會自動執(zhí)行完整的編譯和打包流程,最終會在/build/distributions文件夾下生成對應(yīng)的jar文件。
之后,在idea的settings中搜索plugins,點擊配置中的本地install選項,即可選擇并加載對應(yīng)的插件jar。
效果展示
創(chuàng)建一個簡單的UserMapper類。
public interface UserMapper {
public String queryG();
public String queryKKP();
}
在編輯頁面上右鍵顯示菜單,點擊我們之前設(shè)置的新按鈕增加自定義注解信息,增加成功。
文章來源:http://www.zghlxwxcb.cn/news/detail-816882.html
總結(jié)
這次主要是記錄了下簡單的idea插件開發(fā)過程,idea的sdk以編輯頁面為基礎(chǔ)提供了PSI api來對當(dāng)前頁面與整體項目的展示進行修改,還是挺方便的。配置文件對action展示的位置進行編輯,感覺和傳統(tǒng)的gui開發(fā)差不多。
對現(xiàn)在這個插件,感覺還可以拓展一下編輯界面,輸進其他想增加的注解類型和展示邏輯,有空再拓展吧。文章來源地址http://www.zghlxwxcb.cn/news/detail-816882.html
到了這里,關(guān)于自定義注解與攔截器實現(xiàn)不規(guī)范sql攔截(自定義注解填充插件篇)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!