0
点赞
收藏
分享

微信扫一扫

JavaPoet APT (二) 使用

了解组件化的架构

组件化,业务组件横向是不能有依赖,也就是需要解决组件通信的问题,所以我们要用到路由,业务组件通过路由转发功能和其他组件通信

应用AutoService Processor 写自己的路由

  1. Path 与 Group 的映射关系

  1. Path的生成管理 和 Group 的生成管理
  • Path仓库 Group仓库 定义两个仓库存储,为了后续的管理工作 - - 准备工作: 路由对象封装,校验作用域为Activity,Fragment等需要的内容
  • 校验路由对象 -> checkRoutePath(routeBean),赋值给仓库Path
  • 生成一系列的Path
  • 生成一系列的Group ---> 对应上Path

Group和Path 仓库其实就是内存缓存。通过Map保存

我们要借助JavaPoet 生成的Gropu和Path目标class

最终生成的效果:Group

public class ARouter$$Group$$personal implements ARouterGroup {
  @Override
  public Map<String, Class<? extends ARouterPath>> getGroupMap() {
    Map<String, Class<? extends ARouterPath>> groupMap = new HashMap<>();
    groupMap.put("personal", ARouter$$Path$$personal.class);
    return groupMap;
  }
}

最终生成的效果:Path

public class ARouter$$Path$$personal implements ARouterPath {
  @Override
  public Map<String, RouterBean> getPathMap() {
    Map<String, RouterBean> pathMap = new HashMap<>();
    pathMap.put("/personal/Personal_Main2Activity", RouterBean.create();
    pathMap.put("/personal/Personal_MainActivity", RouterBean.create());
    return pathMap;
  }
}

核心arouter_compiler
我们需要继承AbstractProcccessor,并进行配置


// AutoService则是固定的写法,加个注解即可
// 通过auto-service中的@AutoService可以自动生成AutoService注解处理器,用来注册
// 用来生成 META-INF/services/javax.annotation.processing.Processor 文件
@AutoService(Processor.class)
// 允许/支持的注解类型,让注解处理器处理
@SupportedAnnotationTypes({ProcessorConfig.AROUTER_PACKAGE})
// 指定JDK编译版本
@SupportedSourceVersion(SourceVersion.RELEASE_8)
// 注解处理器接收的参数
@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;
    // 各个模块传递过来的模块名 例如:app order personal ---- 这个配合Gradle配置
    private String options;
    // 各个模块传递过来的目录 用于统一存放 apt生成的文件
    private String aptPackage;

    // 仓库1,Path 缓存 -> Map<"personal",Listt<RouteBean>>
    private Map<String, List<RouterBean>> mAllPathMaps = new HashMap<>();

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

有两个重要的函数init和process核心处理
初始化工作

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


    }

核心process

/**
     * 相当于main函数,开始处理注解
     * 注解处理器的核心方法,处理具体的注解,生成Java文件
     *
     * @param annotations 使用了支持处理注解的节点集合
     * @param roundEnv    当前或是之前的运行环境,可以通过该对象查找的注解。
     * @return true 表示后续处理器不会再处理(已经处理完成)
     */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

        if (annotations.isEmpty()) {
            messager.printMessage(Diagnostic.Kind.NOTE, "未发现@Arouter注解的地方");
            return false;
        }

        // 获取所有被@Arouter 注解的元素集合
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(ARouter.class);
        // 通过Element工具类,获取Activity,Callback类型
        TypeElement activityType = elementTool.getTypeElement(ProcessorConfig.ACTIVITY_PACKAGE);
        // 显示类信息(获取被注解的节点,类节点)这也叫自描述 Mirror
        TypeMirror activityMirror = activityType.asType();

        // 遍历所有的类节点 包含注解的
        for (Element element : elements) {
            // 获取类节点,获取包节点
            String packageName = elementTool.getPackageOf(element).getQualifiedName().toString();
            // 获取简单类名,例如:MainActivity
            String className = element.getSimpleName().toString();
            messager.printMessage(Diagnostic.Kind.NOTE, ">>>>>>>>>>>>>>for 被@ARetuer注解的类有:" + className); // 打印出 就证明APT没有问题
            messager.printMessage(Diagnostic.Kind.NOTE, ">>>>>>>>>>>>>>for Options:" + options); // 打印出 就证明APT没有问题
            // 拿到注解
            ARouter aRouter = element.getAnnotation(ARouter.class);
            // TODO: 1.准备RouterBean 检查参数和路径
            RouterBean routerBean = new RouterBean.Builder()
                    .addGroup(aRouter.group())
                    .addPath(aRouter.path())
                    .addElement(element)
                    .build();
          ...................检查路径
        }
        // TODO: 2.  第一步:生成系列PATH
          ...................PATH生成

        // TODO: 3. 第二步:生成组Group
          ...................Group生成
        elements.clear();
        return true;
    }
}
// 我们自己定义:ARouter注解的类 必须继承 Activity
            // Main2Activity的具体详情 例如:继承了 Activity
            TypeMirror elementMirror = element.asType();
            // activityMirror  android.app.Activity描述信息
            if (typeTool.isSubtype(elementMirror, activityMirror)) {
                routerBean.setTypeEnum(RouterBean.TypeEnum.ACTIVITY);
            } else {
                // 我们测试,随便写一个类DavidTest.java 加上@Arouter注解,但是不满足我们规则typeEnum
                // 主动抛出异常
                throw new RuntimeException("@Arouter 注解目前仅限用于Activity类上");
            }

            if (checkRouterPath(routerBean)) {
                messager.printMessage(Diagnostic.Kind.NOTE, "RouterBean Check Success:" + routerBean.toString());
                // 赋值到 mAllPathMap 集合
                List<RouterBean> routerBeans = mAllPathMaps.get(routerBean.getGroup());
                // 如果从Map中找不到key为:bean.getGroup()的数据,就新建List集合再添加进Map
                if(ProcessorUtils.isEmpty(routerBeans)){
                    routerBeans = new ArrayList<>();
                    routerBeans.add(routerBean);
                    mAllPathMaps.put(routerBean.getGroup(), routerBeans);
                } else {
                    routerBeans.add(routerBean);
                }
            } else {
                // ERROR 编译期发生异常
                messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解未按规范配置,如:/app/MainActivity");
            }

检测Router 的path

  /**
     * 校验@ARouter注解的值,如果group未填写就从必填项path中截取数据
     *
     * @param routerBean 路由详细信息,最终实体封装类
     */
    private boolean checkRouterPath(RouterBean routerBean) {
        // "app"   "order"   "personal"
        String group = routerBean.getGroup();
        // "/app/MainActivity"   "/order/Order_MainActivity"   "/personal/Personal_MainActivity"
        String path = routerBean.getPath();
        // 校验  @ARouter注解中的path值,必须要以 / 开头(模仿阿里Arouter规范)
        if(ProcessorUtils.isEmpty(path) || !path.startsWith("/")){//TextUtils Java 工程无法引用,所以封装processutils
            messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解中的path值,必须要以 / 开头");
        }

        // 因为用户使用 @ARouter(path="/app/MainActivity" group="app") 我们默认省略group,替业务程序取出组名app
        String finalGroup = path.substring(1, path.indexOf("/",1));

        // 检查 @ARouter注解中group赋值情况
        if(!ProcessorUtils.isEmpty(group) && !group.equals(options)){
            // 架构师定义规范,让开发者遵循
            messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解中的group值必须和子模块名一致!");
            return false;
        } else {
            routerBean.setGroup(finalGroup);
        }

        return true;
    }
// TODO: 2. 第一步:生成系列PATH
        try {
            createPathFile(pathType); // 生成 Path类
        } catch (IOException e) {
            e.printStackTrace();
            messager.printMessage(Diagnostic.Kind.NOTE, "在生成PATH模板时,异常了 e:" + e.getMessage());
        }

--------------------

 /**
     * 系列Path的类  生成工作
     *
     * @param pathType ARouterPath 高层的标准
     * @throws IOException
     */
    private void createPathFile(TypeElement pathType) throws IOException {
        if (ProcessorUtils.isEmpty(mAllPathMaps)) {
            return;
        }

        TypeName methodReturn = ParameterizedTypeName.get(
                ClassName.get(Map.class),//Map
                ClassName.get(String.class),//Map<String,
                ClassName.get(RouterBean.class)//Map<String,RouterBean>
        );

        // 遍历仓库 app,order,personal
        for (Map.Entry<String, List<RouterBean>> entry : mAllPathMaps.entrySet()) {
            // 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<>(); 如果是$T class类型一般要包装一下
            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<>();
            );

            // 循环添加map赋值
            List<RouterBean> pathList = entry.getValue();
            //  pathMap.put("/app/Main2Activity", RouterBean.create(RouterBean.TypeEnum.ACTIVITY, Main2Activity.class, "/app/Main2Activity", "app"));
            for (RouterBean bean : pathList) {
                methodBuilder.addStatement("$N.put($S, $T.create($T.$L,$T.class,$S,$S))",
                        ProcessorConfig.PATH_VAR1,
                        bean.getPath(),
                        ClassName.get(RouterBean.class),
                        ClassName.get(RouterBean.TypeEnum.class),
                        bean.getTypeEnum(),
                        ClassName.get((TypeElement) bean.getElement()),
                        bean.getPath(),
                        bean.getGroup()
                );
            }// end for

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

            // TODO 注意:不能像以前一样,1.方法,2.类  3.包, 因为这里面有implements ,所以 方法和类要合为一体生成才行,这是特殊情况
            String finalClassName = ProcessorConfig.PATH_FILE_NAME + entry.getKey();

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

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

// 仓库二 缓存二  非常重要一步,注意:PATH 路径文件生成出来了,才能赋值路由组mAllGroupMap
            mAllGroupMap.put(entry.getKey(), finalClassName);
        }
    }


 try {
            createGroupFile(groupType, pathType);
        } catch (IOException e) {
            e.printStackTrace();
            messager.printMessage(Diagnostic.Kind.NOTE, "在生成GROUP模板时,异常了 e:" + e.getMessage());
        }

------------
 /**
     * 生成路由组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(mAllPathMaps)) {
            return;
        }


        TypeName methodReturn = ParameterizedTypeName.get(
                ClassName.get(Map.class),//Map
                ClassName.get(String.class),//Map<String,
                // Class<? extends ARouterPath>> 难度
                ParameterizedTypeName.get(ClassName.get(Class.class),
                        WildcardTypeName.subtypeOf(ClassName.get(pathType)))//? extends ARouterPath
                // WildcardTypeName.supertypeOf() 做实验 ? super
        );

        // 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(methodReturn); // 方法返回值
        //Map<String, Class<? extends ARouterPath>> groupMap = new HashMap<>();
        methodBuidler.addStatement("$T<$T, $T> $N = new $T<>()",
                ClassName.get(Map.class),
                ClassName.get(String.class),
                ParameterizedTypeName.get(ClassName.get(Class.class),
                        WildcardTypeName.subtypeOf(ClassName.get(pathType))),
                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$$persional
        JavaFile.builder(aptPackage,
                TypeSpec.classBuilder(finalClassName)
                        .addSuperinterface(ClassName.get(pathType))// 实现ARouterLoadPath接口  implements ARouterPath==pathType
                        .addModifiers(Modifier.PUBLIC)
                        .addMethod(methodBuidler.build())// 方法的构建(方法参数 + 方法体)
                        .build())// 类构建完成
                .build()// JavaFile构建完成
                .writeTo(filer);// 文件生成器开始生成类文件
    }

------------------------补充----------------------------

RouteBean 的封装

/**
 * 最终路由 要 传递 对象
 * <p>
 * 路由路径Path的最终实体封装类
 * 例如:app分组中的MainActivity对象,这个对象有更多的属性
 */
public class RouterBean {
    // 为了以后的扩展
    public enum TypeEnum {
        ACTIVITY
    }

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

    public RouterBean(TypeEnum typeEnum, /*Element element,*/ Class<?> myclass, String path, String group) {
        this.typeEnum = typeEnum;
//        this.element = element;
        this.myclass = myclass;
        this.path = path;
        this.group = group;
    }

    // 对外暴露
    // 对外提供简易版构造方法,主要是为了方便APT生成代码
    public static RouterBean create(TypeEnum type, Class<?> clazz, String path, String group) {
        return new RouterBean(type, clazz, path, group);
    }

    public RouterBean(Builder builder) {
        this.typeEnum = builder.type;
//        this.element = builder.element;
        this.myclass = builder.clazz;
        this.path = builder.path;
        this.group = builder.group;
    }

    public TypeEnum getTypeEnum() {
        return typeEnum;
    }

    public Element getElement() {
        return element;
    }

    public Class<?> getMyclass() {
        return myclass;
    }

    public String getPath() {
        return path;
    }

    public String getGroup() {
        return group;
    }

    public static class Builder {
        // 枚举类型:Activity
        private TypeEnum type;
        // 类节点
        private Element element;
        // 注解使用的类对象
        private Class<?> clazz;
        // 路由地址
        private String path;
        // 路由组
        private String group;

        public Builder addType(TypeEnum type) {
            this.type = type;
            return this;

        }

        public Builder addElement(Element element) {
            this.element = element;
            return this;

        }

        public Builder addClazz(Class<?> clazz) {
            this.clazz = clazz;
            return this;

        }

        public Builder addPath(String path) {
            this.path = path;
            return this;

        }

        public Builder addGroup(String group) {
            this.group = group;
            return this;
        }

        public RouterBean build() {

            if(path == null || path.length() == 0){
                throw new IllegalArgumentException("path必填项为空,应该为如:/app/MainActivity");
            }
            return new RouterBean(this);
        }
}

知识点

Diagnostic.Kind.ERROR
还有这些 日志级别打印:WARNING,MANDATORY_WARNING,NOTE,OTHER

messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解中的path值,必须要以 / 开头");

Map<String, RouterBean> getPathMap() 生成

TypeName非常重要,在使用javapoet时经常需要它。它可以标识任何声明的类。声明的类型只是Java富类型系统的开始:我们还有数组、参数化类型、通配符类型和类型变量

TypeName methodReturn = ParameterizedTypeName.get(
                ClassName.get(Map.class),//Map
                ClassName.get(String.class),//Map<String,
       ClassName.get(RouterBean.class)//Map<String,RouterBean>
                );

占位 $T $S $N $L
$T 类class
$N 变量
$L 在枚举代码中,我们使用TypeSpec.anonymousInnerClass(). 匿名内部类也可以用在代码块中。它们是可以用$L引用的值
$S 字符串

// Map<String, RouterBean> pathMap = new HashMap<>(); 如果是$T class类型一般要包装一下
            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<>();
                    );

JavaPoet 语法挺绕口的,可以参见github官网文档

https://github.com/square/javapoet

举报

相关推荐

0 条评论