编写一个IDEA插件之:使用PSI分析Java代码
PSI
是Program Structure Interface
的缩写,即程序结构接口。
如果我们想要分析源代码文件的内容就离不开PSI
。
我们知道,JVM
在加载类之前,首先需要读取Class
文件,并将Class
文件解析成一个结构体对象,对应的是Class
文件结构。与JVM
解析Class
文件不同的是,IDEA
解析的是Java
源代码,但IDEA
也是将Java
文件解析为一个结构体对象。
请记住一句话,对于任何拥有固定结构的文件或者代码,都可以使用访问者模式。
不仅Java
文件,任何代码文件都会有一定的结构,否则编译器也不能识别,也是因为如此,IDEA
实现的PSI
与Java
字节码操作工具ASM
有非常多的相似之处,除了都是将文件解析成结构外,也都支持使用访问者模式编辑文件,一个大的结构下面包含许多小的结构,小的结构也支持使用访问者模式编辑。
因为很相似,所以我们可以用学习使用ASM
工具分析、创建、或改写Class
文件的思维去学习PSI
。
由于不同的编程语言编写的代码文件有不同的结构,IDEA
将文件结构抽象为接口,叫程序结构接口文件(PSI File
),不同类型的文件解析后生成不同的PsiFile
接口的实现类实例,这也是IDEA
能够扩展支持多语言的基础。
PsiFile
接口
一个文件就是一个PsiFile
,也是一个文件的结构树的根节点,PsiFile
是一个接口,如果文件是一个.java
文件,那么解析生成的PsiFile
就是PsiJavaFile
对象,如果是一个Xml
文件,则解析后生成的是XmlFile
对象。
PsiElement
接口
Class
文件结构包含字段表、属性表、方法表等,每个字段、方法也都有属性表,但在PSI
中,总体上只有PsiFile
和PsiElement
。
Element
即元素,一个PsiFile
(本身也是PsiElement
)由许多的PsiElement
构成,每个PsiElement
也可以由许多的PsiElement
构成。
PsiElement
用于描述源代码的内部结构,不同的结构对应不同的实现类。
对应Java
文件的PsiElement
种类有:PsiClass
、PsiField
、PsiMethod
、PsiCodeBlock
、PsiStatement
、PsiMethodCallExpression
等等。其中,PsiField
、PsiMethod
都是PsiClass
的子元素,PsiCodeBlock
是PsiMethod
的子元素,PsiMethodCallExpression
是PsiCodeBlock
的子元素,正是这种关系构造成了一棵树。
解析一个Java
文件有上百种类型的PsiElement
,对于一个新手,我们如何才能快速的认识对应Java
代码文件中的每行代码都会解析生成呢?好在IDEA
提供了PSI
视图查看器。
如果你正在编写插件,那么IDEA
会自动在“工具”菜单中显示“查看PSI
结构”的选项,否则,我们需要修改IDEA
的配置文件才能在“工具”菜单中看到这个选项。
配置文件在IDEA
安装路径的bin
目录下,找到idea.properties
文件,如下图所示。
我们需要在idea.properties
文件中添加这样一行配置:
idea.is.internal=true
添加配置后重启IDEA
就能看到tools
菜单下新加了两个选择,如下图所示。
其中View PSI Structure of Current File
是将当前查看的文件解析为结构树,选中选项后弹出如下图所示的窗口。
-
Show PSI structure for
:选择PsiFile
类型; -
Show PsiWhiteSpace
:去掉勾选后可以隐藏表示连续空格(包括换行符)的元素PsiElement
;
当我们选中源码时,IDEA
会找到对应的PsiElement
标志为选中状态,如上图左侧的PSI Tree
窗口所示。
PsiReference
一个PsiReference
表示代码中某个PsiElement
链接到相应的声明。
简单理解,PsiReference
就是我们选中鼠标右键弹出菜单中Go To
的Declaration or Usages
、或者按住command
键+鼠标点击后能够跳转到相应声明的依据。
我们可以通过调用PsiElement#getReference
方法获取一个PsiElement
的PsiReference
,然后调用PsiReference#resolve
方法取得该PsiElement
链接到(引用)的PsiElement
。
例如,获取一个方法调用表达式PsiMethodCallExpression
链接到声明的PsiElement
可以这样写。
下面是这段代码的一次调试的截图:
如上图所示,此次PsiMethodCallExpression
表示的是payConfigApplicationService.createOrUpdate(dto)
,PsiMethodCallExpression
也是一个PsiElement
,可以调用getReference
获取到该元素的PsiReference
实例,最后调用PsiReference
实例的resolve
方法取得该方法调用表达式元素链接到的声明是一个PsiMethod
,表示createOrUpdate
方法。
我们还可以继续获取该表达式链接到的PsiMethod
所属的类PsiClass
。
通过分析一个元素的PsiReference
,我们可以判断一行代码是否有调用某个类的方法,如果有,则在代码行号处显示一个图标,点击图标跳转到目标方法等。
总之,要想在自定义插件中分析源代码就不得不了解PSI
。
后记
笔者是通过阅读官方文档、通过PSI
查看器学习了解PSI
、并通过分析MybatisX
这个插件的源码,以及自己动手不断试错学习如何编写一个IDEA
插件的,这与笔者以前学习ASM
操作字节码一样,都是瞎折腾,但不畏惧困难。
[Java艺术] 微信号:javaskill
一个只推送原创文章的技术公众号,分享Java后端相关技术。
- 如何在Kudu1.5中使用Sentry授权
- 深度学习入门实战
- 如何使用Zookeeper实现HiveServer2的HA
- Python 统计个人加密货币资产
- 如何使用HAProxy实现HiveServer2负载均衡
- 机器学习之决策树算法
- 如何使用HAProxy实现Impala的负载均衡
- 解读GEO数据存放规律及下载,一文就够
- 从GEO数据库下载得到表达矩阵 一文就够
- 机器学习各类算法比较
- Kerberos环境下删除ZooKeeper服务注册信息问题分析
- 根据分组信息做差异分析- 这个一文不够的
- 如何使用Cloudera Manager启用YARN的HA
- 如何使用java代码通过JDBC连接Hive(附github源码)
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- Android之自定义实现BaseAdapter(通用适配器三)
- Android实现音乐播放器锁屏页
- android studio3.3.1代码提示忽略大小写的设置
- Ascgen2可以把图片变成文字的小工具
- 解决android studio卡顿,提升studio运行速度的方法
- Android物理键盘事件解析
- AndroidQ(10)分区存储完美适配方法
- (全局快捷键工具)Power Keys彻底提升码字效率?
- android自定义view实现钟表效果
- 教你如何用OpenVZ限制虚拟机硬盘IO速度
- Android自定义控件实现短信验证码自动填充
- android studio 3.6.1升级后如何处理 flutter问题
- Android项目迁移到AndroidX的方法步骤
- Android中layer-list基本使用详解
- Android Studio中主题样式的使用方法详解