<commit_before>---
title:        "注解处理器"
description:  "注解是Java 5就出来的一个javac工具，被广泛运用在很多框架与项目上，本文是一篇翻译学习博客"
image:        "http://placehold.it/400x200"
author:       "<NAME>"
date:         "2017-06-28"
---
# 注解处理器
## 写在前面的
为啥会写这篇博客呢，因为别人都用啊~~ 当然，正经说，注解处理器是运用在编译时期，它现在被大家发掘的用法是来生成一些文件，无论是java还是xml还是简单的键值对文档。那么这个的作用就很明显了，它可以帮我们来生成一些固定的东西，例如重复的代码逻辑，重复的配置文件。这么抽象？不不不，你看看`ButterKnife`、`Dagger`，虽然最后生成出来的代码可能比你一行一行硬写的要多不少，但是嘛，程序员时间才是最宝贵的。老写一样的代码这种体力劳动，嘿嘿...

这篇文章嘛主要是大致翻译的[ANNOTATION PROCESSING101](http://hannesdorfmann.com/annotation-processing/annotationprocessing101)，也有其他博主前辈翻译过啊（他们是正儿八经翻译，我是纯扯淡...）戳链接[Java注解处理器](https://www.race604.com/annotation-processing/)，他们好像还有什么授权问题，然而我是来误人子弟的，也不盈利，**侵删**~~
### 关于注解 - Annotation（作为年轻人这些东西我都是听说的）
注解最早出现在`Java 5`（没有考证哇，人家这么说的，我也无所谓它什么时候出现的╮(╯_╰)╭）。注解是一种用来`描述数据的数据`，在Java中就是用来描述字段函数类等等的。为什么要有注解这个东西呢，据说是因为当时没有注解的时候，大家都用xml来描述数据，但是呢，一些人觉得xml太特么没法看了，就有了注解（依然不可考，毕竟我是年轻人╮(╯_╰)╭）。而注解出来了以后呢，伟大的程序员们拿这个做了些什么东西呢。
* 生成文档，javadoc
* 编译器的检查：`@Override` `@Deprecated` `@SuppressWarnings`
* 编译时相关处理：这篇文章的主要目的，在编译时对代码做一些处理。
* 运行时相关处理：通常使用反射来使用，既可以通过注解来找目标，也可以通过目标来获取注解内容

由于博主很懒，注解的相关基础介绍只能送你个电梯了->[Java Annotation Tutorials](https://docs.oracle.com/javase/tutorial/java/annotations/),另外`Java 8`对于注解还是出了一些新的特性，比如重复注解还有类型注解，还有一些新的预设注解，所以这个教程还是值得看一看的，反正很短~
## 一些小小的预备知识，不想看就跳过，反正你最后会回来看
### APT（ Android Annotation Processing Tool）
首先是电梯直达[android-apt Bitbucket](https://bitbucket.org/hvisser/android-apt)

这个插件是用来辅助`Annotation Processor`在Android Studio上的工作的，让项目在仅在编译时可以使用Annotation Processor的功能来生成文件，并且生成的文件可以在工程中得到正确的引用。
### JavaPoet
一个用来生成java文件的工具，它是Square公司的一个开源项目，按照惯例，送你电梯[JavaPoet in GitHub](https://github.com/square/javapoet/graphs/contributors)

JavaPoet用来写java源文件不要太easy....
## 开始正文内容(不正经的翻译)
### 写在前面的
首先这篇博文讲的不是运行时的注解，而是编译时注解。

注解处理器是一个用来在编译时扫描处理注解的javac工具。我们可以给指定的注解注册相应的注解处理器。在注解处理器里面，你可以根据注册的相应的注解来做相应的处理，我们这里会去生成一些`.java`文件，并使用这些生成的文件实现一个工厂方法。
### AbstractProcessor
`AbstractProcessor`是实现注解处理器必须要继承的类，一般来说需要实现里面的四个方法
```java : n
package com.example;

public class MyProcessor extends AbstractProcessor {

	@Override
	public synchronized void init(ProcessingEnvironment env){ }

	@Override
	public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }

	@Override
	public Set<String> getSupportedAnnotationTypes() { }

	@Override
	public SourceVersion getSupportedSourceVersion() { }

}
```
* init(ProcessingEnvironment env):每个处理器**必须有一个空的构造方法**，`init()`方法会由Annotation Processing Tool 调用，并传入一个`ProcessingEnviroment`对象作为参数，从这个对象里面我们可以获得`Elements`，`Types`,`Filer`这三个辅助类的对象，这三个对象的作用会在后面介绍。
* process(Set<? extends TypeElement> annotations, RoundEnvironment env):这个函数是处理器的主要函数，我们可以在这个函数里面进行对特定的注解进行相应处理。其中`RoundEnvironment`可以用来查询被特定注解标记的元素。
* getSupportedAnnotationTypes():这个方法是用来指定这个处理器处理哪些注解。
* getSupportedSourceVersion(): 用来指定使用的Java版本，一般来说返回`SourceVersion.latestSupported()`就可以。

在`Java 7`以上版本里面，我们可以使用注解来代替`getSupportedAnnotationTypes()`,`getSupportedSourceVersion()`：

```java : n
@SupportedSourceVersion(SourceVersion.latestSupported())
@SupportedAnnotationTypes({
   // Set of full qullified annotation type names
 })
public class MyProcessor extends AbstractProcessor {

	@Override
	public synchronized void init(ProcessingEnvironment env){ }

	@Override
	public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }
}
```
然而由于兼容性的问题，特别是Android平台上，还是重写这两个方法比较好。

值得一提的是，javac工具会启动一个独立的jvm来运行注解处理器，也就是说，所有适用于一般项目的lib以及工程模式在注解处理器中统统适用。
### 注册你自己的处理器
如何注册一个处理器到javac中？在最终打包好的处理器jar中，你的工程应该看上去像这样
```
MyProcessor.jar
	- com
		- example
			- MyProcessor.class
            ...
	- META-INF
		- services
			- javax.annotation.processing.Processor
```
也就是要有一个名为`javax.annotation.processing.Processor`的文件放在jar包中`META-INF/services`文件夹下，而这个文件的内容是你这个jar包中所包含的处理器的完全类名，一行一个~，ep：
```
com.example.MyProcessor
com.foo.OtherProcessor
net.blabla.SpecialProcessor
```
这样，javac会从jar包中`javax.annotation.processing.Processor`读取相应的信息，并把这些处理器注册进去。
###一个工厂模式的例子
是时候来一个举个栗子了，然而这里我很不要脸的把栗子换成了自己的，送你电梯[直达我的项目](https://github.com/ShieldShen/AnnotationProcessorDemo)，而且原作者的例子是一个单纯的java工程，我的例子是在Android环境下哦。
当然，这个例子原作者也说了，没什么实际的卵用，而且使用注解处理器的大部分需求不是简单的一两行代码可以实现，这里的例子就是个例子~~~这里会使用处理器来实现一个简单工厂模式的代码生成。
那么，需求来了：一个披萨店，提供两种披萨，披萨A披萨B~~还有一个点心T。
一般来说，我们的代码可以这样写：
```java : n
public interface Meal {
  public float getPrice();
}<commit_msg>代码高亮调整<commit_after>---
title:        "注解处理器"
description:  "注解是Java 5就出来的一个javac工具，被广泛运用在很多框架与项目上，本文是一篇翻译学习博客"
image:        "http://placehold.it/400x200"
author:       "<NAME>"
date:         "2017-06-28"
---
# 注解处理器
## 写在前面的
为啥会写这篇博客呢，因为别人都用啊~~ 当然，正经说，注解处理器是运用在编译时期，它现在被大家发掘的用法是来生成一些文件，无论是java还是xml还是简单的键值对文档。那么这个的作用就很明显了，它可以帮我们来生成一些固定的东西，例如重复的代码逻辑，重复的配置文件。这么抽象？不不不，你看看`ButterKnife`、`Dagger`，虽然最后生成出来的代码可能比你一行一行硬写的要多不少，但是嘛，程序员时间才是最宝贵的。老写一样的代码这种体力劳动，嘿嘿...

这篇文章嘛主要是大致翻译的[ANNOTATION PROCESSING101](http://hannesdorfmann.com/annotation-processing/annotationprocessing101)，也有其他博主前辈翻译过啊（他们是正儿八经翻译，我是纯扯淡...）戳链接[Java注解处理器](https://www.race604.com/annotation-processing/)，他们好像还有什么授权问题，然而我是来误人子弟的，也不盈利，**侵删**~~
### 关于注解 - Annotation（作为年轻人这些东西我都是听说的）
注解最早出现在`Java 5`（没有考证哇，人家这么说的，我也无所谓它什么时候出现的╮(╯_╰)╭）。注解是一种用来`描述数据的数据`，在Java中就是用来描述字段函数类等等的。为什么要有注解这个东西呢，据说是因为当时没有注解的时候，大家都用xml来描述数据，但是呢，一些人觉得xml太特么没法看了，就有了注解（依然不可考，毕竟我是年轻人╮(╯_╰)╭）。而注解出来了以后呢，伟大的程序员们拿这个做了些什么东西呢。
* 生成文档，javadoc
* 编译器的检查：`@Override` `@Deprecated` `@SuppressWarnings`
* 编译时相关处理：这篇文章的主要目的，在编译时对代码做一些处理。
* 运行时相关处理：通常使用反射来使用，既可以通过注解来找目标，也可以通过目标来获取注解内容

由于博主很懒，注解的相关基础介绍只能送你个电梯了->[Java Annotation Tutorials](https://docs.oracle.com/javase/tutorial/java/annotations/),另外`Java 8`对于注解还是出了一些新的特性，比如重复注解还有类型注解，还有一些新的预设注解，所以这个教程还是值得看一看的，反正很短~
## 一些小小的预备知识，不想看就跳过，反正你最后会回来看
### APT（ Android Annotation Processing Tool）
首先是电梯直达[android-apt Bitbucket](https://bitbucket.org/hvisser/android-apt)

这个插件是用来辅助`Annotation Processor`在Android Studio上的工作的，让项目在仅在编译时可以使用Annotation Processor的功能来生成文件，并且生成的文件可以在工程中得到正确的引用。
### JavaPoet
一个用来生成java文件的工具，它是Square公司的一个开源项目，按照惯例，送你电梯[JavaPoet in GitHub](https://github.com/square/javapoet/graphs/contributors)

JavaPoet用来写java源文件不要太easy....
## 开始正文内容(不正经的翻译)
### 写在前面的
首先这篇博文讲的不是运行时的注解，而是编译时注解。

注解处理器是一个用来在编译时扫描处理注解的javac工具。我们可以给指定的注解注册相应的注解处理器。在注解处理器里面，你可以根据注册的相应的注解来做相应的处理，我们这里会去生成一些`.java`文件，并使用这些生成的文件实现一个工厂方法。
### AbstractProcessor
`AbstractProcessor`是实现注解处理器必须要继承的类，一般来说需要实现里面的四个方法
```java
package com.example;

public class MyProcessor extends AbstractProcessor {

	@Override
	public synchronized void init(ProcessingEnvironment env){ }

	@Override
	public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }

	@Override
	public Set<String> getSupportedAnnotationTypes() { }

	@Override
	public SourceVersion getSupportedSourceVersion() { }

}
```
* init(ProcessingEnvironment env):每个处理器**必须有一个空的构造方法**，`init()`方法会由Annotation Processing Tool 调用，并传入一个`ProcessingEnviroment`对象作为参数，从这个对象里面我们可以获得`Elements`，`Types`,`Filer`这三个辅助类的对象，这三个对象的作用会在后面介绍。
* process(Set<? extends TypeElement> annotations, RoundEnvironment env):这个函数是处理器的主要函数，我们可以在这个函数里面进行对特定的注解进行相应处理。其中`RoundEnvironment`可以用来查询被特定注解标记的元素。
* getSupportedAnnotationTypes():这个方法是用来指定这个处理器处理哪些注解。
* getSupportedSourceVersion(): 用来指定使用的Java版本，一般来说返回`SourceVersion.latestSupported()`就可以。

在`Java 7`以上版本里面，我们可以使用注解来代替`getSupportedAnnotationTypes()`,`getSupportedSourceVersion()`：

```java
@SupportedSourceVersion(SourceVersion.latestSupported())
@SupportedAnnotationTypes({
   // Set of full qullified annotation type names
 })
public class MyProcessor extends AbstractProcessor {

	@Override
	public synchronized void init(ProcessingEnvironment env){ }

	@Override
	public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }
}
```
然而由于兼容性的问题，特别是Android平台上，还是重写这两个方法比较好。

值得一提的是，javac工具会启动一个独立的jvm来运行注解处理器，也就是说，所有适用于一般项目的lib以及工程模式在注解处理器中统统适用。
### 注册你自己的处理器
如何注册一个处理器到javac中？在最终打包好的处理器jar中，你的工程应该看上去像这样
```
MyProcessor.jar
	- com
		- example
			- MyProcessor.class
            ...
	- META-INF
		- services
			- javax.annotation.processing.Processor
```
也就是要有一个名为`javax.annotation.processing.Processor`的文件放在jar包中`META-INF/services`文件夹下，而这个文件的内容是你这个jar包中所包含的处理器的完全类名，一行一个~，ep：
```
com.example.MyProcessor
com.foo.OtherProcessor
net.blabla.SpecialProcessor
```
这样，javac会从jar包中`javax.annotation.processing.Processor`读取相应的信息，并把这些处理器注册进去。
###一个工厂模式的例子
是时候来一个举个栗子了，然而这里我很不要脸的把栗子换成了自己的，送你电梯[直达我的项目](https://github.com/ShieldShen/AnnotationProcessorDemo)，而且原作者的例子是一个单纯的java工程，我的例子是在Android环境下哦。
当然，这个例子原作者也说了，没什么实际的卵用，而且使用注解处理器的大部分需求不是简单的一两行代码可以实现，这里的例子就是个例子~~~这里会使用处理器来实现一个简单工厂模式的代码生成。
那么，需求来了：一个披萨店，提供两种披萨，披萨A披萨B~~还有一个点心T。
一般来说，我们的代码可以这样写：
```java
public interface Meal {
  public float getPrice();
}
{'fraction_non_alphanumeric': 0.1199160936953735, 'fraction_numerical': 0.0062929728469875304, 'mean_word_length': 9.71410736579276, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 15, 'https://': 10, 'lorem ipsum': 0, 'www.': 2, 'xml': 6}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3858305', 'n_tokens_mistral': 5168, 'n_tokens_neox': 5513, 'n_words': 323}