JavaPoet动态生成代码,安卓常见面试题

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16

四、源码浅析


下面来看看调用了JavaFile的writeTo后实际做了些什么。

public void writeTo(Appendable out) throws IOException { // First pass: emit the entire class, just to collect the types we'll need to import. CodeWriter importsCollector = new CodeWriter(NULL_APPENDABLE, indent, staticImports); emit(importsCollector); Map<String, ClassName> suggestedImports = importsCollector.suggestedImports(); // Second pass: write the code, taking advantage of the imports. CodeWriter codeWriter = new CodeWriter(out, indent, suggestedImports, staticImports); emit(codeWriter); }

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

通过源码可以知道,writeTo分为两部分:第一步收集import,记录下来后第二步才跟随内容一起写到CodeWriter。

另外我们可以看到源码中的emit方法,通过查看其它源码发现,在JavaPoet中,所有java文件的抽象元素都定义了emit方法,如TypeSepc,ParameterSepc等,emit方法传入CodeWriter对象输出字符串。上层元素调用下层元素的emit方法,如JavaFile的emit方法调用TypeSpec的emit方法,从而实现整个java文件字符串的生成。

下面我们以MethodSpec为例,查看其emit代码:

void emit(CodeWriter codeWriter, String enclosingName, Set<Modifier> implicitModifiers) throws IOException { codeWriter.emitJavadoc(javadoc); codeWriter.emitAnnotations(annotations, false); codeWriter.emitModifiers(modifiers, implicitModifiers); if (!typeVariables.isEmpty()) { codeWriter.emitTypeVariables(typeVariables); codeWriter.emit(" "); } if (isConstructor()) { codeWriter.emit("$L(", enclosingName); } else { codeWriter.emit("$T $L(", returnType, name); } boolean firstParameter = true; for (Iterator<ParameterSpec> i = parameters.iterator(); i.hasNext(); ) { ParameterSpec parameter = i.next(); if (!firstParameter) codeWriter.emit(",").emitWrappingSpace(); parameter.emit(codeWriter, !i.hasNext() && varargs); firstParameter = false; } codeWriter.emit(")"); if (defaultValue != null && !defaultValue.isEmpty()) { codeWriter.emit(" default "); codeWriter.emit(defaultValue); } if (!exceptions.isEmpty()) { codeWriter.emitWrappingSpace().emit("throws"); boolean firstException = true; for (TypeName exception : exceptions) { if (!firstException) codeWriter.emit(","); codeWriter.emitWrappingSpace().emit("$T", exception); firstException = false; } } if (hasModifier(Modifier.ABSTRACT)) { codeWriter.emit(";\n"); } else if (hasModifier(Modifier.NATIVE)) { // Code is allowed to support stuff like GWT JSNI. codeWriter.emit(code); codeWriter.emit(";\n"); } else { codeWriter.emit(" {\n"); codeWriter.indent(); codeWriter.emit(code); codeWriter.unindent(); codeWriter.emit("}\n"); } }

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16

  • 17

  • 18

  • 19

  • 20

  • 21

  • 22

  • 23

  • 24

  • 25

  • 26

  • 27

  • 28

  • 29

  • 30

  • 31

  • 32

  • 33

  • 34

  • 35

  • 36

  • 37

  • 38

  • 39

  • 40

  • 41

  • 42

  • 43

  • 44

  • 45

  • 46

  • 47

  • 48

  • 49

  • 50

  • 51

  • 52

  • 53

  • 54

  • 55

  • 56

  • 57

  • 58

  • 59

  • 60

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16

  • 17

  • 18

  • 19

  • 20

  • 21

  • 22

  • 23

  • 24

  • 25

  • 26

  • 27

  • 28

  • 29

  • 30

  • 31

  • 32

  • 33

  • 34

  • 35

  • 36

  • 37

  • 38

  • 39

  • 40

  • 41

  • 42

  • 43

  • 44

  • 45

  • 46

  • 47

  • 48

  • 49

  • 50

  • 51

  • 52

  • 53

  • 54

  • 55

  • 56

  • 57

  • 58

  • 59

  • 60

可以看出,MethodSepc通过调用codeWriter的emit方法依次输出javadoc,annotation,parameter,codeblock等。

五、使用场景


5.1 根据编译时注解生成代码

5.1.1 前言

用过butterknife的同学会发现,使用butterknife我们可以省去平时重复书写的findViewById之类的代码,通过注解的方式即可实现。而早期的butterknife使用的注解是运行时注解,即运行时通过注解然后使用反射实现,存在一定的性能问题,后面作者做了改进,使用编译时注解,编译期间,在注解处理器中对注解进行处理生成相应代码。

通过查看butterknife源码,如下:

  • build.gradle (butterknife-parent)

ext.deps = [ ... javapoet: 'com.squareup:javapoet:1.8.0', ... ]

  • 1

  • 2

  • 3

  • 4

  • 5

  • 1

  • 2

  • 3

  • 4

  • 5

  • build.gradle (butterknife-compiler)

dependencies { ... compile deps.javapoet ... }

  • 1

  • 2

  • 3

  • 4

  • 5

  • 1

  • 2

  • 3

  • 4

  • 5

  • ButterKnifeProcessor.java (butterknife-compiler)

(注解处理器)

@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) { Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env); for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) { TypeElement typeElement = entry.getKey(); BindingSet binding = entry.getValue(); JavaFile javaFile = binding.brewJava(sdk); try { javaFile.writeTo(filer); } catch (IOException e) { error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage()); } } return false; }

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16

  • 17

  • 18

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16

  • 17

  • 18

可以看到butterknife在编译时在Processor中获取对应的注解,然后使用JavaPoet进行代码生成工作。(事实上开源框架Dagger也使用了JavaPoet)

5.1.2 一个简单示例

本节将简单演示利用编译时注解+JavaPoet来实现编译期间动态生成代码。

工程目录结构:

  • Hello

  • app

  • hello-annotation (注解相关)

  • hello-compiler (处理器生成代码相关)

①. 导入依赖:

build.gralde (project)

buildscript { ... dependencies { ... classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' } }

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

build.gradle (Module:app)

`apply plugin: ‘com.neenbedankt.android-apt’ dependencies { … compile project(’:hello-annotation’) apt project(

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享

‘:hello-compiler’) }`

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

build.gradle (Module:hello-compiler)

dependencies { ... compile 'com.squareup:javapoet:1.9.0' compile 'com.google.auto.service:auto-service:1.0-rc2' }

  • 1

  • 2

  • 3

  • 4

  • 5

  • 1

  • 2

  • 3

  • 4

  • 5

注: 自Android Gradle 插件 2.2 版本开始,官方提供了名为 annotationProcessor 的功能来完全代替 android-apt。

若工程使用gradle版本>=2.2,则此处无需引用com.neenbedankt.android-apt相关,将 apt project(':hello-compiler') 改为 annotationProcessor project(':hello-compiler') 即可。

②. 定义注解: (Module:hello-annotation)

HelloAnnotation.java

@Retention(RetentionPolicy.CLASS) @Target(ElementType.TYPE) public @interface HelloAnnotation { }

  • 1

  • 2

  • 3

  • 4

  • 1

  • 2

  • 3

  • 4

③. 定义Processor: (Module:hello-compiler)

HelloProcessor.java

@AutoService(Processor.class) public class HelloProcessor extends AbstractProcessor { private Filer filer; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); filer = processingEnv.getFiler(); // for creating file } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { for (TypeElement element : annotations) { if (element.getQualifiedName().toString().equals(HelloAnnotation.class.getCanonicalName())) { // main method MethodSpec main = MethodSpec.methodBuilder("main") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(void.class) .addParameter(String[].class, "args") .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!") .build(); // HelloWorld class TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld") .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addMethod(main) .build(); try { // build com.example.HelloWorld.java JavaFile javaFile = JavaFile.builder("com.example", helloWorld) .addFileComment(" This codes are generated automatically. Do not modify!") .build(); // write to file javaFile.writeTo(filer); } catch (IOException e) { e.printStackTrace(); } } } return true; } @Override public Set<String> getSupportedAnnotationTypes() { return Collections.singleton(HelloAnnotation.class.getCanonicalName()); } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } }

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16

  • 17

  • 18

  • 19

  • 20

  • 21

  • 22

  • 23

  • 24

  • 25

  • 26

  • 27

  • 28

  • 29

  • 30

  • 31

  • 32

  • 33

  • 34

  • 35

  • 36

  • 37

  • 38

  • 39

  • 40

  • 41

  • 42

  • 43

  • 44

  • 45

  • 46

  • 47

  • 48

  • 49

  • 50

  • 51

  • 52

  • 53

  • 54

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16

  • 17

  • 18

  • 19

  • 20

  • 21

  • 22

  • 23

  • 24

  • 25

  • 26

  • 27

  • 28

  • 29

  • 30

  • 31

  • 32

  • 33

  • 34

  • 35

  • 36

  • 37

  • 38

  • 39

  • 40

  • 41

  • 42

  • 43

  • 44

  • 45

  • 46

  • 47

  • 48

  • 49

  • 50

  • 51

  • 52

  • 53

  • 54

④. 使用注解并调用生成的类函数

MainActivity.java (Module:app)

@HelloAnnotation public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); HelloWorld.main(null); } }

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

未编译前,HelloWorld.java是不存在的,这里会报错。那么,我们尝试编译一下,就会发现HelloWorld.java会自动生成,如下:

// This codes are generated automatically. Do not modify! package com.example; import java.lang.String; import java.lang.System; public final class HelloWorld { public static void main(String[] args) { System.out.println("Hello, JavaPoet!"); } }

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

5.2 根据协议文件生成对应代码

假设我们对类的声明以及接口的声明是以特定格式写在一个协议文件中,那么我们可以先读取该协议文件内容,使用JavaPoet根据协议对应生成Java代码。

如定义以下协议文件:

service TestDemo { rpc doRequest (MyRequest) returns (MyResponse) { // 请求接口定义 } message MyRequest { // 请求内容实体 string content; } message MyResponse { // 返回内容实体 int32 status_code; string entity; } }

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

那么利用JavaPoet我们可以生成对应的TestDemo.java, MyRequest.java, MyResponse.java, 以及TestDemo.java中对应的请求接口和实现。

注:此部分协议定义参考自google开源的protobuffer和grpc

5.3 更多待扩展

六、知识储备


6.1 注解处理器(Annotation Processor)

注解处理器(Annotation Processor)是javac的一个工具,它用来在编译时扫描和处理注解(Annotation)。你可以自定义注解,并注册相应的注解处理器(自定义的注解处理器需继承自AbstractProcessor)。

6.1.1 自定义注解处理器

定义一个注解处理器,需要继承自AbstractProcessor。如下所示:

package com.example; public class MyProcessor extends AbstractProcessor { @Override public synchronized void init(ProcessingEnvironment env){ } @Override public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { } @Override public Set<String> getSupportedAnnotationTypes() { } @Override public SourceVersion getSupportedSourceVersion() { } }

  • 1

  • 2

  • 3

  • 4

  • 5

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值