组件化 2 -- APT 路由封装、注解处理器生成类文件

围巾🧣 2024年03月21日 235次浏览

APT

APT是什么?

APT(Annotation Processing Tool) 是一种处理注释的工具,它对源代码文件进行检测找出 其中的Annotation,根据注解自动生成代码,如果想要自定义的注解处理器能够正常运行,必须要通过 APT工具来进行处理。 也可以这样理解,只有通过声明APT工具后,程序在编译期间自定义注解解释器 才能执行。

通俗理解:根据规则,帮我们生成代码、生成类文件

APT中用到的结构体思路

element组成的结构体
<html>
<body>
<div>.....</div>
</body>
</html>
对于java源文件来说,它同样也是一种结构体语言
package com.netease.apt; // PackageElement 包元素/节点

public class Main { // TypeElement 类元素/节点
    private int x; // VariableElement 属性元素/节点

    private Main() { // ExecuteableElement 方法元素/节点
    }

    private void print(String msg) {
    }
}

APT中用到的重要元素

PackageElement
表示一个包程序元素。提供对有关包及其成员的信息的访问

ExecutableElement
表示某个类或接口的方法、构造方法或初始化程序(静态或实例)

TypeElement
表示一个类或接口程序元素。提供对有关类型及其成员的信息的访问

VariableElement
表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数

APT中用到API

  • getEnclosedElements() 返回该元素直接包含的子元素
  • getEnclosingElement() 返回包含该element的父element,与上一个方法相反
  • getKind() 返回element的类型,判断是哪种element
  • getModifiers() 获取修饰关键字,入public static final等关键字
  • getSimpleName() 获取名字,不带包名
  • getQualifiedName() 获取全名,如果是类的话,包含完整的包名路径
  • getParameters() 获取方法的参数元素,每个元素是一个VariableElement
  • getReturnType() 获取方法元素的返回值
  • getConstantValue() 如果属性变量被final修饰,则可以使用该方法获取它的值

1.APT技术

APT全称: annotation process tool是一种处理注释的工具,它对源代码文件进行检测找出其 中的Annotation,使用Annotation进行额外的处理。

  • APT 编译的时候 ----------> 处理注解 --------> 生成代码----------> 编译完成
  • APT 传统方式 ---------------> 生成 java文件
  • APT JavaPoet方式 ---------> 生成Java文件

Javapoet

JavaPoet是square推出的开源java代码生成框架,提供Java Api生成.java源文件 
这个框架功能非常实用,也是我们习惯的Java面向对象OOP语法 
可以很方便的使用它根据注解生成对应代码通过这种自动化生成代码的方式,可以让我们用更加简洁优雅的方式要替代繁琐冗杂的重复工作

JavaPoet相关

类对象 说明
MethodSpec 代表一个构造函数或方法声明
TypeSpec 代表一个类,接口,或者枚举声明
FieldSpec 代表一个成员变量,一个字段声明
JavaFile 包含一个顶级类的Java文件
ParameterSpec 用来创建参数
AnnotationSpec 用来创建注解
ClassName 用来包装一个类
TypeName 类型,如在添加返回值类型是使用 TypeName.VOID

$S 字符串,如:$S, ”hello”
$T 类、接口,如:$T, MainActivity

高级用法JavaPoet

JavaPoet到底是什么? 答:oop思想方式:优点(加入oop思想) 缺点(不习惯,倒序)

javapoet

APT的学习

创建JavaLib,导入 auto-service 依赖,编译期扫描注解生成文件

apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    // AS 4.3.1 ->  4.0.1 没有问题
    // As-3.4.1  +  gradle-5.1.1-all + auto-service:1.0-rc4
    compileOnly'com.google.auto.service:auto-service:1.0-rc4'
    annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'

    // 帮助我们通过类调用的形式来生成Java代码
    implementation "com.squareup:javapoet:1.9.0"

    // 引入annotation,处理@ARouter注解
    implementation project(':arouter_annotation')
}

// java控制台输出中文乱码
tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
}

sourceCompatibility = "7"
targetCompatibility = "7"

定义注解

com.xiangxue.compiler.ARouterProcessor

JavaPoet的学习

引入

// 帮助我们通过类调用的形式来生成Java代码
implementation "com.squareup:javapoet:1.9.0"

模型1:生成静态代码 写死

package com.example.helloworld;

public final class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, JavaPoet!");
    }
}

模型2:动态变化,根据要求动态生成类

public class MainActivity$$$$$$$$$ARouter {
    public static Class findTargetClass(String path) {
        return path.equals("app/MainActivity") ? MainActivity.class : null;
    }
}

生成步骤

倒序生成代码

构建方法


// 任何的class类型,必须包装
// Map<String, RouterBean>
TypeName methodReturn = ParameterizedTypeName.get(
          ClassName.get(Map.class),         // Map
          ClassName.get(String.class),      // Map<String,
          ClassName.get(RouterBean.class)   // Map<String, RouterBean>
);

//定义方法签名
// 1.方法
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(ProcessorConfig.PATH_METHOD_NAME)
        .addAnnotation(Override.class) // 给方法上添加注解  @Override
        .addModifiers(Modifier.PUBLIC) // public修饰符
        .returns(methodReturn) // 把Map<String, RouterBean> 加入方法返回

//方法的语句
// Map<String, RouterBean> pathMap = new HashMap<>(); // $N == 变量 为什么是这个,因为变量有引用 所以是$N
methodBuilder.addStatement("$T<$T, $T> $N = new $T<>()",
        ClassName.get(Map.class),           // Map
        ClassName.get(String.class),        // Map<String,
        ClassName.get(RouterBean.class),    // Map<String, RouterBean>
        ProcessorConfig.PATH_VAR1,          // Map<String, RouterBean> pathMap
        ClassName.get(HashMap.class)        // Map<String, RouterBean> pathMap = new HashMap<>();
        );

// 返回
// return pathMap;
methodBuilder.addStatement("return $N", ProcessorConfig.PATH_VAR1);

构建类

// 生成类文件:ARouter$$Path$$personal
JavaFile.builder(aptPackage, // 包名  APT 存放的路径
        TypeSpec.classBuilder(finalClassName) // 类名
                 // 实现ARouterLoadPath接口  implements ARouterPath==pathType
                .addSuperinterface(ClassName.get(pathType)) 
                .addModifiers(Modifier.PUBLIC) // public修饰符
                .addMethod(methodBuilder.build()) // 方法的构建(方法参数 + 方法体)
                .build()) // 类构建完成
        .build() // JavaFile构建完成
        .writeTo(filer); // 文件生成器开始生成类文件

路由框架结构

架构

三个模块

  • arouter_annotation
  • arouter_api
  • arouter_compiler

arouter_annotation

定义注解

@Target(ElementType.TYPE) // 该注解作用在类之上
@Retention(RetentionPolicy.CLASS) // 要在编译时进行一些预处理操作,注解会在class文件中存在
public @interface ARouter {

    // 详细路由路径(必填),如:"/app/MainActivity"
    String path();

    // 路由组名(选填,如果开发者不填写,可以从path中截取出来)
    String group() default "";
}

RouterBean 路由路径Path的最终实体封装类

封装Activity和能力提供者 Call/Provider 的Class

public class RouterBean {

    // 为了以后的发展
    public enum TypeEnum {
        ACTIVITY,
        CALL
    }

    private TypeEnum typeEnum; // 枚举类型:Activity
    private Element element; // 类节点 JavaPoet学习的时候,可以拿到很多的信息
    private Class<?> myClass; // 被注解的 Class对象 例如: MainActivity.class  Main2Activity.class
    private String path; // 路由地址  例如:/app/MainActivity
    private String group; // 路由组  例如:app  order  personal
    ……
}

arouter_compiler

扫描应用了注解的类,根据规则生成 Group、Path

ARouterProcessor

具体的扫描注解生成文件的逻辑,需要 extends AbstractProcessor

初始化

// AutoService则是固定的写法,加个注解即可
// 通过auto-service中的@AutoService可以自动生成AutoService注解处理器,用来注册
// 用来生成 META-INF/services/javax.annotation.processing.Processor 文件
@AutoService(Processor.class)

// 允许/支持的注解类型,让注解处理器处理
@SupportedAnnotationTypes({ProcessorConfig.AROUTER_PACKAGE})

// 指定JDK编译版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)

// 注解处理器接收的参数
@SupportedOptions({ProcessorConfig.OPTIONS, ProcessorConfig.APT_PACKAGE})

public class ARouterProcessor extends AbstractProcessor {

    // 操作Element的工具类(类,函数,属性,其实都是Element)
    private Elements elementTool;

    // type(类信息)的工具类,包含用于操作TypeMirror的工具方法
    private Types typeTool;

    // Message用来打印 日志相关信息
    private Messager messager;

    // 文件生成器, 类 资源 等,就是最终要生成的文件 是需要Filer来完成的
    private Filer filer;

    private String options; // 各个模块传递过来的模块名 例如:app order personal
    private String aptPackage; // 各个模块传递过来的目录 用于统一存放 apt生成的文件

    // 仓库一 Path  缓存一
    // Map<"personal", List<RouterBean>>
    private Map<String, List<RouterBean>> mAllPathMap = new HashMap<>(); // 目前是一个

    // 仓库二 Group 缓存二
    // Map<"personal", "ARouter$$Path$$personal.class">
    private Map<String, String> mAllGroupMap = new HashMap<>();

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);

        elementTool = processingEnvironment.getElementUtils();
        messager = processingEnvironment.getMessager();
        filer = processingEnvironment.getFiler();
        typeTool = processingEnvironment.getTypeUtils();

        // 只有接受到 App壳 传递过来的数据,才能证明我们的 APT环境搭建完成
        options = processingEnvironment.getOptions().get(ProcessorConfig.OPTIONS);
        aptPackage = processingEnvironment.getOptions().get(ProcessorConfig.APT_PACKAGE);
        messager.printMessage(Diagnostic.Kind.NOTE, ">>>>>>>>>>>>>>>>>>>>>> options:" + options);
        messager.printMessage(Diagnostic.Kind.NOTE, ">>>>>>>>>>>>>>>>>>>>>> aptPackage:" + aptPackage);
        if (options != null && aptPackage != null) {
            messager.printMessage(Diagnostic.Kind.NOTE, "APT 环境搭建完成....");
        } else {
            messager.printMessage(Diagnostic.Kind.NOTE, "APT 环境有问题,请检查 options 与 aptPackage 为null...");
        }
    }
    // ……
}

生成 Class 流程

获取显示类信息 TypeMirror

遍历所有 ARouter注解的类 elements

  • 封装RouterBean
  • ARouter注解的类类型检查
  • 缓存到 mAllPathMap
  • 获取 pathType、groupType
  • createPathFile、createGroupFile

createGroupFile 举例

    /**
     * 生成路由组Group文件,如:ARouter$$Group$$app
     * @param groupType ARouterLoadGroup接口信息
     * @param pathType ARouterLoadPath接口信息
     */
    private void createGroupFile(TypeElement groupType, TypeElement pathType) throws IOException {
        // 仓库二 缓存二 判断是否有需要生成的类文件
        if (ProcessorUtils.isEmpty(mAllGroupMap) || ProcessorUtils.isEmpty(mAllPathMap)) return;

        // 返回值 这一段 Map<String, Class<? extends ARouterPath>>
        TypeName methodReturns = ParameterizedTypeName.get(
                ClassName.get(Map.class),        // Map
                ClassName.get(String.class),    // Map<String,

                // Class<? extends ARouterPath>> 难度
                ParameterizedTypeName.get(ClassName.get(Class.class),
                        // ? extends ARouterPath
                        WildcardTypeName.subtypeOf(ClassName.get(pathType))) // ? extends ARouterLoadPath
                        // WildcardTypeName.supertypeOf() 做实验 ? super

                // 最终的:Map<String, Class<? extends ARouterPath>>
        );

        // 1.方法 public Map<String, Class<? extends ARouterPath>> getGroupMap() {
        MethodSpec.Builder methodBuidler = MethodSpec.methodBuilder(ProcessorConfig.GROUP_METHOD_NAME) // 方法名
                .addAnnotation(Override.class) // 重写注解 @Override
                .addModifiers(Modifier.PUBLIC) // public修饰符
                .returns(methodReturns); // 方法返回值

        // Map<String, Class<? extends ARouterPath>> groupMap = new HashMap<>();
        methodBuidler.addStatement("$T<$T, $T> $N = new $T<>()",
                ClassName.get(Map.class),
                ClassName.get(String.class),

                // Class<? extends ARouterPath> 难度
                ParameterizedTypeName.get(ClassName.get(Class.class),
                        WildcardTypeName.subtypeOf(ClassName.get(pathType))), // ? extends ARouterPath
                        ProcessorConfig.GROUP_VAR1,
                        ClassName.get(HashMap.class));

        //  groupMap.put("personal", ARouter$$Path$$personal.class);
        //	groupMap.put("order", ARouter$$Path$$order.class);
        for (Map.Entry<String, String> entry : mAllGroupMap.entrySet()) {
            methodBuidler.addStatement("$N.put($S, $T.class)",
                    ProcessorConfig.GROUP_VAR1, // groupMap.put
                    entry.getKey(), // order, personal ,app
                    ClassName.get(aptPackage, entry.getValue()));
        }

        // return groupMap;
        methodBuidler.addStatement("return $N", ProcessorConfig.GROUP_VAR1);

        // 最终生成的类文件名 ARouter$$Group$$ + personal
        String finalClassName = ProcessorConfig.GROUP_FILE_NAME + options;

        messager.printMessage(Diagnostic.Kind.NOTE, "APT生成路由组Group类文件:" +
                aptPackage + "." + finalClassName);

        // 生成类文件:ARouter$$Group$$app
        JavaFile.builder(aptPackage, // 包名
                TypeSpec.classBuilder(finalClassName) // 类名
                .addSuperinterface(ClassName.get(groupType)) // 实现ARouterLoadGroup接口 implements ARouterGroup
                .addModifiers(Modifier.PUBLIC) // public修饰符
                .addMethod(methodBuidler.build()) // 方法的构建(方法参数 + 方法体)
                .build()) // 类构建完成
                .build() // JavaFile构建完成
                .writeTo(filer); // 文件生成器开始生成类文件
    }

arouter_api

RouterManager

提供向外的 API,获取单例,提供导航功能、参数

build 方法返回 BundleManager

BundleManager 可以携带参数,保存到 bundle 里面

navigation 方法先寻找 ARouterGroup,再找对应的 ARouterPath,缓存

再根据 path 寻找 RouterBean

public class RouterManager {

    private String group; // 路由的组名 app,order,personal ...
    private String path;  // 路由的路径  例如:/order/Order_MainActivity

    /**
     * 上面定义的两个成员变量意义:
     * 1.拿到ARouter$$Group$$personal  根据组名 拿到 ARouter$$Path$$personal
     * 2.操作路径,通过路径 拿到  Personal_MainActivity.class,就可以实现跳转了
     */

    // 单例模式
    private static volatile RouterManager instance;

    public static RouterManager getInstance() {
        if (instance == null) {
            synchronized (RouterManager.class) {
                if (instance == null) {
                    instance = new RouterManager();
                }
            }
        }
        return instance;
    }

    // 提供性能  LRU缓存
    private LruCache<String, ARouterGroup> groupLruCache;
    private LruCache<String, ARouterPath> pathLruCache;

    // 为了拼接,例如:ARouter$$Group$$personal
    private final static String FILE_GROUP_NAME = "ARouter$$Group$$";

    private RouterManager() {
        groupLruCache = new LruCache<>(100);
        pathLruCache = new LruCache<>(100);
    }
	// ……

    // 真正的导航
    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    public Object navigation(Context context, BundleManager bundleManager) {
        // 例如:寻找 ARouter$$Group$$personal  寻址   ARouter$$Group$$order   ARouter$$Group$$app
        String groupClassName = context.getPackageName() + "." + FILE_GROUP_NAME + group;
        Log.e("derry >>>", "navigation: groupClassName=" + groupClassName);


        try {
            // TODO 第一步 读取路由组Group类文件
            ARouterGroup loadGroup = groupLruCache.get(group);
            if (null == loadGroup) { // 缓存里面没有东东
                // 加载APT路由组Group类文件 例如:ARouter$$Group$$order
                Class<?> aClass = Class.forName(groupClassName);
                // 初始化类文件
                loadGroup = (ARouterGroup) aClass.newInstance();

                // 保存到缓存
                groupLruCache.put(group, loadGroup);
            }

            // TODO 第二步 读取路由Path类文件
            ARouterPath loadPath = pathLruCache.get(path);
            if (null == loadPath) { // 缓存里面没有东东 Path
                // 1.invoke loadGroup
                // 2.Map<String, Class<? extends ARouterLoadPath>>
                Class<? extends ARouterPath> clazz = loadGroup.getGroupMap().get(group);

                // 3.从map里面获取 ARouter$$Path$$personal.class
                loadPath = clazz.newInstance();

                // 保存到缓存
                pathLruCache.put(path, loadPath);
            }

            // TODO 第三步 跳转
            if (loadPath != null) { // 健壮

                // 执行操作
                RouterBean routerBean = loadPath.getPathMap().get(path);

                if (routerBean != null) {
                    switch (routerBean.getTypeEnum()) {
                        case ACTIVITY:
                            Intent intent = new Intent(context, routerBean.getMyClass()); // 例如:getClazz == Order_MainActivity.class
                            intent.putExtras(bundleManager.getBundle()); // 携带参数
                            // context.startActivity(intent, bundleManager.getBundle()); // 大部分手机有问题,没有任何反应
                            context.startActivity(intent);
                            break;
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }
}

使用 LRU 缓存提高性能

// 提供性能  LRU缓存
private LruCache<String, ARouterGroup> groupLruCache;
private LruCache<String, ARouterPath> pathLruCache;