abstractprocessor:利用注解动态生成代码

ZhangJianIsAStark ZhangJianIsAStark     2022-12-10     352

关键词:

按照处理时期,注解分为两种类型,一种是运行时注解,另一种是编译时注解。

编译时注解的核心依赖APT(Annotation Processing Tools)实现,对应的处理流程为:
在某些代码元素上(如类型、函数、字段等)添加注解;
编译时编译器会检查AbstractProcessor的子类,
然后将添加了注解的所有元素都传递到该类的process函数中;
使得开发人员可以在编译器进行相应的处理。
例如,根据注解生成新的Java类,
这也就是EventBus,Retrofit,Dragger等开源库的基本原理。

本篇博客就从一个简单的例子入手,
看看如何利用AbstractProcessor和注解来动态生成代码。


一、创建Java Library
Java API已经提供了扫描源码并解析注解的框架,
我们只需要继承AbstractProcessor类来实现解析注解相关的逻辑。
因此,我们一般需要自己创建一个Java Library。

考虑到兼容性问题,在Java Library对应的build.gradle中可以添加如下字段:

sourceCompatibility = "1.7"
targetCompatibility = "1.7"

之后,我们就可以简单的创建一个注解:

package com.zhangjian;

/**
 * @author zhangjian on 18-3-23.
 */

public @interface CustomAnnotation 

然后创建对应的注解处理器:

//指定该注解处理器可以解决的类型,需要完整的包名+类命
@SupportedAnnotationTypes("com.zhangjian.CustomAnnotation")
//指定编译的JDK版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class CustomAnnotationProcessor extends AbstractProcessor

    //这里就是处理注解的process函数
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) 
        //创建动态代码,实际上就是创建一个String, 写入到文件里
        //然后文件会被解释为.class文件

        StringBuilder builder = new StringBuilder()
                .append("package com.zhangjian.annotationprocessor.generated;\\n\\n")
                .append("public class GeneratedClass \\n\\n")
                .append("\\tpublic String getMessage() \\n")
                .append("\\t\\treturn \\"");

        //获取所有被CustomAnnotation修饰的代码元素
        for (Element element : roundEnvironment.getElementsAnnotatedWith(CustomAnnotation.class)) 
            String objectType = element.getSimpleName().toString();
            builder.append(objectType).append(" exists!\\\\n");
        

        builder.append("\\";\\n")
                .append("\\t\\n")
                .append("\\n");

        //将String写入并生成.class文件
        try 
            JavaFileObject source = processingEnv.getFiler().createSourceFile(
                    "com.zhangjian.annotationprocessor.generated.GeneratedClass");

            Writer writer = source.openWriter();
            writer.write(builder.toString());
            writer.flush();
            writer.close();
         catch (IOException e) 
            //
        

        return true;
    

接着,需要在Java Module的main目录下,创建出resources目录;
在resources目录下创建出META-INF目录;
并在META-INF目录下创建出services目录。
最后,在services目录下创建出名为javax.annotation.processing.Processor的文件。
在其中申明我们的注解处理器:

com.zhangjian.CustomAnnotationProcessor

二、使用Java Library
创建完Java Library后,我们就可以使用了。
在app module对应的build.gradle中,添加类似如下语句:

task processorTask(type: Exec) 
    //将编译出的java library对应的jar包,复制到app modulelibs
    commandLine 'cp', '../processor/build/libs/processor.jar', 'libs/'


processorTask.dependsOn(':processor:build')
preBuild.dependsOn(processorTask)

此外,还需要指定依赖文件并考虑兼容性,需要在build.gradle中添加:

android 
    ............
    defaultConfig 
        ........
        //由于我们是自己创建的annotationProcessor, 且在编译时使用
        //因此需要在此申明
        //不过这个字段在高版本的gradle中已经是deprecated了
        javaCompileOptions 
            annotationProcessorOptions 
                includeCompileClasspath true
            
        
    
    ............
    compileOptions 
        sourceCompatibility JavaVersion.VERSION_1_7
        targetCompatibility JavaVersion.VERSION_1_7
    


dependencies 
    ..........
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    annotationProcessor "com.neenbedankt.gradle.plugins:android-apt:1.8"

如上注释,在高版本的gradle中建议以如下方式,引入自定义的annotationProcessor:

dependencies 
    ........
    annotationProcessor files("libs/processor.jar")

接下来我们就可以在代码中使用注解了:

/**
 * @author zhangjian
 */
@CustomAnnotation
public class MainActivity extends AppCompatActivity 
    @Override
    @CustomAnnotation
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);

        //GeneratedClass是动态生成的
        Log.v("Test", new GeneratedClass().getMessage());
    

我们rebuild一下app module就可以看到对应动态生成的代码,
在build/source/apt目录下:

package com.zhangjian.annotationprocessor.generated;

public class GeneratedClass 
    public String getMessage() 
        return "MainActivity exists!\\nonCreate exists!\\n";
    

三、总结
至此,我们大概了解如何利用注解动态生成代码了。
按照套路来,应该还是比较容易理解和使用的。

升级你的logger(注解+动态代理)

...现。我们在自定义Log的时候,应该考虑到的几点:描述:利用注解描述一个类型的日志,利用动态代理生成一种日志类型。直接看demo,简单粗暴:日志等级枚举:普通的日志类,可以实例化之后使用:Config注解,描述一种类型... 查看详情

源码级注解(代码片段)

...写代码之前在开始写代码之前,我们需要了解一个叫AbstractProcessor的类。AbstractProcessor(虚处理器),是注解处理器核心API。注解处理器需要继承于AbstractProcessor,如下所示 查看详情

怎么爬取网页的动态内容,很多都是js动态生

...向工程获取动态数据接口(真实的访问路径),另一种是利用selenium库模拟真实浏览器,获取JavaScript渲染后的内容。但selenium库用起来比较繁琐,抓取速度相对较慢,所以第一种方法日常使用较多。参考技术A获取不到动态加载的... 查看详情

javajavajsr269简介(代码片段)

...至生成最终的class文件。实现注解处理器的第一步是继承AbstractProcessor类,实现它的process方法,如下面的代码清单8-1所示。SupportedAnnotationTypes 查看详情

mybatis之动态构建sql语句

今天一个新同事问我,我知道如何利用XML的方式来构建动态SQL,可是Mybatis是否能够利用注解完成动态SQL的构建呢?!!答案是肯定的,MyBatis提供了注解,@InsertProvider,@UpdateProvider,@DeleteProvider和@SelectProvider,来帮助构建动态SQL语句,... 查看详情

android注解框架butterknife的核心代码分析笔记(代码片段)

...心类。2、1 首先他集成了JAVA处理注解所用到的抽象类 AbstractProcessor  关于AbstractProcessor的主要功能的说明,做下面的截图的总结:看一下ButterKnifeProcessor的基本常量有哪些:publicstaticfinalStringSUFFIX="$$ViewInjector&#... 查看详情

android注解框架butterknife的核心代码分析笔记(代码片段)

...心类。2、1 首先他集成了JAVA处理注解所用到的抽象类 AbstractProcessor  关于AbstractProcessor的主要功能的说明,做下面的截图的总结:看一下ButterKnifeProcessor的基本常量有哪些:publicstaticfinalStringSUFFIX="$$ViewInjector&#... 查看详情

springboot整合mybatis基于注解开发以及动态sql的使用

...ation.properties),而今天我们就是来简化mybatis的工作的——利用注解替代xml配置文件。  先贴出mapper接口代码@MapperpublicinterfaceUserMapper{//获取用户名单publicList<U 查看详情

注解深入浅出(二apt)

...反射在运行时处理注解大大提高了程序性能。APT的核心时AbstractProcessor类,关于AbstractProcessor类后面会做详细说明。2.2哪里用到了APT?APT技术被广泛的运用在Java框架中,包括Android项目以及Java后台项目,除了ButterKni... 查看详情

@lombok注解背后的原理是什么,让我们走近自定义java注解处理器(代码片段)

...义注解处理器,这里我们采用更简单的方法通过继承AbstractProcessor类实现自定义注解处理器。实现抽象方法process处理我们想要的功能。publicclassCustomProcessorextendsAbstractProcessor@Overridepublicbooleanprocess(Set<?extendsTypeElement>anno... 查看详情

自定义注解并进行动态解析(代码片段)

...一个控件自动绑定功能(老版本butterknife,新版本改成利用IDE插件预先编译了)以及通过注解设置activity主布局,通过这两个小例子来学习如何自定义自己的动态注解首先我们定义一个注解。还叫BindView吧/***Createdbygengqiquanon20... 查看详情

自定义注解并进行动态解析(代码片段)

...一个控件自动绑定功能(老版本butterknife,新版本改成利用IDE插件预先编译了)以及通过注解设置activity主布局,通过这两个小例子来学习如何自定义自己的动态注解首先我们定义一个注解。还叫BindView吧/***Createdbygengqiquanon20... 查看详情

变量的生存期和存储分配

...内存空间的时间段称为生存期,分为三类:静态生存期、动态生存期、自动生存期。  全局变量具有静态生存期,局部变量和函数的参数一般具有自动生存期,对于具有动态生存期的变量,其内存空间一般用new操作分配,用del... 查看详情

计算机程序的思维逻辑(84)-反射

...些动态特性,包括反射、类加载器、注解和动态代理等。利用这些特性,可以以优雅的方式实现一些灵活和通用的功能,经常用于各种框架、库和系统程序中,比如:在63节介绍的实用序列化库Jackson,利用反射和注解实现了通用... 查看详情

注解与反射(代码片段)

...许多的注解,通过反射读取注解的值,来简化操作.? 比如利用反射读取注解的值,通过值拼成SQL语句,就可以动态地生成表,或者其他高级的功能.什么是注解(Annotation)Annotation的作用:可以被其他程序(比如:编译器等)读取Annotation在... 查看详情

anroidapt(代码片段)

...理器实现自己的注解处理器,我们需要定义一个类去继承AbstractProcessor类处理器的写法有固定的套路,示例代码如下publicclassMyProcessorextendsAbstractProcessor@Overridepublicsynchronizedvoidinit(ProcessingEnvironmentprocessingEnv)super.init(processingEnv);@Over... 查看详情

anroidapt(代码片段)

...理器实现自己的注解处理器,我们需要定义一个类去继承AbstractProcessor类处理器的写法有固定的套路,示例代码如下publicclassMyProcessorextendsAbstractProcessor@Overridepublicsynchronizedvoidinit(ProcessingEnvironmentprocessingEnv)super.init(processingEnv);@Over... 查看详情

利用反射调用注解

 利用反射调用注解  packagenet.jeesite.java;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;importjava.lang.reflect.Method;@Retention(value=RetentionPolicy.RUNTIME)@interfa 查看详情