首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Android 自定义编译时注解1 - 简单的例子

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

作者头像
程序员徐公
发布于 2018-09-17 08:02:46
发布于 2018-09-17 08:02:46
48900
举报
运行总次数:0

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://cloud.tencent.com/developer/article/1340329

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

因为在 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 这样键值对的形式。大概需要三步。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Person {
    @Seriable()
    String name;
    @Seriable()
    String area;
    @Seriable()
    int age;
    int weight;

    @Seriable()
    List<Article> mArticleList;
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{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:

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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 方法,处理相关逻辑。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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,新建两个类,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Seriable
public class Article {
    private String title;
    private String content;
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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”);)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{class:"xj.jsonlibdemo.Article",
 fields:
 {
  title:"java.lang.String",
  content:"java.lang.String",
  time:"long"
 }
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{class:"xj.jsonlibdemo.Person",
 fields:
 {
  name:"java.lang.String",
  area:"java.lang.String",
  age:"int",
  mArticleList:"java.util.List<xj.jsonlibdemo.Article>"
 }
}

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


参考博客:

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

github 地址

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2017年04月19日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
深入探索Java类加载与字节码技术:从字节码指令集到Lambda表达式实现
在Java虚拟机(JVM)的执行过程中,类加载机制扮演着至关重要的角色。这一机制不仅决定了Java程序如何被加载到内存中,还影响着程序的运行效率和安全性。理解类加载机制是深入掌握JVM工作原理的基础,也是后续探讨字节码技术的前提。
用户6320865
2025/08/27
1950
深入探索Java类加载与字节码技术:从字节码指令集到Lambda表达式实现
深入探索Java类加载与字节码技术:双亲委派破坏、SPI机制与OSGi类加载隔离
在Java虚拟机(JVM)的体系结构中,类加载机制是实现动态性和安全性的核心基础设施。当我们在代码中写下new MyClass()时,背后隐藏着一套精密的类加载流程——从字节码文件的定位、验证到内存中的最终成型,整个过程构成了Java"一次编写,到处运行"能力的基石。
用户6320865
2025/08/27
2300
深入探索Java类加载与字节码技术:双亲委派破坏、SPI机制与OSGi类加载隔离
JVM-类加载子系统
​ Java虚拟机将描述类的数据从class字节码文件加载到内存,并且对数据进行校验,转化,解析,初始化的工作,最终形成在内存中可以直接使用的数据类型。这个过程叫做虚拟机的类加载机制。
程序员阿杜
2021/08/03
4930
JVM-类加载子系统
JVM深入原理(六)(一):JVM类加载器
摘星.
2025/05/20
1040
JVM深入原理(六)(一):JVM类加载器
JVM精通面试系列 | 掘金技术征文
JRE 仅包含运行 Java 程序的必需组件,包括 Java 虚拟机以及 Java 核心类库等。我们 Java 程序员经常接触到的 JDK(Java 开发工具包)同样包含了 JRE,并且还附带了一系列开 发、诊断工具。
蒋老湿
2020/03/27
9060
JVM精通面试系列 | 掘金技术征文
JVM-10.类加载
准备是为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中分配。
悠扬前奏
2019/05/28
4450
JVM实战 - 类加载的过程
任何程序都需要加载到内存才能与CPU进行交流 同理, 字节码.class文件同样需要加载到内存中,才可以实例化类 ClassLoader的使命就是提前加载.class 类文件到内存中 在加载类时,使用的是Parents Delegation Model(溯源委派加载模型)
JavaEdge
2019/02/15
1.3K0
JVM实战 - 类加载的过程
彻底搞懂JVM类加载器:基本概念
在Java面试中,在考察完项目经验、基础技术后,我会根据候选人的特点进行知识深度的考察,如果候选人简历上有写JVM(Java虚拟机)相关的东西,那么我常常会问一些JVM的问题。JVM的类加载机制是一个很经典的知识点,围绕这个知识点可以有下面这些难度不同的问题。
阿杜
2019/10/08
7080
彻底搞懂JVM类加载器:基本概念
jvm入门
java字节码:java语言编译成的字节码,.class文件,jvm与java语言无必然联系,只与特定的二进制文件-class文件格式关联。class文件包含了jvm指令集(或称字节码)和符号表,其他辅助信息
用户10832809
2025/02/23
2040
阿里架构师带你深入浅出jvm
本文跟大家聊聊JVM的内部结构,从组件中的多线程处理,JVM系统线程,局部变量数组等方面进行解析 JVM JVM = 类加载器(classloader) + 执行引擎(execution engine
用户1263954
2018/06/22
7740
【JVM】关于JVM,你需要掌握这些!!
作者个人研发的在高并发场景下,提供的简单、稳定、可扩展的延迟消息队列框架,具有精准的定时任务和延迟队列处理功能。自开源半年多以来,已成功为十几家中小型企业提供了精准定时调度方案,经受住了生产环境的考验。为使更多童鞋受益,现给出开源框架地址:
冰河
2020/10/29
4510
【JVM】关于JVM,你需要掌握这些!!
JVM体系结构详解
虚拟机是物理机的软件实现。Java是用WORA(编写一次运行到任何地方)的概念开发的,它在VM上运行。编译器将Java文件编译成Java .class文件,然后将.class文件输入JVM, JVM加载并执行类文件。下面是JVM的架构图。
挨踢小子部落阁
2019/09/25
6480
JVM体系结构详解
JVM 架构解读
每个Java开发人员都知道字节码由JRE(Java运行时环境)执行。但许多人不知道JRE是Java Virtual Machine(JVM)的实现,它分析字节码,解释代码并执行它。作为开发人员,我们应该知道JVM的架构是非常重要的,因为它使我们能够更有效地编写代码。在本文中,我们将更深入地了解Java中的JVM架构和JVM的不同组件。
哲洛不闹
2018/09/18
7050
JVM 架构解读
Java虚拟机JVM介绍
Java虚拟机(JVM)是一个抽象的计算机,它为Java字节码提供了一个运行环境。以下是与JVM相关的一些主要概念:
运维开发王义杰
2023/08/16
2280
Java虚拟机JVM介绍
JVM简介—3.JVM的执行子系统
字节码是各种不同平台虚拟机与所有平台都能统一使用的程序存储格式,所以字节码(ByteCode)是构成平台无关性的基石,是语言无关性的基础。
东阳马生架构
2025/03/11
1970
java-jvm
JVM中类的装载是由ClassLoader和它的子类来实现的,Java ClassLoader 是一个重要的Java运行时系统组件。它负责在运行时查找和装入类文件的类。 Java中的所有类,都需要由类加载器装载到JVM中才能运行。类加载器本身也是一个类,而它的工作就是把class文件从硬盘读取到内存中。在写程序的时候,我们几乎不需要关心类的加载,因为这些都是隐式装载的,除非我们有特殊的用法,像是反射,就需要显式的加载所需要的类。 类装载方式,有两种 (1)隐式装载,程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中,利用反射即隐式加载可绕过一些权限检查机制。 (2)显式装载,通过class.forname()等方法,显式加载需要的类 ,隐式加载与显式加载的区别:两者本质是一样的。 java中类加载是动态的,并不会一次性把所有的类全部加载后再运行,而是保证程序运行的基础类(像是基类)完全加载到jvm中,至于其他类,则在需要的时候才加载。这当然就是为了节省内存开销。
知识浅谈
2021/10/25
5090
java-jvm
深入理解JVM,虚拟机类加载机制
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括以下7个阶段:
李红
2019/09/17
5140
深入理解JVM,虚拟机类加载机制
独特视角解读JVM内存模型
每一个类被加载的时候,java 虚拟机都监视这个类,看它到底是被启动类加载器加载还是用户定义的类加载器加载。当被装载的类引用了另外一个类的时候,虚拟机就会使用装载第一个类的类装载器装载被引用的类。
大忽悠爱学习
2023/03/23
5240
独特视角解读JVM内存模型
JVM体系结构认知
虚拟机 何为虚拟机呢?虚拟机是模拟执行某种指令集体系结构(ISA)的软件,是对操作系统和硬件的一种抽象。其软件模型如下图所示: 计算机系统的这种抽象类似于面向对象编程(OOP)中的针对接口编程泛型(
xiangzhihong
2018/01/26
9170
JVM | 基于openJDK源码深度拆解Java虚拟机
在上一篇文章中,我通过探讨类的生命周期,为你详细解析了类在加载进JVM时的全过程。当然,这仅仅只是JVM虚拟机的冰山一角,像执行引擎的动态编译、垃圾回收系统的内存管理、本地方法接口的与本地库的交互,以及本地方法库的结构和功能等诸多核心内容还未涉及。
kfaino
2023/10/08
1.8K2
JVM | 基于openJDK源码深度拆解Java虚拟机
相关推荐
深入探索Java类加载与字节码技术:从字节码指令集到Lambda表达式实现
更多 >
LV.1
这个人很懒,什么都没有留下~
目录
  • 前言
  • 注解的重要知识
    • 根据注解使用方法和用途,我们可以将Annotation分为三类:
    • 元注解:
  • 编译时注解例子说明
    • 第一步:我们新建一个 java library,搭配好相关的配置,并编写我们自定义的 Animation Seriable,如下所示
    • 第二步:编写我们的解析器,继承 AbstractProcessor ,并重写 process 方法,处理相关逻辑。
    • 第三步:调用 gradle build 命令生成 jar 包
    • 第四步,将我们生成的 jar 包复制到 moudle 目录下,compile fileTree(dir: ‘libs’, include: ‘*.jar’) , 就可以使用了。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档