Java编译注解-自动生成代码

3,575 阅读5分钟

Android

世界这么大,又有谁能了解我呢

Annotation注解在Android的开发中的使用越来越普遍,例如EventBus、ButterKnife、Dagger2等,记一次使用插件annotationProcessor实现下载监听注解框架,为什么不用android-apt呢,我不会告诉你android-apt不再维护了。


Javadoc代码注解-代码文档:传送门


注解简介

注解

Annontation是Java5开始引入的新特征,中文名称叫注解。它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,并且供指定的工具或框架使用。Annontation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。

注解是一种元数据, 可以添加到java代码中. 类、方法、变量、参数、包都可以被注解,注解对注解的代码没有直接影响。之所以产生作用, 是对其解析后做了相应的处理. 注解仅仅只是个标记。

定义注解用的关键字是@interface

注解的用处

  • 生成文档,编译进行检查。这是最常见的,即Javadoc代码注解
  • 跟踪代码依赖性,实现替代配置文件功能。如EventBus、ButterKnife、Dagger2依赖注入等,本篇记录一次编译注解使用过程

元注解

java.lang.annotation提供了四种元注解,专门注解其他的注解:

  • @Documented –注解是否将包含在Javadoc文档中

  • @Retention –什么时候使用该注解,有三种选择,默认为CLASS

    • RetentionPolicy.SOURCE : 在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。
    • RetentionPolicy.CLASS : 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式
    • RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。
  • @Target –表示该注解用于什么地方。默认值为任何元素,表示该注解用于什么地方。可用的ElementType参数包括

    • ElementType.CONSTRUCTOR:用于描述构造器
    • ElementType.FIELD:成员变量、对象、属性(包括enum实例)
    • ElementType.LOCAL_VARIABLE:用于描述局部变量
    • ElementType.METHOD:用于描述方法
    • ElementType.PACKAGE:用于描述包
    • ElementType.PARAMETER:用于描述参数
    • ElementType.TYPE:用于描述类、接口(包括注解类型) 或enum声明
  • @Inherited – 是否允许子类继承该注解,默认为false

Android中使用编译时注解,自动生成代码

这里只讲解编译注解,其他注解待续~~~


Downloader源码传送门


Annotations项目结构

  • DownloadAnnotations:Java工程,申明使用的注解
  • DownloadCompiler:Java工程,用于编译期间自动生成代码
  • DownloaderLibrary:下载依赖库
  • Demo:测试示例

创建自定义注解

创建Java工程:DownloadAnnotations

  • 声明一个Download注解,声明周期为Class,作用域为方法

    @Retention(RetentionPolicy.CLASS)
    @Target(ElementType.METHOD)
    public @interface Download
    {
    	/**
    	 * 下载开始,获取文件大小
    	 * @see com.excellence.downloader.FileDownloader.DownloadTask#getFileSize()
    	 *
    	 */
    	@Retention(RetentionPolicy.CLASS)
    	@Target(ElementType.METHOD)
    	@interface onPreExecute
    	{
    		String[] value() default { NO_URL };
    	}
    }
  • DownloadAnnotations的build.gradle配置

    apply plugin: 'java'
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
    }
    tasks.withType(JavaCompile) {
        options.encoding = "UTF-8"
    }
    sourceCompatibility = JavaVersion.VERSION_1_7
    targetCompatibility = JavaVersion.VERSION_1_7

解析编译时注解

创建Java工程:DownloadCompiler

解析编译时注解需要继承AbstractProcessor,并且使用注解@AutoService(Processor.class),该注解在编译时自动执行被注解的类,即自动执行该类的Java程序,用于创建类文件。

  • DownloadProcessor事件注解扫描器

    @AutoService(Processor.class)
    public class DownloadProcessor extends AbstractProcessor
    {
    	private ElementHandler mElementHandler = null;
    	/**
    	 * 每一个注解处理器类都必须有一个空的构造函数。然而,这里有一个特殊的{@link #init}方法,它会被注解处理工具调用,并输入{@link ProcessingEnvironment}参数。{@link ProcessingEnvironment}提供很多有用的工具类{@link Elements}、{@link Types}、{@link Filer}。
    	 *
    	 * @param processingEnvironment
    	 */
    	@Override
    	public synchronized void init(ProcessingEnvironment processingEnvironment)
    	{
    		super.init(processingEnvironment);
    		mElementHandler = new ElementHandler(processingEnvironment.getFiler(), processingEnvironment.getElementUtils());
    	}
    	/**
    	 * 必须指定,这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称。换句话说,在这里定义你的注解处理器注册到哪些注解上。
    	 *
    	 * @return
    	 */
    	@Override
    	public Set<String> getSupportedAnnotationTypes()
    	{
    		Set<String> annotations = new LinkedHashSet<>();
    		annotations.add(Download.onPreExecute.class.getCanonicalName());
    		return annotations;
    	}
    	/**
    	 * 用来指定你使用的Java版本。
    	 *
    	 * @return
    	 */
    	@Override
    	public SourceVersion getSupportedSourceVersion()
    	{
    		return SourceVersion.latestSupported();
    	}
    	/**
    	 * 这相当于每个处理器的主函数main()。 在这里写扫描、评估和处理注解的代码,以及生成Java文件。输入参数{@link RoundEnvironment},可以让查询出包含特定注解的被注解元素。
    	 * 该方法返回ture表示该注解已经被处理, 后续不会再有其他处理器处理; 返回false表示仍可被其他处理器处理
    	 *
    	 * @param set
    	 * @param roundEnv
    	 * @return
    	 */
    	@Override
    	public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv)
    	{
    		mElementHandler.clean();
    		mElementHandler.handleDownload(roundEnv);
    		mElementHandler.createProxyFile();
    		return true;
    	}
    }
  • ElementHandler元素处理(代码过长,请查看源码)

    • 编译时自动创建事件代理的类文件的管理类,用于管理有注册代理的类,生成路径:Demo/build/generated/source/apt/debug/com/excellence/downloader/ProxyClassCounter
      ProxyClassCounter

    • 编译时自动创建事件代理的类文件,用于发送监听接口,生成类文件的路径:Demo/build/generated/source/apt/debug/com/zv/downloader/downloader/SingleThreadActivity?DownloadListenerProxy
      SingleThreadActivity?DownloadListenerProxy

  • DownloadCompiler的build.gradle配置

    • com.google.auto.service:auto-service:1.0-rc2 谷歌提供的Java 生成源代码库
    • com.squareup:javapoet:1.9.0 提供了各种 API 让你用各种姿势去生成 Java 代码文件,javapoet传送门
      apply plugin: 'java'
      dependencies {
          compile fileTree(dir: 'libs', include: ['*.jar'])
          compile 'com.google.auto.service:auto-service:1.0-rc2'
          compile 'com.squareup:javapoet:1.9.0'
          compile project(':DownloadAnnotations')
      }
      tasks.withType(JavaCompile) {
          options.encoding = "UTF-8"
      }
      sourceCompatibility = JavaVersion.VERSION_1_7
      targetCompatibility = JavaVersion.VERSION_1_7

到此注解生成代码过程结束

Downloader依赖库处理注解

  • 根据DownloadCompile里的ElementHandler类创建注解接口ISchedulerListener

    public interface ISchedulerListener<TASK>
    {
    	void onPreExecute(TASK task);
    	void onProgressChange(TASK task);
    	void onProgressSpeedChange(TASK task);
    	void onCancel(TASK task);
    	void onError(TASK task);
    	void onSuccess(TASK task);
    }
  • DownloadScheduler事件调度器,注册、解绑、监听,用于处理任务状态的调度

    /**
     * 初始化代理参数,获取注解类集合
     */
    private void initDownloadCounter()
    {
    	try
    	{
    		Class clazz = Class.forName("com.excellence.downloader.ProxyClassCounter");
    		Method download = clazz.getMethod("getDownloadCounter");
    		Object object = clazz.newInstance();
    		Object downloadCounter = download.invoke(object);
    		if (downloadCounter != null)
    			mDownloadCounter = unmodifiableSet((Set<String>) downloadCounter);
    	}
    	catch (ClassNotFoundException e)
    	{
    		e.printStackTrace();
    	}
    	catch (NoSuchMethodException e)
    	{
    		e.printStackTrace();
    	}
    	catch (InstantiationException e)
    	{
    		e.printStackTrace();
    	}
    	catch (IllegalAccessException e)
    	{
    		e.printStackTrace();
    	}
    	catch (InvocationTargetException e)
    	{
    		e.printStackTrace();
    	}
    }

Demo使用注解监听

使用方式

// 注册
Downloader.register(this);
// 解绑
Downloader.unregister(this);
// 监听
@Download.onPreExecute
public void onPre(DownloadTask task)
{
	/**
	 * 注解参数不添加URL,则获取全部任务的下载监听;
	 * 加了URL,则过滤出对应的任务的下载监听
	 * 如:@Download.onPreExecute({URL1, URL2})
	 */
}

感谢