Android 自定义编译时注解1 - 简单的例子

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gdutxiaoxu/article/details/70244169

为什么要写这一系列的博客呢?

因为在 Android 开发的过程中, 泛型,反射,注解这些知识进场会用到,几乎所有的框架至少都会用到上面的一两种知识,如 Gson 就用到泛型,反射,注解,Retrofit 也用到泛型,反射,注解 。学好这些知识对我们进阶非常重要,尤其是阅读开源框架源码或者自己开发开源框架。

java Type 详解

java 反射机制详解

注解使用入门(一)

Android 自定义编译时注解1 - 简单的例子

Android 编译时注解 —— 语法详解

带你读懂 ButterKnife 的源码

前言

记得去年的时候写过一篇博客 注解使用入门(一),这篇博客主要介绍了注解的一些基本知识,以及基于运行时注解的 Demo。今天这篇博客主要介绍怎样编写编译时注解的Demo。

这篇博客代码参考了鸿洋的博客: Android 打造编译时注解解析框架 这只是一个开始

注解的重要知识

我们先复习一下注解的一些重要知识:

根据注解使用方法和用途,我们可以将Annotation分为三类:

  1. JDK内置系统注解,如 @Override 等
  2. 元注解
  3. 自定义注解,我们自己实现的自定义注解

元注解:

元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它annotation类型作说明。Java5.0定义的元注解: 1. @Target 2. @Retention 3. @Documented 4. @Inherited

元注解 解析说明

  • @Documented 是否会保存到 Javadoc 文档中
  • @Retention 保留时间,可选值 SOURCE(源码时),CLASS(编译时),RUNTIME(运行时) 默认为 CLASS,SOURCE 大都为 Mark Annotation,这类 Annotation 大都用来校验,比如 Override, SuppressWarnings
  • @Target 可以用来修饰哪些程序元素,如 TYPE, METHOD, CONSTRUCTOR, FIELD, PARAMETER 等,未标注则表示可修饰所有 ANONOTATION_TYPE(注解类型声明), PACKAGE(包) TYPE (类,包括enum及接口,注解类型) METHOD (方法) CONSTRUCTOR (构造方法) FIFLD (成员变量) PARAMATER (参数) LOCAL_VARIABLE (局部 变量)
  • @Inherited 是否可以被继承,默认为 false

编译时注解例子说明

这里我们以 AndroidStudio 为例子讲解。假设我们要把 User 这样的一个类,在编译时转化成类似于 json 这样键值对的形式。大概需要三步。

public class Person {
    @Seriable()
    String name;
    @Seriable()
    String area;
    @Seriable()
    int age;
    int weight;

    @Seriable()
    List<Article> mArticleList;
}
{class:"xj.jsonlibdemo.Person",
 fields:
 {
  name:"java.lang.String",
  area:"java.lang.String",
  age:"int",
  mArticleList:"java.util.List<xj.jsonlibdemo.Article>"
 }
}

第一步:我们新建一个 java library,搭配好相关的配置,并编写我们自定义的 Animation Seriable,如下所示

首先:我们新建一个 java library:

接着: 编写我们的自定义注解

@Documented()
// 表示是基于编译时注解的
@Retention(RetentionPolicy.CLASS)
// 表示可以作用于成员变量,类、接口
@Target({ElementType.FIELD, ElementType.TYPE}) 
public @interface Seriable {

}

如果对元注解还步了解的话,建议先阅读我之前写的博客 注解使用入门(一),这里不再讲解

最后:在 resources/META-INF/services/javax.annotation.processing.Processor 文件中 添加 我们自定义注解的全限定路径 com.example.JsonProcessor。注意若 resources/META-INF/services/javax.annotation.processing.Processor 不存在,需要自己添加。

第二步:编写我们的解析器,继承 AbstractProcessor ,并重写 process 方法,处理相关逻辑。

@SupportedAnnotationTypes({"com.example.Seriable"})
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class JsonProcessor extends AbstractProcessor {

    private Elements mElementUtils;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        //  工具辅助类
        mElementUtils = processingEnv.getElementUtils();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        //   第一步,根据我们自定义的注解拿到 elememts set 集合
        Set<? extends Element> elememts = roundEnv.getElementsAnnotatedWith(Seriable.class);
        TypeElement typeElement;
        VariableElement variableElement;
        Map<String, List<VariableElement>> map = new HashMap<>();
        List<VariableElement> fileds = null;
        //  第二步: 根据 element 的类型做相应的处理,并存进 map 集合
        for (Element element : elememts) {
            ElementKind kind = element.getKind();
            // 判断该元素是否为类
            if (kind == ElementKind.CLASS) {
                typeElement = (TypeElement) element;
                //  这里以类的全限定类名作为 key,确保唯一
                String qualifiedName = typeElement.getQualifiedName().toString();
                map.put(qualifiedName, fileds = new ArrayList<VariableElement>());
                // 判断该元素是否为成员变量
            } else if (kind == ElementKind.FIELD) {
                variableElement = (VariableElement) element;
                //                获取该元素的封装类型
                typeElement = (TypeElement) variableElement.getEnclosingElement();
                String qualifiedName = typeElement.getQualifiedName().toString();
                fileds = map.get(qualifiedName);
                if (fileds == null) {
                    map.put(qualifiedName, fileds = new ArrayList<VariableElement>());
                }
                fileds.add(variableElement);
            }
        }

        Set<String> set = map.keySet();

        for (String key : set) {
            if (map.get(key).size() == 0) {
                typeElement = mElementUtils.getTypeElement(key);
                List<? extends Element> allMembers = mElementUtils.getAllMembers(typeElement);
                if (allMembers.size() > 0) {
                    map.get(key).addAll(ElementFilter.fieldsIn(allMembers));
                }
            }
        }
        // 第三步:根据 map 集合数据生成代码
        generateCodes(map);

        return true;
    }

    // 生成我们的代码文件
    private void generateCodes(Map<String, List<VariableElement>> maps) {
        File dir = new File("f://Animation");
        if (!dir.exists())
            dir.mkdirs();
        // 遍历map
        for (String key : maps.keySet()) {

            // 创建文件
            File file = new File(dir, key.replaceAll("\\.", "_") + ".txt");
            try {
                /**
                 * 编写json文件内容
                 */
                FileWriter fw = new FileWriter(file);
                fw.append("{").append("class:").append("\"" + key + "\"")
                        .append(",\n ");
                fw.append("fields:\n {\n");
                List<VariableElement> fields = maps.get(key);

                for (int i = 0; i < fields.size(); i++) {
                    VariableElement field = fields.get(i);
                    fw.append("  ").append(field.getSimpleName()).append(":")
                            .append("\"" + field.asType().toString() + "\"");
                    if (i < fields.size() - 1) {
                        fw.append(",");
                        fw.append("\n");
                    }
                }
                fw.append("\n }\n");
                fw.append("}");
                fw.flush();
                fw.close();

            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }
}

思路解析

  • 第一步,根据我们自定义的注解拿到 elememts set 集合
  • 第二步:根据 elememt 的类型做相应的处理,并存进 map 集合
  • 第三步:根据 map 集合的数据,生成相应的代码。

第三步:调用 gradle build 命令生成 jar 包

在 AndroidStudio 中的 Terminal 窗口输入 gradle build 命令,完成之后将生成 jar 包。我们就可以使用这一个 jar 包了。需要注意的是 我们需要将 gradle 添加到环境变量中。

第四步,将我们生成的 jar 包复制到 moudle 目录下,compile fileTree(dir: ‘libs’, include: [‘*.jar’]) , 就可以使用了。

比如我们新建一个 moudle,新建两个类,如下:

@Seriable
public class Article {
    private String title;
    private String content;
}
public class User {
    @Seriable()
    String name;
    @Seriable()
    String area;
    @Seriable()
    int age;
    int weight;

    @Seriable()
    List<Article> mArticleList;
}

在 moudle 的目录下执行 gradle build 命令,将可以在我们的保存路径中看到我们生成的两个文件,(这个路径是我们前面在编写 JsonProcessor 缩写的,File dir = new File(“f://Animation”);)

{class:"xj.jsonlibdemo.Article",
 fields:
 {
  title:"java.lang.String",
  content:"java.lang.String",
  time:"long"
 }
}
{class:"xj.jsonlibdemo.Person",
 fields:
 {
  name:"java.lang.String",
  area:"java.lang.String",
  age:"int",
  mArticleList:"java.util.List<xj.jsonlibdemo.Article>"
 }
}

到此,一个简单的例子讲解完毕:


参考博客:

Android 打造编译时注解解析框架 这只是一个开始

github 地址

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏pangguoming

Java Annotation 及几个常用开源项目注解原理简析

PDF 版: Java Annotation.pdf, PPT 版:Java Annotation.pptx, Keynote 版:Java Annotatio...

46450
来自专栏程序猿DD

Spring框架中的设计模式(三)

在之前的两篇文章中,我们看到了一些在Spring框架中实现的设计模式。这一次我们会发现这个流行框架使用的3种新模式。 本文将从描述两个创意设计模式开始:原型和...

43170
来自专栏xingoo, 一个梦想做发明家的程序员

【设计模式】—— 组合模式Composite

  模式意图   使对象组合成树形的结构。使用户对单个对象和组合对象的使用具有一致性。   应用场景   1 表示对象的 部分-整体 层次结构   2 忽略组...

19380
来自专栏wannshan(javaer,RPC)

dubbo通信消息解析过程分析(1)

由于rpc底层涉及网络编程接口,线程模型,网络数据结构,服务协议,细到字节的处理。牵涉内容较多,今天就先从一个点说起。 说说,dubbo通过netty框架做传...

54160
来自专栏ImportSource

Junit 5新特性全集

本文略长,但都是大白话,如果你能一口气看完,你赢了。 如果你来不及看这么长,那么建议你滑到文末,直接看黑体部分就知道大概了。 在5中的一个测试类的基本生命周期是...

515120
来自专栏冰霜之地

高效的序列化/反序列化数据方式 Protobuf

上篇文章中其实已经讲过了 encode 的过程,这篇文章以 golang 为例,从代码实现的层面讲讲序列化和反序列化的过程。

66850
来自专栏拭心的安卓进阶之路

使用编译时注解简单实现类似 ButterKnife 的效果

这篇文章是学习鸿洋前辈的 Android 如何编写基于编译时注解的项目 的笔记,用于记录我的学习收获。 什么是编译时注解 上篇文章 什么是注解以及运行时注解的...

50380
来自专栏分布式系统进阶

Kafka中Message存储相关类大揭密Kafka源码分析-汇总

23810
来自专栏Hongten

利用Velocity自动生成自定义代码_java版_源码下载

=======================================================

17820
来自专栏偏前端工程师的驿站

Java魔法堂:JUnit4使用详解

目录                                                                              ...

19850

扫码关注云+社区

领取腾讯云代金券