不是大牛,也能上手组件化(附录1:JavaPoet 的使用)

568 阅读3分钟

这是我参与更文挑战的第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!");
  }
}

我们来看一下这段代码,其实可以分为三个部分,包、类、方法。

示例1

而 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 记录查看