在之前的“Java中的屠龙之术:如何修改语法树”中,我们详细介绍了如何使用Javac源码提供的工具类来修改语法树。

而在此基础上,有一款开源工具javapoet可以更加快捷地生成字节码,实现原理其实也就是对JavaAPT的封装,然而Javapoet有一个局限性,就是只能生成新的.class文件,却无法修改原有的类,这也是它的一大局限性所在。接下来就让我们看看它的使用方法把。

0x00 概述

注解系列

  • 注解基础
  • JavaPoet
  • 编译期注解处理之APT

上一篇限于篇幅只介绍了APT,这篇来继续介绍javapoet,是square公司的开源库。正如其名,java诗人,通过注解来生成java源文件,通常要使用javapoet这个库与Filer配合使用。主要和注解配合用来干掉那些重复的模板代码(如butterknife 和databinding所做的事情),当然你也可以使用这个技术让你的代码更加的炫酷。

0x01 简单使用

使用之前要先引入这个库

compile 'com.squareup:javapoet:1.7.0'
复制代码

javapoet是用来生成代码的,需要借助

常用类

使用javapoet前需要了解8个常用类

类名 作用
MethodSpec 代表一个构造函数或方法声明
TypeSpec 代表一个类,接口,或者枚举声明
FieldSpec 代表一个成员变量,一个字段声明
JavaFile 包含一个顶级类的Java文件
ParameterSpec 用来创建参数
AnnotationSpec 用来创建注解
ClassName 用来包装一个类
TypeName 类型,如在添加返回值类型是使用 TypeName.VOID

除此之外 JavaPoet提供了一套自定义的字符串格式化规则,常用的有

格式化规则 表示含义
$L 字面量
$S 字符串
$T 类、接口
$N 变量

0x02 使用进阶

下面由浅入深,循序渐进的说明用法

方法&控制流:

  • 添加方法 addcodeaddstatement 对与无需类引入的极简代码可以直接使用addCode
MethodSpec main = MethodSpec.methodBuilder("main").addCode(""+ "int total = 0;\n"+ "for (int i = 0; i < 10; i++) {\n"+ "  total += i;\n"+ "}\n").build();
复制代码

生成的是

void main() {int total = 0;for (int i = 0; i < 10; i++) {total += i;}
}
复制代码

要是需要import的方法,如上面的.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!") 就需要使用.addStatement来声明

  • 更优雅的流控制

beginControlFlow 流开启 addStatement 处理语句 endControlFlow()流结束

如上面的用流改写就是

MethodSpec main = MethodSpec.methodBuilder("main").addStatement("int total = 0").beginControlFlow("for (int i = 0; i < 10; i++)").addStatement("total += i").endControlFlow().build();
复制代码

占位符

javapoet里面提供了占位符来帮助我们更好地生成代码

  • $L 字面常量(Literals)
private MethodSpec computeRange(String name, int from, int to, String op) {return MethodSpec.methodBuilder(name).returns(int.class).addStatement("int result = 0").beginControlFlow("for (int i = $L; i < $L; i++)", from, to).addStatement("result = result $L i", op).endControlFlow().addStatement("return result").build();
}
复制代码

这个就是一个for循环,op负责加减乘除等符号

  • $S 字符串常量(String)
  • $T 类型(Types)

最大的特点是自动导入包

MethodSpec today = MethodSpec.methodBuilder("today").returns(Date.class).addStatement("return new $T()", Date.class).build();TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld").addModifiers(Modifier.PUBLIC, Modifier.FINAL).addMethod(today).build();JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld).build();javaFile.writeTo(System.out);复制代码

生成的代码如下,而且会自动导包

package com.example.helloworld;import java.util.Date;public final class HelloWorld {Date today() {return new Date();}
}
复制代码
  • $N 命名(Names),通常指我们自己生成的方法名或者变量名等等

比如这样的代码块

public String byteToHex(int b) {char[] result = new char[2];result[0] = hexDigit((b >>> 4) & 0xf);result[1] = hexDigit(b & 0xf);return new String(result);
}public char hexDigit(int i) {return (char) (i < 10 ? i + '0' : i - 10 + 'a');
}
复制代码

我们可以传递hexDigit()来代替。

MethodSpec hexDigit = MethodSpec.methodBuilder("hexDigit").addParameter(int.class, "i").returns(char.class).addStatement("return (char) (i < 10 ? i + '0' : i - 10 + 'a')").build();MethodSpec byteToHex = MethodSpec.methodBuilder("byteToHex").addParameter(int.class, "b").returns(String.class).addStatement("char[] result = new char[2]").addStatement("result[0] = $N((b >>> 4) & 0xf)", hexDigit).addStatement("result[1] = $N(b & 0xf)", hexDigit).addStatement("return new String(result)").build();
复制代码

获取对应类

有两种方式:

  • ClassName.bestGuess(“类全名称”) 返回ClassName对象,这里的类全名称表示的类必须要存在,会自动导入相应的包

  • ClassName.get(“包名”,”类名”) 返回ClassName对象,不检查该类是否存在

    因此如果使用JavaPoet的话后续代码重构改变类名往往需要格外注意一点

ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard");
ClassName list = ClassName.get("java.util", "List");
ClassName arrayList = ClassName.get("java.util", "ArrayList");
TypeName listOfHoverboards = ParameterizedTypeName.get(list, hoverboard);MethodSpec beyond = MethodSpec.methodBuilder("beyond").returns(listOfHoverboards).addStatement("$T result = new $T<>()", listOfHoverboards, arrayList).addStatement("result.add(new $T())", hoverboard).addStatement("result.add(new $T())", hoverboard).addStatement("result.add(new $T())", hoverboard).addStatement("return result").build();
复制代码

然后生成

package com.example.helloworld;import com.mattel.Hoverboard;
import java.util.ArrayList;
import java.util.List;public final class HelloWorld {List<Hoverboard> beyond() {List<Hoverboard> result = new ArrayList<>();result.add(new Hoverboard());result.add(new Hoverboard());result.add(new Hoverboard());return result;}
}
复制代码

构建类的元素

  • 方法

方法的修饰,如Modifiers.ABSTRACT

MethodSpec flux = MethodSpec.methodBuilder("flux").addModifiers(Modifier.ABSTRACT, Modifier.PROTECTED).build();TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld").addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT).addMethod(flux).build();
复制代码

这将会生成如下代码

public abstract class HelloWorld {protected abstract void flux();
}复制代码

当然Methods需要和MethodSpec.Builder配置来增加方法参数、异常、javadoc、注解等。

  • 构造器

这个其实也是个函数方法而已,因此可以使用MethodSpec来生成构造器方法。比如:

MethodSpec flux = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC).addParameter(String.class, "greeting").addStatement("this.$N = $N", "greeting", "greeting").build();TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld").addModifiers(Modifier.PUBLIC).addField(String.class, "greeting", Modifier.PRIVATE, Modifier.FINAL).addMethod(flux).build();
复制代码

将会生成

public class HelloWorld {private final String greeting;public HelloWorld(String greeting) {this.greeting = greeting;}
}
复制代码
  • 参数(重要)

之前我们是通过addstatement直接设置参数,其实参数也有自己的一个专用类ParameterSpec,我们可以使用ParameterSpec.builder()来生成参数,然后MethodSpec的addParameter去使用,这样更加优雅。

ParameterSpec android = ParameterSpec.builder(String.class, "android").addModifiers(Modifier.FINAL).build();MethodSpec welcomeOverlords = MethodSpec.methodBuilder("welcomeOverlords").addParameter(android).addParameter(String.class, "robot", Modifier.FINAL).build();
复制代码

生成的代码

void welcomeOverlords(final String android, final String robot) {
}
复制代码

稍微复杂点的类型 比如泛型 、Map之类的,需要了解下JavaPoet定义的几种专门描述类型的类

常见的有

分类 生成的类型 JavaPoet 写法 也可以这么写 (等效的 Java 写法)
内置类型 int TypeName.INT int.class
数组类型 int[] ArrayTypeName.of(int.class) int[].class
需要引入包名的类型 java.io.File ClassName.get(“java.io”, “File”) java.io.File.class
参数化类型 (ParameterizedType List ParameterizedTypeName.get(List.class, String.class) -
类型变量 (WildcardType) 用于声明泛型 T TypeVariableName.get(“T”) -
通配符类型 ? extends String WildcardTypeName.subtypeOf(String.class) -
/**Build input type, format as :*Map<String, Class<? extends IRouteGroup>>*/ParameterizedTypeName inputMapTypeOfRoot = ParameterizedTypeName.get(ClassName.get(Map.class),ClassName.get(String.class),ParameterizedTypeName.get(ClassName.get(Class.class),WildcardTypeName.subtypeOf(ClassName.get(type_IRouteGroup))));/**Map<String, RouteMeta>*/ParameterizedTypeName inputMapTypeOfGroup = ParameterizedTypeName.get(ClassName.get(Map.class),ClassName.get(String.class),ClassName.get(RouteMeta.class));        /**Build input param name.*/ParameterSpec rootParamSpec = ParameterSpec.builder(inputMapTypeOfRoot, "routes").build();ParameterSpec groupParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "atlas").build();
复制代码

生成参数类型

public class ARouter$$Root$$app implements IRouteRoot {@Overridepublic void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {routes.put("service", ARouter$$Group$$service.class);routes.put("test", ARouter$$Group$$test.class);}
}public class ARouter$$Group$$service implements IRouteGroup {@Overridepublic void loadInto(Map<String, RouteMeta> atlas) {atlas.put("/service/hello", RouteMeta.build(RouteType.PROVIDER, HelloServiceImpl.class, "/service/hello", "service", null, -1, -2147483648));atlas.put("/service/json", RouteMeta.build(RouteType.PROVIDER, JsonServiceImpl.class, "/service/json", "service", null, -1, -2147483648));atlas.put("/service/single", RouteMeta.build(RouteType.PROVIDER, SingleService.class, "/service/single", "service", null, -1, -2147483648));}
}复制代码
  • 字段

可以使用FieldSpec去声明字段,然后加到Method中处理

FieldSpec android = FieldSpec.builder(String.class, "android").addModifiers(Modifier.PRIVATE, Modifier.FINAL).build();TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld").addModifiers(Modifier.PUBLIC).addField(android).addField(String.class, "robot", Modifier.PRIVATE, Modifier.FINAL).build();
复制代码

然后生成代码

public class HelloWorld {private final String android;private final String robot;
}
复制代码

通常Builder可以更加详细的创建字段的内容,比如javadoc、annotations或者初始化字段参数等,如:

FieldSpec android = FieldSpec.builder(String.class, "android").addModifiers(Modifier.PRIVATE, Modifier.FINAL).initializer("$S + $L", "Lollipop v.", 5.0d).build();
复制代码

对应生成的代码

private final String android = "Lollipop v." + 5.0;
复制代码
  • 接口

接口方法必须是PUBLIC ABSTRACT并且接口字段必须是PUBLIC STATIC FINAL ,使用TypeSpec.interfaceBuilder

如下

TypeSpec helloWorld = TypeSpec.interfaceBuilder("HelloWorld").addModifiers(Modifier.PUBLIC).addField(FieldSpec.builder(String.class, "ONLY_THING_THAT_IS_CONSTANT").addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).initializer("$S", "change").build()).addMethod(MethodSpec.methodBuilder("beep").addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT).build()).build();
复制代码

生成的代码如下

public interface HelloWorld {String ONLY_THING_THAT_IS_CONSTANT = "change";void beep();
}
复制代码
  • 继承父类 实现接口

接口代码

package com.test.javapoet;
public interface TestInterface<T> {void test(T testPara);
}
复制代码

父类代码

public class TestExtendesClass {}
复制代码

使用javapoet实现接口并且继承父类

final ClassName  InterfaceName = ClassName.get("com.test.javapoet","TestInterface");ClassName superinterface = ClassName.bestGuess("com.test.javapoet.TestClass");//ClassName superinterface = ClassName.get("com.test.javapoet","aa");TypeSpec.Builder spec = TypeSpec.classBuilder("TestImpl").addModifiers(Modifier.PUBLIC)// 添加接口,ParameterizedTypeName的参数1是接口,参数2是接口的泛型.addSuperinterface(ParameterizedTypeName.get(InterfaceName, superinterface)) //使用ClassName.bestGuess会自动导入包.superclass(ClassName.bestGuess("com.zs.javapoet.test.TestExtendesClass"));MethodSpec.Builder methodSpec = MethodSpec.methodBuilder("test").addAnnotation(Override.class).returns(TypeName.VOID).addParameter(superinterface, "testPara").addStatement("System.out.println(hello)" );TypeSpec typeSpec = spec.addMethod(methodSpec.build()).build();JavaFile file = JavaFile.builder("com.zs.javapoet", typeSpec).build();file.writeTo(System.out);复制代码

生成代码

package com.test.javapoet;import com.zs.javapoet.test.TestExtendesClass;import java.lang.Override;public class TestImpl extends TestExtendesClass implements TestInterface<TestClass> {@Overridevoid test(TestClass testPara) {System.out.println(hello);}}
复制代码
  • 枚举类型

使用TypeSpec.enumBuilder来创建,使用addEnumConstant来添加

TypeSpec helloWorld = TypeSpec.enumBuilder("Roshambo").addModifiers(Modifier.PUBLIC).addEnumConstant("ROCK").addEnumConstant("SCISSORS").addEnumConstant("PAPER").build();
复制代码

生成的代码

public enum Roshambo {ROCK,SCISSORS,PAPER
}
复制代码

更复杂的类型也可以支持,如重写、注解等

TypeSpec helloWorld = TypeSpec.enumBuilder("Roshambo").addModifiers(Modifier.PUBLIC).addEnumConstant("ROCK", TypeSpec.anonymousClassBuilder("$S", "fist").addMethod(MethodSpec.methodBuilder("toString").addAnnotation(Override.class).addModifiers(Modifier.PUBLIC).addStatement("return $S", "avalanche!").build()).build()).addEnumConstant("SCISSORS", TypeSpec.anonymousClassBuilder("$S", "peace").build()).addEnumConstant("PAPER", TypeSpec.anonymousClassBuilder("$S", "flat").build()).addField(String.class, "handsign", Modifier.PRIVATE, Modifier.FINAL).addMethod(MethodSpec.constructorBuilder().addParameter(String.class, "handsign").addStatement("this.$N = $N", "handsign", "handsign").build()).build();
复制代码

生成代码

public enum Roshambo {ROCK("fist") {@Overridepublic void toString() {return "avalanche!";}},SCISSORS("peace"),PAPER("flat");private final String handsign;Roshambo(String handsign) {this.handsign = handsign;}
}
复制代码
  • 匿名内部类

需要使用Type.anonymousInnerClass(""),通常可以使用$L占位符来指代

TypeSpec comparator = TypeSpec.anonymousClassBuilder("").addSuperinterface(ParameterizedTypeName.get(Comparator.class, String.class)).addMethod(MethodSpec.methodBuilder("compare").addAnnotation(Override.class).addModifiers(Modifier.PUBLIC).addParameter(String.class, "a").addParameter(String.class, "b").returns(int.class).addStatement("return $N.length() - $N.length()", "a", "b").build()).build();TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld").addMethod(MethodSpec.methodBuilder("sortByLength").addParameter(ParameterizedTypeName.get(List.class, String.class), "strings").addStatement("$T.sort($N, $L)", Collections.class, "strings", comparator).build()).build();
复制代码

生成代码

void sortByLength(List<String> strings) {Collections.sort(strings, new Comparator<String>() {@Overridepublic int compare(String a, String b) {return a.length() - b.length();}});
}
复制代码

定义匿名内部类的一个特别棘手的问题是参数的构造。在上面的代码中我们传递了不带参数的空字符串。TypeSpec.anonymousClassBuilder("")。

  • 注解

注解使用起来比较简单

MethodSpec toString = MethodSpec.methodBuilder("toString").addAnnotation(Override.class).returns(String.class).addModifiers(Modifier.PUBLIC).addStatement("return $S", "Hoverboard").build();
复制代码

生成代码

  @Overridepublic String toString() {return "Hoverboard";}
复制代码

通过AnnotationSpec.builder() 可以对注解设置属性:

MethodSpec logRecord = MethodSpec.methodBuilder("recordEvent").addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT).addAnnotation(AnnotationSpec.builder(Headers.class).addMember("accept", "$S", "application/json; charset=utf-8").addMember("userAgent", "$S", "Square Cash").build()).addParameter(LogRecord.class, "logRecord").returns(LogReceipt.class).build();
复制代码

代码生成如下

@Headers(accept = "application/json; charset=utf-8",userAgent = "Square Cash"
)
LogReceipt recordEvent(LogRecord logRecord);
复制代码

注解同样可以注解其他注解,通过$L引用如

MethodSpec logRecord = MethodSpec.methodBuilder("recordEvent").addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT).addAnnotation(AnnotationSpec.builder(HeaderList.class).addMember("value", "$L", AnnotationSpec.builder(Header.class).addMember("name", "$S", "Accept").addMember("value", "$S", "application/json; charset=utf-8").build()).addMember("value", "$L", AnnotationSpec.builder(Header.class).addMember("name", "$S", "User-Agent").addMember("value", "$S", "Square Cash").build()).build()).addParameter(LogRecord.class, "logRecord").returns(LogReceipt.class).build();
复制代码

生成代码

@HeaderList({@Header(name = "Accept", value = "application/json; charset=utf-8"),@Header(name = "User-Agent", value = "Square Cash")
})
LogReceipt recordEvent(LogRecord logRecord);
复制代码

注释

  • javadoc

0x03 后续

在javapoet之前有javawriter,但javapoet有着更强大的代码模型,并且对类的理解更加到位,因此推荐使用javapoet

参考文章

  • juejin.cn/post/684490…

  • cloud.tencent.com/developer/a…

  • blog.csdn.net/qq_26376637…

作者:渡口一艘船
链接:https://juejin.cn/post/6844903456629587976
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Java中的屠龙之术(二):如何方便快捷地生成.class文件相关推荐

  1. Java中如何实现QR(二维码)的生成

    二维码在我们的日常生活中的使用现在可谓是如火如荼.二维码,即一种新生代的信息存储介质.最早被使用在汽车生产厂,用于跟踪售后的汽车. 同样,在互联网时代,二维码也被广泛使用,随着二维码的迅速发展,一些手 ...

  2. Java 中的屠龙之术:如何修改语法树?

    作者:不学无数的程序员 来源:https://my.oschina.net/u/4030990/blog/3211858 在网上关于如何修改Java的抽象语法树的相关API文档并不多,于是本篇记录一下 ...

  3. 在 Java 中,如何批量读取本项目资源目录下的所有文件

    在 Java 中,如何批量读取本项目资源目录下的所有文件 读取资源目录下的指定文件 方法 1:使用 JDK 中原始 API 方法 2:借助 Spring 附录 将 InputStream 转化为 by ...

  4. si446使用记录(二):使用WDS3生成头文件

    目录 一.概述 二.使用默认配置生成头文件 三.逐项修改配置 3.1 根据注释修改 3.1.1 速率.带宽.频偏 3.1.2 基频 -- 设置868M 3.1.3 使能中断 3.2 具体指令修改 3. ...

  5. c++中string插入一个字符_Java内存管理-探索Java中字符串String(十二)

    做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 一.初识String类 首先JDK API的介绍: public final class String extends O ...

  6. Java中的一维数组和二维数组

    Java中的数组 常用的就是一维数组.二维数组. 一维数组 数组定义:数据就是相同数据元素的集合. 数组是一种引用数据类型.不属于基本数据类型. (1) 如何定义(声明)一维数组? 声明格式:数据类型 ...

  7. Java 中的一维数组以及二维数组(8千字零基础超全详解)

    文章目录 前言 一.一维数组 1. 数组的初始化 1.1 动态初始化一 1.2 动态初始化二 1.3 静态初始化 2. 数组使用的注意事项和细节 3. 经典例题 4. 数组赋值机制(重难点!) 5. ...

  8. Java中的数组方法and二维数组

    今日份鼓励:如果你想中一颗树,那么它最好的栽种时间,一个是十年前,一个是现在!! 在这里因为博主的知识能力有限,我将会使用自己能够使用并且解释清楚的方法来完成这篇博客 目录 一.数组的拷贝 二.数组的 ...

  9. Java中如何读写cookie (二)

    Java中删除cookie Cookie[]   cookies=request.getCookies();        //cookies不为空,则清除        if(cookies!=nu ...

最新文章

  1. 讨论下 Java 流文件读写缓存大小设置的问题
  2. 改变Jupyter的默认项目路径
  3. 配置tomcat6.0的HTTPS(单向)
  4. linux的nfs端口号,#Linux NFS服务 固定端口及防火墙配置#
  5. LeetCode Week 4:第 31 ~ 40 题
  6. 蓝桥杯——黑色星期五
  7. Java基础知识拾遗--IO篇
  8. CUBRID学习笔记 21 查看主键外键索引
  9. 生产环境 xfs filesystem 上安装Mariadb
  10. 一步步教你写一份优秀的软件测试简历(带样例)
  11. 解耦电容选型定性与定量分析
  12. IIS 发布网站无法显示CSS、背景及图片文件---另一个思路--终极方案
  13. Android安卓-UI自动打卡Appium+PO+Pytest(1)
  14. 转: 学习开源项目的若干建议(infoq)
  15. FT5316调试记录
  16. spark+mongodb大数据框架搭建
  17. 计算机主机显卡安装,安装后如何在计算机中启用独立显卡
  18. 竞赛排名_纪中1743_模拟
  19. Gradle排除依赖模块的某个类
  20. 三个月考研英语复习法

热门文章

  1. XSKY全新一代SDS一体机五大场景之存储+灾备
  2. TinyWS —— 一个C++写的简易WEB服务器(三)
  3. 应届生,找java后端开发要什么水平才算合格?
  4. BZOJ 1875 [SDOI2009]HH去散步 矩阵乘法
  5. 区位码,国标码以及汉字内码的关系以及为什么是这样的关系
  6. Mozilla 正在火狐中集成谷歌翻译;内存产能增长减缓,基本告别降价
  7. 聚类算法实践(2)——谱聚类、Chameleon聚类
  8. springboot打包成jar包部署,无法访问到fastDFS配置文件【已解决】
  9. 关于移动端插件扩展:lsuperslide和l iscroll以及zy.media.js视频插件
  10. OpenCV——Canny直线检测