字节码插桩(四): AST
我们通过 AndroidStudio 生成Bean对象一般是通过注解来实现自动生成getter/setter方法、equals()和hashCode()方法,其中类(或接口)要符合驼式命名法,首字母大写。方法要符合驼式命名法,首字母小写,类或实例变量要符合驼式命名法,首字母小写。常量要求全部由大写字母或下划线构成,且第一个字符不能是下划线,否则编译器会报警告
那么: 编译器是怎么解析到这些不规范的命名方式呢?这里不得不提到一个很重要的字节码插桩技术AST,什么是AST?
一. AST概念
AST 是 Abstract Syntax Tree 的缩写,即 “抽象语法树”, 是编译器对代码第一步工作加工后的结果,是一个树形表示的源代码。源代码的每一个元素映射到一个节点或子节树
二. Java编译过程
了解AST之前要了解整个Java编译过程:大概分为如下三个阶段
2.1 第一阶段
所有源文件会被解析成语法树
2.2 第二阶段
调用注解处理器,即APT模块。如果注解处理器产生了新的源文件,新的源文件也要参与编译
2.3 第三阶段
语法树会被分析转换成类文件
三. AST原理
编译器对代码处理流程大概是
javaTXT -> 词语法分析 -> 生成AST -> 语义分析 -> 编译字节码
用过操作AST,可以达到修改源代码功能
四. 代码实现层面 APT + AST
- 4.1 通过 AnnotationProcessor 的 process 方法, 通过拿到所有 Elements 对象
- 4.2 自定义 TreeTranslator,在 visitMethodDef 可对方法进行判断
- 4.3 如果是目标方法,通过 AST 框架有关 API 插入代码
五. AST缺陷
- 5.1 不支持Lambda
- 5.2 APT无法扫描其他moudle,AST无法处理其他moudle
六. AST 运用场景: 代码规范检查
- 6.1 对象调用的非空判断
- 6.2 编写我们特定的语法规则,对不符合规则的代码进行修改或优化
- 6.3 增删改查
七. AST优点
AST操作属于编译器级别,对程序运行完全没有影响,效率相对其他AOP更高
八. AST常见API
- 抽象内部类,内部定义了访问各种语法节点的方法,获取到对应的语法节点后我们可以对语法节点增加删除或者修改语句;
- Visitor派生子类有TreeScanner(扫描所有的语法节点)和 TreeTranslator(扫描节点且可以把语法节点转换成另一种语法节点)
九. Android 中 AST的 运用
Android Lint是Google提供给Android开发者的静态代码检查工具。内部基于已经帮我们AST封装了一层,使用Lint对Android工程代码进行扫描和检查,可以发现代码潜在的问题,提醒程序员及早修正。这样代码看起来就没那么难受,今天我就带大家打造一款自己企业项目的Lint工具吧
十. 开发步骤
需求点:
- 日志输出
禁止 Log 和 System.out日志输出,防止部分隐私数据泄露
- Toast
禁止直接使用系统Toast,保证样式以及低版本room上面的奔溃概率
- 文件命名检测
资源文件(layout、drawble、anim、color、dime、style、string)的命名必须以module打头
- Thread检测
避免自己新建线程
- 序列化检测
10.1 创建Java工程,配置Gradle
apply plugin: 'java-library'dependencies {implementation fileTree(dir: 'libs', include: ['*.jar'])// lint-api: 官方给出的API,API并不是最终版,官方提醒随时有可能会更改API接口compileOnly 'com.android.tools.lint:lint-api:27.1.0'// lint-checks:已有的检查compileOnly 'com.android.tools.lint:lint-checks:27.1.0'
}tasks.withType(JavaCompile) {options.encoding = "UTF-8"
}sourceCompatibility = "8"
targetCompatibility = "8"
10.2 创建Detector
Detector负责扫描代码,发现问题并报告
10.2.1 Id类型检查
/*** Created by 杨正友 on 2020-01-10* Id类型检查:数字类型的id只能被定义为long,不能被定义为int、char或者short** @since 1.0*/
public class IdCheckDetector extends Detector implements Detector.UastScanner {private static final String REGEX = ".*id";public static final Issue ISSUE = Issue.create("IdDefinedError","Id of number type should be defined long not int.","Please change the Id to long!",Category.CORRECTNESS, 9, Severity.ERROR,new Implementation(IdCheckDetector.class, Scope.JAVA_FILE_SCOPE));@Nullable@Overridepublic List<Class<? extends UElement>> getApplicableUastTypes() {return Arrays.asList(UClass.class);}@Nullable@Overridepublic UElementHandler createUastHandler(@NotNull JavaContext context) {return new UElementHandler() {@Overridepublic void visitClass(@NotNull UClass node) {// 当前类检查check(node, context);UClass[] innerClasses = node.getInnerClasses();for (UClass uClass : innerClasses) {// 内部类检查check(uClass, context);}super.visitClass(node);}};}private void check(@NotNull UClass node, @NotNull JavaContext context) {for (UField field : node.getFields()) {String name = field.getName();if (name != null && name.toLowerCase().matches(REGEX)&& (field.getType() == PsiType.INT || field.getType() == PsiType.CHAR || field.getType() == PsiType.SHORT)) {context.report(ISSUE, context.getLocation(field), "Id of number type should be defined long not int.");}}}
}
10.2.2 message.obtain()检查
public class MessageObtainDetector extends Detector implements Detector.UastScanner {private static final Class<? extends Detector> DETECTOR_CLASS = MessageObtainDetector.class;private static final EnumSet<Scope> DETECTOR_SCOPE = Scope.JAVA_FILE_SCOPE;public static final Issue ISSUE = Issue.create("MessageObtainUseError","不建议直接new Message()","建议调用{handler.obtainMessage} or {Message.Obtain()}获取缓存的message",Category.PERFORMANCE,9,Severity.WARNING,new Implementation(DETECTOR_CLASS, DETECTOR_SCOPE));@Nullable@Overridepublic List<String> getApplicableConstructorTypes() {return Collections.singletonList("android.os.Message");}@Overridepublic void visitConstructor(JavaContext context, UCallExpression node, PsiMethod constructor) {context.report(ISSUE, node, context.getLocation(node), "建议调用{handler.obtainMessage} or {Message.Obtain()}获取缓存的message");}
}
10.2.3 避免自己创建Thread
public class MkThreadDetector extends Detector implements Detector.UastScanner {public static final Issue ISSUE = Issue.create("新建Thread","避免自己创建Thread","请勿直接调用new Thread(),建议使用统一的MkThreadManager",Category.PERFORMANCE, 5, Severity.ERROR,new Implementation(NewThreadDetector.class, Scope.JAVA_FILE_SCOPE));@Overridepublic List<String> getApplicableConstructorTypes() {return Collections.singletonList("java.lang.Thread");}@Overridepublic void visitConstructor(JavaContext context, UCallExpression node, PsiMethod constructor) {context.report(ISSUE, node, context.getLocation(node), "禁止直接调用new Thread(),建议使用MkThreadManager");}
}
10.2.4 序列化内部类检查
public class MkSerializableDetector extends Detector implements Detector.UastScanner {private static final String CLASS_SERIALIZABLE = "java.io.Serializable";public static final Issue ISSUE = Issue.create("InnerClassSerializable","内部类需要实现Serializable接口","内部类需要实现Serializable接口",Category.SECURITY, 5, Severity.ERROR,new Implementation(SerializableDetector.class, Scope.JAVA_FILE_SCOPE));@Nullable@Overridepublic List<String> applicableSuperClasses() {return Collections.singletonList(CLASS_SERIALIZABLE);}/*** 扫描到applicableSuperClasses()指定的list时,回调该方法*/@Overridepublic void visitClass(JavaContext context, UClass declaration) {if (declaration instanceof UAnonymousClass) {return;}sortClass(context, declaration);}private void sortClass(JavaContext context, UClass declaration) {for (UClass uClass : declaration.getInnerClasses()) {sortClass(context, uClass);// 判断是否继承了Serializable并提示boolean hasImpled = false;for (PsiClassType psiClassType : uClass.getImplementsListTypes()) {if (CLASS_SERIALIZABLE.equals(psiClassType.getCanonicalText())) {hasImpled = true;break;}}if (!hasImpled) {context.report(ISSUE,uClass.getNameIdentifier(),context.getLocation(uClass.getNameIdentifier()),String.format("内部类 `%1$s` 需要实现Serializable接口", uClass.getName()));}}}
}
10.2.5 禁用系统Log/System.out日志
public class MkLogDetector extends Detector implements Detector.UastScanner {private static final String SYSTEM_SERIALIZABLE = "System.out.println";public static final Issue ISSUE = Issue.create("LogUse","禁止使用Log/System.out.println","推荐MKLog,防止在正式包打印log",Category.SECURITY, 5, Severity.ERROR,new Implementation(LogDetector.class, Scope.JAVA_FILE_SCOPE));@Nullable@Overridepublic List<String> getApplicableConstructorTypes() {return Collections.singletonList(SYSTEM_SERIALIZABLE);}@Overridepublic List<String> getApplicableMethodNames() {//要检测的方法名return Arrays.asList("v", "d", "i", "w", "e", "wtf");}@Overridepublic void visitConstructor(JavaContext context, UCallExpression node, PsiMethod constructor) {context.report(ISSUE, node, context.getLocation(node), "请使用MkLog,避免使用System.out.println");}}
10.3 创建Android Library工程 mk_lintrules
主要用来依赖lint.jar,打包成aar上传到maven,如果没有maven地址可以自己在
gradle中配置
dependencies {lintChecks project(':mk_lint')
}
10.4 IssueRegistry,提供需要被检测的Issue列表
/*** Created by 小木箱 on 2020-10-15** @since 1.0*/
public class IssuesRegister extends IssueRegistry {@NotNull@Overridepublic List<Issue> getIssues() {return new ArrayList<Issue>() {{add(NewThreadDetector.ISSUE);add(MessageObtainDetector.ISSUE);add(SerializableDetector.ISSUE);add(IdCheckDetector.ISSUE);add(LogDetector.ISSUE);}};}@Overridepublic int getApi() {return ApiKt.CURRENT_API;}@Overridepublic int getMinApi() {//兼容3.1return 1;}
}
在getIssues()方法中返回需要被检测的Issue List。
在build.grade中声明Lint-Registry属性
jar {manifest {attributes("Lint-Registry-v2": "com.github.microkibaco.mk_lint.IssuesRegister")}
}
自定义Lint的编码部分就完成了。
十一. Lint的优点
- 对于正式发布包来说,debug和verbose的日志会自动不显示。
- 拥有更多的有用信息,包括应用程序名字、日志的文件和行信息、时间戳、线程等。
- 由于使用了可变参数,禁用后日志的性能比Log高。因为最冗长的日志往往都是debug或verbose日志,这可以稍微提高一些性能。
- 可以覆盖日志的写入位置和格式。
字节码插桩(四): AST相关推荐
- 调研字节码插桩技术,用于系统监控设计和实现
作者:小傅哥 博客:https://bugstack.cn ❝ 沉淀.分享.成长,让自己和他人都能有所收获!???? ❞ 目录 一.来自深夜的电话! 二.准备工作 三.使用 AOP 做个切面监控 1. ...
- Android AOP之字节码插桩
背景 本篇文章基于<网易乐得无埋点数据收集SDK>总结而成,关于网易乐得无埋点数据采集SDK的功能介绍以及技术总结后续会有文章进行阐述,本篇单讲SDK中用到的Android端AOP的实 ...
- aop 获取方法入参出参_ASM字节码编程 | JavaAgent+ASM字节码插桩采集方法名称及入参和出参结果并记录方法耗时...
作者:小傅哥 博客:bugstack.cn ❝ 沉淀.分享.成长,让自己和他人都能有所收获! ❞ 一.前言 在我们实际的业务开发到上线的过程中,中间都会经过测试.那么怎么来保证测试质量呢?比如:提交了 ...
- 字节码插桩(javassist)之插入代码块|IOC框架(Hilt)之对象注入~研究
Hilt对象注入 | javassist插桩 研究 Hilt对象注入 javassist字节码插桩 创建buildSrc的module 重写Transform 熟悉TransformInvocatio ...
- ASM字节码编程 | JavaAgent+ASM字节码插桩采集方法名称以及入参和出参结果并记录方法耗时
作者:小傅哥 博客:bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 在我们实际的业务开发到上线的过程中,中间都会经过测试.那么怎么来保证测试质量呢?比如:提交了多少代码 ...
- Java ASM框架与字节码插桩的常见用法(生成类,修改类,方法插桩,方法注入)
前言 ASM 是一款读写Java字节码的工具,可以达到跳过源码编写,编译,直接以字节码的形式创建类,修改已经存在类(或者jar中的class)的属性,方法等. 通常用来开发一些Java开发的辅助框架, ...
- Android程序员的硬通货——ASM字节码插桩
作者:享学课堂Lance老师 转载请声明出处! 一.什么是插桩 QQ空间曾经发布的<热修复解决方案>中利用 Javaassist库实现向类的构造函数中插入一段代码解决 CLASS_ISPR ...
- 【字节码插桩】AOP 技术 ( “字节码插桩“ 技术简介 | AspectJ 插桩工具 | ASM 插桩工具 )
文章目录 一." 字节码插桩 " 技术简介 二.AspectJ 插桩工具 三.ASM 插桩工具 一." 字节码插桩 " 技术简介 性能优化 , 插件化 , 热修 ...
- 【字节码插桩】Android 打包流程 | Android 中的字节码操作方式 | AOP 面向切面编程 | APT 编译时技术
文章目录 一.Android 中的 Java 源码打包流程 1.Java 源码打包流程 2.字符串常量池 二.Android 中的字节码操作方式 一.Android 中的 Java 源码打包流程 Ja ...
最新文章
- php测试网络通不通函数,PHP检查库或函数是否可用的方法
- 2、JDBC连接数据库
- http 文件服务器 性能测试,Http File Server
- 860. 柠檬水找零 golang
- [html] 你知道什么是粘性布局吗?
- Linux puppet的安装配置部署
- 《并行计算的编程模型》一3.5 远程内存访问:put和get
- python约瑟夫环算法和流程图_约瑟夫环问题及python与c++实现效率对比
- 小峰servlet/jsp(4)EL表达式
- 数据决策力是驱动企业发展的原动力
- [Apio2012]dispatching 左偏树
- 动态硬盘转基本盘转换器
- 【Unity】ET框架学习笔记
- 官方免费数据下载全国行政区划具体到村
- AngularJs的UI组件ui-Bootstrap分享(四)——Datepicker Popup
- 天价球鞋事件中,得物在扮演哪种身份?
- oracle对某两列求和再求和_分手后4个阶段这样挽回,再狠的他也会心软求和
- 细看10个不同类型的社交平台
- [GKCTF2020]EZ三剑客-EzWeb
- 统计学入门基础概念问答(统计学方法的分类、统计数据的分类等)
热门文章
- 人一旦开窍后,会产生哪些改变?
- IntelliJ IDEA 2021下载、安装
- SSH远程连接Ubuntu【局域网】
- Cisco Packet Tracer实验————组建虚拟局域网
- ADI Blackfin DSP处理器-BF533的开发详解8:Timer定时器的驱动和应用(含源代码)
- 计算机交互式登录进程初始化失败,Windows 7开机提示“交互式登录进程初始化失败”的解决方法...
- 如何将SOCKS5代理转换成HTTP代理?
- 常用工具箱:打开手机日志界面
- 有钱任性把冰山搬回家,真有富豪计划这样干!
- 浅析一种电动汽车充电桩集中监控平台的设计与实现