自定义注解处理器和自动生成代码

Android面试技术要点汇总
2021-05-17 14:29 · 阅读时长9分钟
小课

虽然自定义注解处理器是Java1.5发布的特性了,但是仍然十分强大,我们将学习什么是注解处理器以及如何使用注解处理器和代码生成库来一些好用的功能。

一、注解

我相信每个使用过Java的人都知道,Java自身就有很多注解,比如@Override用来声明重写方法,@Singleton用来申明单例,Android SDK中也定义了一些注解,比如@NonNull@StringRes@IntRes等等。

二、代码生成

如果使用过Dagger2LombokDeepLinkDispatch 这些开源库的话,应该知道@Inject、@Component@Module等注解,通过在一些类或者方法上加上这些注解就能实现一些非常神奇的功能。实际上之所以能实现这些功能,是因为这些第三方库定义了一些注解处理器,在编译时,这些注解处理器根据注解自动生成了一些实现功能的Java代码。

三、自定义注解处理器

下面开始实现自定义注解处理器,主要功能是通过注解,实现路由跳转,示例项目分为以下三个部分:

  1. app模块。
  2. annotation模块,用于自定义一些注解。
  3. annotation processor模块,用于自定义一些注解处理器。
自定义注解处理器和自动生成代码

在annotation模块中,新建文件src/main/java/com/example/annotation/Route.java,声明自定义注解

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface Route {
    String name() default "/";
}
  • @interface,用于声明自定义注解。
  • @Target,用于申明自定义注解修饰类型,比如TYPE(类,接口等)、FIELD(成员变量)、METHOD(方法)等等。
  • @Retention,用于声明自定义注解保留的阶段,比如SOURCE(在源码中保留,编译后以及运行时就没有使用该注解的信息)、CLASS(编译后还保留使用该注解的信息,但是在运行时不可用)、RUNTIME(源码、编译后,以及运行时都可以获取使用该注解的信息)。

在annotation-processor模块中,修改build.gradle引入依赖

dependencies {
    //用于生成Java代码
    implementation 'com.squareup:javapoet:1.13.0'
    implementation project(':annotation')

    //用于简化配置Processor,非必需,它本身也是google开发的一个注解处理器,代码比较简单,可以查看源码
    annotationProcessor 'com.google.auto.service:auto-service:1.0'
    implementation 'com.google.auto.service:auto-service-annotations:1.0'
}

然后编写自定义注解处理器,创建文件src/main/java/com/example/annotation/processor/RouteProcessor.java

1@AutoService(Processor.class)
2public class RouteProcessor extends AbstractProcessor {
3
4    @Override
5    public Set<String> getSupportedAnnotationTypes() {
6        return Collections.singleton(Route.class.getCanonicalName());
7    }
8
9    @Override
10    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
11        if (env.processingOver()) {
12            generateSource();
13        } else {
14            parseAnnotation(env);
15        }
16        return true;
17    }
18}
  • getSupportedAnnotationTypes,用于申明,该注解处理器需要处理的注解,只有包含这些注解的才会被该注解处理器处理。
  • process,用于实现处理注解的主要逻辑,包括解析被@Route标注过的类,根据@Route的参数url和对应类生成一张HashMap,然后根据表生成Java代码。
1/**
2* 解析{@link com.example.annotation.Route}注解,把被它标注过的类加入HashMap中
3*
4@param env
5*/
6private void parseAnnotation(RoundEnvironment env) {
7    for (Element element : env.getElementsAnnotatedWith(Route.class)) {
8        if (element.getKind() != ElementKind.CLASS) {
9            return;
10        }
11        TypeElement typeElement = (TypeElement) element;
12        Route uri = element.getAnnotation(Route.class);
13        routes.put(uri.url(), typeElement);
14    }
15}
16
17/**
18* 根据HashMap生成Java代码
19*/
20private void generateSource() {
21    TypeSpec.Builder navigatorClass = TypeSpec.classBuilder("Routers")
22            .addModifiers(Modifier.PUBLIC, Modifier.FINAL);
23    MethodSpec.Builder getMethod = MethodSpec.methodBuilder("get")
24            .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
25            .addParameter(ClassName.get(String.class), "url")
26            .returns(ClassName.get(Class.class));
27    List<Map.Entry<String, TypeElement>> entrySet = new ArrayList<>(routes.entrySet());
28    for (int i = 0; i < entrySet.size(); i++) {
29        Map.Entry<String, TypeElement> entry = entrySet.get(i);
30        ClassName activityClass = ClassName.get(entry.getValue());
31        if (== 0) {
32            getMethod.beginControlFlow("if (url.equals($S))", entry.getKey());
33        } else {
34            getMethod.nextControlFlow("else if (url.equals($S))", entry.getKey());
35        }
36        getMethod.addStatement("return $T.class", activityClass);
37        if (== entrySet.size() - 1) {
38            getMethod.endControlFlow();
39        }
40    }
41    getMethod.addStatement("return null");
42    navigatorClass.addMethod(getMethod.build());
43    try {
44        JavaFile.builder(PKG, navigatorClass.build()).build().writeTo(processingEnv.getFiler());
45    } catch (IOException e) {
46        e.printStackTrace();
47    }
48}

最终通过注解自动生成的代码如下:

public final class Routers {
  public static Class get(String url) {
    if (url.equals("/settings")) {
      return SettingsActivity.class;
    } else if (url.equals("/")) {
      return MainActivity.class;
    }
    return null;
  }
}

这里仅仅是完成了最简单的路由跳转功能,没有考虑传参,以及调用结果之类的逻辑,完整的示例项目代码如下

一些优秀的路由框架:WMRouterARouterDeepLinkDispatch

注释
文件大小修改时间
annotation
2022年02月15日
annotation-processor
2022年02月15日
app
2022年02月15日
build.gradle
431 B 2022年02月15日
gradle/wrapper
2022年02月14日
gradle.properties
1 kB 2022年02月15日
gradlew
6 kB 2022年02月14日
gradlew.bat
3 kB 2022年02月14日
local.properties
437 B 2022年02月14日
settings.gradle
388 B 2022年02月15日
annotation processorannotationcode generation注解