暴力突破 Android 编译插桩(四)- APT 之 JavaPoet 使用

专栏暴力突破 Android 编译插桩系列

一、前言


JavaPoet 是 square 推出的开源 java 代码生成框架,提供 Java Api 生成 .java 源文件。这个框架功能非常有用,我们可以很方便的使用它根据注解、数据库模式、协议格式等来对应生成代码。通过这种自动化生成代码的方式,可以让我们用更加简洁优雅的方式要替代繁琐冗杂的重复工作。

 

二、使用解析


2.1、示例

为了展示 JavaPoet 的能力,这里以自动生成一个全新的 MainActivity 为例。

public class MainActivity extends Activity{
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

我在使用 JavaPoet 的时候,习惯从外向内逐一生成,但是这不是标准,这里可以按照自己的方式来理解和生成。示例如下:

    public static void main(String[] args) {
        ClassName activity = ClassName.get("android.app", "Activity");
        TypeSpec.Builder mainActivityBuilder = TypeSpec.classBuilder("MainActivity")
                .addModifiers(Modifier.PUBLIC)
                .superclass(activity);
        ClassName override = ClassName.get("java.lang", "Override");
        ClassName bundle = ClassName.get("android.os", "Bundle");
        ClassName nullable = ClassName.get("android.support.annotation", "Nullable");
        ParameterSpec savedInstanceState = ParameterSpec.builder(bundle, "savedInstanceState")
                .addAnnotation(nullable)
                .build();
        MethodSpec onCreate = MethodSpec.methodBuilder("onCreate")
                .addAnnotation(override)
                .addModifiers(Modifier.PROTECTED)
                .addParameter(savedInstanceState)
                .addStatement("super.onCreate(savedInstanceState)")
                .addStatement("setContentView(R.layout.activity_main)")
                .build();
        TypeSpec mainActivity = mainActivityBuilder.addMethod(onCreate)
                .build();
        JavaFile file = JavaFile.builder("com.test", mainActivity).build();
        try {
            file.writeTo(System.out);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

通过在 main() 方法中运行以上的代码就可以直接生成出 MainActivity 对象,自上而下的观察上述代码你会发现 JavaPoet 让 java 文件变得有逻辑性。

2.2、JavaPoet 的常用类

类名作用
TypeSpec用于生成类、接口、枚举对象的类
MethodSpec用于生成方法对象的类
ParameterSpec用于生成参数对象的类
AnnotationSpec用于生成注解对象的类
FieldSpec用于配置生成成员变量的类
ClassName通过包名和类名生成的对象,在JavaPoet中相当于为其指定Class
ParameterizedTypeName通过MainClass和IncludeClass生成包含泛型的Class
JavaFile控制生成的Java文件的输出的类

2.3、JavaPoet 的常用方法

设置修饰关键字

addModifiers(Modifier... modifiers)

addModifiers() 方法用来设置修饰关键字,可以传入多个 Modifier 值,Modifier 是一个枚举对象,枚举值为修饰关键字。 所有在 JavaPoet 创建的对象都必须设置修饰符(包括方法、类、接口、枚举、参数、变量)。下面我们看下 Modifier 的源码:

设置注解对象

addAnnotation(AnnotationSpec annotationSpec)
addAnnotation(ClassName annotation)
addAnnotation(Class<?> annotation)

该方法即为类或方法或参数设置注解,参数即可以是 AnnotationSpec,也可以是 ClassName,还可以直接传递 Class 对象。 一般情况下,包含复杂属性的注解一般用 AnnotationSpec,如果单纯添加基本注解无其他附加属性可以直接使用 ClassName 或者 Class 即可。

设置注释

addJavadoc(CodeBlock block)
addJavadoc(String format, Object... args)

在编写类、方法、成员变量时可以通过 addJavadoc() 来设置注释,可以直接传入String 对象或者传入 CodeBlock(代码块)。

2.4、JavaPoet 生成类、接口、枚举对象

在 JavaPoet 中生成类、接口、枚举,必须得通过 TypeSpec 生成,而 classBuilder、interfaceBuilder、enumBuilder 便是创建其关键的方法:

//创建类
TypeSpec.classBuilder("类名“) 
TypeSpec.classBuilder(ClassName className)

//创建接口
TypeSpec.interfaceBuilder("接口名称")
TypeSpec.interfaceBuilder(ClassName className)

//创建枚举
TypeSpec.enumBuilder("枚举名称")
TypeSpec.enumBuilder(ClassName className)

继承、实现接口

//继承类:
.superclass(ClassName className)
//实现接口
.addSuperinterface(ClassName className)

继承存在泛型的父类

当继承父类存在泛型时,需要使用 ParameterizedTypeName

ParameterizedTypeName get(ClassName rawType, TypeName... typeArguments)

返回的 ParameterizedTypeName 对象,已经被添加泛型信息。

方法

addMethod(MethodSpec methodSpec)

通过配置 MethodSpec 对象,使用 addMethod 方法将其添加进 TypeSpec 中。

枚举

addEnumConstan(String enumValue)

通过 addEnumConstan 方法添加枚举值,参数为枚举值名称。

2.5、JavaPoet 生成成员变量

JavaPoet 生成成员变量是通过 FieldSpec 的 build 方法生成:

builder(TypeName type, String name, Modifier... modifiers)

只要传入 TypeName(Class)、name(名称)、Modifier(修饰符),就可以生成一个基本的成员变量。成员变量一般来说由注解(Annotation)、修饰符(Modifier)、注释(Javadoc)、实例化(initializer)。

注解addAnnotation(TypeName name) 
修饰符addModifiers(Modifier ...modifier)
注释addJavadoc(String format, Object... args)
实例化initializer(String format, Object... args)

由于上述前三个方法,都在通用方法介绍过这里就不再重复介绍。下面看看实例化,即成员变量的实例化,例:

public Activity mActivity = new Activity;

而 initializer 方法中的内容就是 “=” 后面的内容,下面看下具体的代码实现,上面的成员变量:

  ClassName activity = ClassName.get("android.app", "Activity");
  FieldSpec spec = FieldSpec.builder(activity, "mActivity")
                .addModifiers(Modifier.PUBLIC)
                .initializer("new $T", activity)
                .build();

2.6、JavaPoet 生成方法

JavaPoet 生成方法分为两种:

构造方法MethodSpec.constructorBuilder()

常规方法

MethodSpec.methodBuilder(String name)

方法的主要构成有方法参数、注解、返回值、方法体、抛出异常五种,注解可以参考通用方法 addAnnotation,其他方法我们将会一一介绍:

方法参数

addParameter(ParameterSpec parameterSpec)

设置方法参数的方法通过 addParameterSpec 来实现,ParameterSpec 的具体使用参考下一小节。

返回值

returns(TypeName returnType)

设置方法的返回值,只需传入一个 TypeName 对象,而 TypeName 是 ClassName,ParameterizedTypeName 的基类。

方法体

在JavaPoet中,设置方法体内容有两个方法,分别是 addCode() 和 addStatement(),这两个本质上都是设置方法体内容,但是不同的是使用 addStatement() 方法时,你只需要专注于该段代码的内容,至于结尾的分号和换行它都会帮你做好。 而 addCode() 添加的方法体内容就是一段无格式的代码片,需要开发者自己添加其格式。

方法体模板

在 JavaPoet 中,设置方法体使用模板是比较常见的,因为 addCode() 和 addStatement() 方法都存在这样的一个重载:

addCode(String format, Object... args)
addStatement(String format, Object... args)

在 JavaPoet 中 format 中存在三种特定的占位符:

$T

$T 在 JavaPoet 代指的是 TypeName,该模板主要将 Class 抽象出来,用传入的 TypeName 指向的 Class 来代替。

ClassName bundle = ClassName.get("android.os", "Bundle");
addStatement("$T bundle = new $T()",bundle)

上述添加的代码内容为:

Bundle bundle = new Bundle();

$N

$N 在 JavaPoet 中代指的是一个名称,例如调用的方法名称、变量名称,这一类存在意思的名称

addStatement("data.$N()",toString)

上述代码添加的内容:

data.toString();

$S

$S 在 JavaPoet 中就和 String.format 中 %s 一样,字符串的模板,将指定的字符串替换到 $S 的地方

.addStatement("super.$S(savedInstanceState)","onCreate")

即将"onCreate"字符串代替到$S的位置上.

抛出异常

.addException(TypeName name)

设置方法抛出异常,可以使用 addException() 方法,传入指定的异常的 ClassName,即可为该方法设置其抛出该异常。

2.7、JavaPoet 生成方法参数

JavaPoet 生成有参方法时,需要填充参数,而生成参数则需要通过 ParameterSpec 这个类。

addParameter(ParameterSpec parameterSpec)

初始化 ParameterSpec

ParameterSpec.builder(TypeName type, String name, Modifier... modifiers)

给参数设置其 Class,以及参数名称和修饰符。

通常来说参数的构成包括:参数的类型(Class)、参数的名称(name)、修饰符(modifiers)、注解(Annotation)。

除了 builder() 方法初始化类型、以及名称、修饰符之外,其余可以通过如下方法进行设置:

添加修饰符

.addModifiers(Modifier modifier)

添加注解

addAnnotation(TypeName name) 

添加修饰符、注解具体使用可参考通用方法。

2.8、JavaPoet 生成注解

在 JavaPoet 创建类、成员变量、方法参数、方法,都会用到注解。如果使用不包含属性的注解可以直接通过:

   .addAnnotation(TypeName name)

直接传入 TypeName 或者 Class 进行设置。

如果使用的注解包含属性并且不止一个时,这时候就需要生成 AnnotationSpec 来解决,下面简单了解下 AnnotationSpec。

初始化AnnotationSpec

AnnotationSpec.builder(ClassName type)

可以发现初始化,只需传入 ClassName 或者 Class 即可。

设置属性

addMember(String name, String format, Object... args)

使用 addMember 可以设置注解的属性值,name 对应的就是属性名称,format 的内容即属性体,同样方法体的格式化在这里也是适用的。

2.9、JavaPoet 如何生成代码

如果上述内容你已经看完,那么恭喜你已经明白 JavaPoet 的意图,但是现在的你还差最后一步,即如何生成代码。JavaPoet 中负责生成的类是 JavaFile:

JavaFile.builder(String packageName, TypeSpec typeSpec)

JavaFile 通过向 build 方法传入 PackageName(Java文件的包名)、TypeSpec(生成的内容)生成。

打印结果

javaFile.writeTo(System.out)

生成的内容会输出到控制台中

生成文件

javaFile.writeTo(File file)

生成的内容会以 java 文件的方式存放到你传入 File 文件的位置。

 

三、总结


当你读完了本文,如果你产生下面的想法:

  1. JavaPoet 原来还可以这样
  2. JavaPoet 编写过程为什么那么流畅,原来 Java 文件也可以用编程的方式生成
  3. JavaPoet 可不可以改进我的编码流程,提升效率

那么说明你已经对 JavaPoet 感兴趣了,可以自己动手尝试一下感受下 JavaPoet 的魅力。项目主页及源码:https://github.com/square/javapoet

最后贴一张 JavaPoet 辅助图,相信有了它会很好的帮助你使用 JavaPoet。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值