这是我参与更文挑战的第9天,活动详情查看: 更文挑战
前情提要
上一篇组件化相关文章中使用了 JavaPoet 来生成我们需要的 Java 代码,这篇博客就来介绍一些 JavaPoet 的用法。
这个框架也是来自于大名鼎鼎的 spuare 公司和 JakeWharton 之手。github 地址是:github.com/square/java… 因为这个框架引用了一些 java 的类,所以是不支持在 Android 环境中使用的,但是生成的代码是可以的。当然我们也是用来在编译时期生成代码的,正好完美结合。
探探路
话不多说,我们来上手使用,内容主要来源于框架的文档。
首先就是一个 HelloWrold 代码程序。
package com.example.helloworld;
public final class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, JavaPoet!");
}
}
我们来看一下这段代码,其实可以分为三个部分,包、类、方法。
而 JavaPoet 也就是采用了这种方法,通过不同的方法构建出方法、类、包,最后组合起来,形成 Java 内容,写入文件。
来看看我们写出来的生成代码。可以查看代码中的注释。
private static void testHelloWorld() {
// 生成 main 方法
MethodSpec main = MethodSpec.methodBuilder("main")
// 出参类型
.returns(void.class)
// 参数类型
.addParameter(String[].class, "args")
// 修饰符
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
// 具体的语句
.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
.build();
// 生成类
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
// 修饰符
.addModifiers(Modifier.FINAL, Modifier.PUBLIC)
// 添加方法
.addMethod(main)
.build();
// 生成包,设置包名和类
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
// 这里面没有生成到文件,而是直接打印出来了
System.out.println(javaFile.toString());
}
这里面都是一些固定的框架用法,多看多用就可以了。其中添加语句部分的语句中,需要注意,如果是需要添加引用的类,则应该用 $T 代替,字符串应该用 $S 代替。
实战一把
下面我们来写生成一个真实代码,代码是 ARouter 生成的。
package com.alibaba.android.arouter.routes;
import com.alibaba.android.arouter.facade.template.IRouteGroup;
import com.alibaba.android.arouter.facade.template.IRouteRoot;
import java.lang.Class;
import java.lang.Override;
import java.lang.String;
import java.util.Map;
/**
* DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Root$$main implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put("farmer", ARouter$$Group$$test.class);
}
}
这里比较麻烦的是参数的 map 和 map 的泛型。以及 map 的值 class 。其中 ARouter$$Group$$test.class 是其他类,我们为了演示直接新建一个空类就可以。
我们先看 Class<? extends IRouteGroup> 的写法,这里泛型相关的 JavaPoet 给我们提供了 ParameterizedTypeName 类和 WildcardTypeName 可以搞定这些麻烦。我们看一些用法。
// List<? extends String>
WildcardTypeName typeName = WildcardTypeName.subtypeOf(ClassName.get(String.class));
ParameterizedTypeName list2 = ParameterizedTypeName.get(
ClassName.get(List.class), typeName);
// Map<String,String>
ParameterizedTypeName map1 = ParameterizedTypeName.get(
ClassName.get(Map.class),
ClassName.get(String.class),
ClassName.get(String.class));
有了这些基础用法, 上面的示例写起来就简单了。
ClassName IRouteGroup =
ClassName.get("com.alibaba.android.arouter.facade.template", "IRouteGroup");
ClassName IRouteRoot =
ClassName.get("com.alibaba.android.arouter.facade.template", "IRouteRoot");
ClassName mapClazz = ClassName.get("java.util", "Map");
TypeName map = ParameterizedTypeName.get(mapClazz,
ClassName.get(String.class),
ParameterizedTypeName.get(ClassName.get(Class.class), WildcardTypeName.subtypeOf(IRouteGroup))
);
MethodSpec loadInto = MethodSpec.methodBuilder("loadInto")
.returns(void.class)
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.addParameter(map, "routes")
.addStatement("$N.put($S,$T.class)", "routes", "farmer", ClassName.get(ARouter$$Group$$test.class))
.build();
TypeSpec main = TypeSpec.classBuilder("ARouter$$Root$$demo")
.addModifiers(Modifier.PUBLIC)
.addSuperinterface(IRouteRoot)
.addMethod(loadInto)
.build();
JavaFile javaFile = JavaFile.builder("com.alibaba.android.arouter.routes", main)
.build();
System.out.println(javaFile.toString());
总结
到这里我们基本完成了 JavaPoet 的常用方法,当然还有很多写法我们没有去了解,这些都在文档里面,等我们需要的时候进行查阅就可以了。在平时开发学习过程中也是这样,接触到的好的框架和工具太多了,我们不可能每一个都弄清楚,这时候就应该有所取舍,学习一些基本的使用方法,开阔眼界就可以了。等到真正需要理解学习的时候再去深入研究。
对应的源码地址 github.com/xyz0z0/Comp… 可以参考对应的 commit 记录查看