在Java Web的开始中注解的使用相当的相当的广泛,比如Controller,Service,Component等的注解能够很好的解决耦合的问题。现在在 Android中也开始用的越来越广泛,比如ButterKnife,Retrofit,EventBus等等都选择使用注解来配置。
在现阶段的Android开发中,注解越来越流行起来,按照处理时期,注解又分为两种类型,一种是运行时注解,另一种是编译时注解,运行时注解由于性能问题被一些人所诟病,编译时注解的核心原理依赖APT(Annotation Processing Tools)实现,例如,著名的ButterKnife,Dragger,Retrofit等开源库都是基于APT。那么编译期注解是如何工作的?
编译时Annotation解析的基本原理是,在某些代码元素上(如类型、函数、字段等)添加注解,在编译时编译器会检查AbstractProcessor
的子类,并且调用该类型的process
函数,然后将添加了注解的所有元素都传递到process
函数中,使得开发人员可以在编译器进行相应的处理,例如,根据注解生成新的Java类,这也就是ButterKnife等开源库的基本原理。
在编译处理的时候,是分开处理的。如果在某个处理中产生了新的Java源文件,那么就需要另外一个处理来处理新生成的源文件,如此反复,直到没有新文件被生成为止。在完成处理之后,再对Java代码进行编。JDK 5中提供了APT工具用来对注解进行处理。APT是一个命令行工具,与之配套的还有一套用来描述程序语义结构的Mirror API,Mirror API描述的是程序在编译时的静态结构,通过Mirror API可以获取到被注解的Java类型元素的信息,从而提供相应的处理逻辑,具体的处理工作交给APT工具来完成。编写注解处理器的核心是AnnotationProcessorFactory
和AnnotationProcessor
两个接口,后者便是德是注解处理器,从前者则是为某些注解类型创建注解处理器的工厂。
对于编译器来说,代码中的元素结构是基本不变的,例如,组成代码的基本元素有包,类,函数,字段,字段参数,变量。JDK中为这些元素定义了一个基类,也就是Element
类,它有如下几个子类:
- PackageElement——包元素,包含了某个包下的信息,可以获取到包名等;
- TypeElement——类型元素,如某个字段属于某种类型;
- ExecutableElement——可执行元素,代表了函数类型的元素;
- VariableElement——变量元素;
- TypeParameterElement——类型参数元素。
因为注解可以指定作用在哪些元素上,因此,通过上述的抽象来对应这些元素,例如下面的这个注解,指定的是只能作用于函数上面,并且这个注解只能保留在class文件中:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Test {
String value();
}
这个注解因为只能作用于函数类型,因此,它对应的元素类型就是ExecutableElement当我们想通过APT处理这个注解的时候就可以获取目标对象上的Test注解,并且将所有这些元素转换为ExecutableElement元素,以便获取到他们对应的信息。
我们看看你元素基类的的实现(在线文档地址):
package javax.lang.model.element;
import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ElementVisitor;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.type.TypeMirror;
public interface Element {
TypeMirror asType();
//获取元素类型
ElementKind getKind();
List<? extends AnnotationMirror> getAnnotationMirrors();
<A extends Annotation> A getAnnotation(Class<A> var1);
//获取修饰符 如public,static ,final等
Set<Modifier> getModifiers();
Name getSimpleName();
Element getEnclosingElement();
List<? extends Element> getEnclosedElements();
boolean equals(Object var1);
int hashCode();
//接受访问者的访问
<R, P> R accept(ElementVisitor<R, P> var1, P var2);
}
我们看到Element定义了一个代码元素的一些通用接口,其中很显眼的就是accept
函数,这个函数接受一个ElementVisitor和类型为P的参数。
public interface ElementVisitor<R, P> {
//访问元素
R visit(Element e, P p);
R visit(Element e);
//访问包元素
R visitPackage(PackageElement e, P p);
//访问类型元素
R visitType(TypeElement e, P p);
//访问变量元素
R visitVariable(VariableElement e, P p);
//访问克而执行元素
R visitExecutable(ExecutableElement e, P p);
//访问参数元素
R visitTypeParameter(TypeParameterElement e, P p);
//处理位置的元素类型,这是为了应对后续Java语言的扩折而预留的接口,例如后续元素类型添加了,那么通过这个接口就可以处理上述没有声明的类型
R visitUnknown(Element e, P p);
}
在ElementgVisitor中定义了多个visit接口,每个接口处理一种元素类型,这就是典型的访问者模式。我们制定,一个类元素和函数元素是完全不一样的,他们的结构不一样,因此,在编译器对他们的操作肯定是不一样,通过访问者模式正好可以解决数据结构与数据操作分离的问题,避免某些操作污染数据对象类。
Andrioid使用编译时注解来Bind View
在Android开发张,我们接触的比较多的编译期注解的库应该就是ButterKnife,这个库是针对View,资源ID,部分事件等进行注解的开源库,它能够去除掉一些不怎么雅观的样板式代码,使得我们的代码更加简洁,易于维护,同事给予APT也能使得它的效率得到保证。ButterKnife是针对View等进行注解的开源库,如果要对对象进行注解可以使用Square公司的Dagger开源库,目前该库已经交由Google维护,新的名字为Dagger 2。
class ExampleActivity extends Activity {
@BindView(R.id.user) EditText username;
@BindView(R.id.pass) EditText password;
@BindString(R.string.login_error) String loginErrorMessage;
@OnClick(R.id.submit) void submit() {
// TODO call server...
}
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
ButterKnife.bind(this);
// TODO Use fields...
}
}
例如上面的,使用BindView进行加载View,不再需要findviewById并进行强转的代码。
ButterKnife.bind(this);
要放在setContentView
之后,使用控件的代码之前。
注解只存在于class文件中,因为,一旦过了编译期就不再需要它了。
使用编译时注解实现View注入的实例请查看下一文章 (//www.zhangningning.com.cn/blog/Android/android_rentention_sample.html