概述
注解的解析可以通过反射,但反射的性能较低。所以在移动平台上,如安卓端使用,那是得不偿失的。那么Android 端如何解析注解的呢?Android
端使用 apt
解析注解。然后使用自动生成的代码实现需要的逻辑。
自定义注解系列文章
- 那些高端、优雅的注解是怎么实现的<0> – 注解的分类
- 那些高端、优雅的注解是怎么实现的 <1> – 自定义注解语法
- 那些高端、优雅的注解是怎么实现的<2> – 解析注解
- 那些高端、优雅的注解是怎么实现的<3> - 可继承性@Inherited
- 那些高端、优雅的注解是怎么实现的<4> – 使用Annotaion Processing Tool 解析注解
- 那些高端、优雅的注解是怎么实现的<5> --使用Annotaion Processing Tool 自定义注解
- 那些高端、优雅的注解是怎么实现的<6> --自定义持久层框架-类 Hibernate
APT 工具
APT(annotation processing tool)
是一个命令行工具, 它对源代码文件进行检测找出其中的annotation后,使用annotation processors
来处理annotation
处理过程
annotation processors
处理annotation
的基本过程如下
- APT运行
annotation processors
根据提供的源文件中的annotation
生成源代码文件和其它的文件(文件具体内容由annotation processors的编写者决定) - 接着APT将生成的源代码文件和提供的源文件进行编译生成类文件。
自定义 annotation processor
APT 运行 annotation processors
根据源文件中的 annotation
生成源代码文件和其它的文件。那么如何定义 annotation processor
从而生成我们需要的代码,来实现自己逻辑就是问题的关键点了。自定义 annotation processors
需要继承
AbstractProcessor
。
现在我们逐一说下 AbstractProcessor
中对我们来说比较重要的方法
一:init(ProcessingEnvironment processingEnv) 方法
初始化操作的方法,RoundEnvironment
会提供很多有用的工具类Elements、Types和Filer等。这些工具类可以简化我们后续自定义逻辑中的逻辑和代码。
二:process(Set<? extends TypeElement> set, RoundEnvironment roundEnv)方法
这相当于每个处理器的主函数main()
。在该方法中去扫描、评估、处理以及生成Java
文件。
- roundEnv.getElementsAnnotatedWith(Factory.class))
返回所有被注解了@Factory
的元素的列表,所有元素列表。也就是包括 类、包、方法、变量等。所以element 是如此重要的一个概念。
element 的概念
表示一个程序元素,比如包、类或者方法。每个元素都表示一个静态的语言级构造(不表示虚拟机的运行时构造)。 元素应该使用equals(Object)方法进行比较。不保证总是使用相同的对象表示某个特定的元素。 要实现基于Element
对象类的操作,可以使用ElementVisitor
或者使用getKind()
方法的结果。使用instanceof
确定此建模层次结构中某一对象的有效类未必可靠,因为一个实现可以选择让单个对象实现多个Element子接口。
如下的这个类就包含多种element
public class Foo { // TypeElement 类型元素
private int a; // VariableElement 变量元素
private Foo other; // VariableElement 变量元素
public Foo() { // ExecuteableElement 可执行元素
}
public void setA(int newA ) { // newA 代表是一个 TypeElement)
}
}
再来看下 Element
的源码,感谢一个努力的码农的分享。
public interface Element extends javax.lang.model.AnnotatedConstruct {
/**
* 返回该元素定义的类型。
* 泛型元素定义了一系列类型,而不仅仅是一个类型。如果这是一个泛型元素,则返回一个原型
* 类型。这是元素在对应于它自己的正式类型参数的类型变量上的调用。例如,对于泛型类元素
* C<N extends Number>,返回参数化类型C<N>。类型实用程序接口有更一般的方法来获取元
* 素定义的所有类型的范围。
*/
TypeMirror asType();
/**
* 返回该元素的类型
*/
ElementKind getKind();
/**
* 返回该元素的修饰符,包括注解.
* 隐式修饰符也包含,比如接口方法中的public和static
*/
Set<Modifier> getModifiers();
/**
* 返回该元素的简单名称.泛型类型的名称不包括对其正式类型参数的任何引用。
* 举例,java.util.Set<E>的简单名称是Set.
* 如果该元素代表的是未命名包,则返回一个空 Name.
* 如果代表的是构造器,则返回<init>所对应的Name.如果代表的是静态代码块,则返回的是<clinit>
* 如果代表的是匿名类或者是初始代码块,则返回一个空 Name.
*/
Name getSimpleName();
/**
* 返回包围该元素的最内层的元素.
* 如果这个元素的声明紧接在另一个元素的声明中,则返回另一个元素。
* 如果这是顶级类型,则返回其包。
* 如果这是一个包,则返回null。
* 如果这是类型参数,则返回类型参数的泛型元素。
* 如果这是一个方法或构造函数参数,则返回声明该参数的可执行元素。
*/
Element getEnclosingElement();
/**
* 返回该元素所包含的元素.
* 类或接口被认为包含了它直接声明的字段、方法、构造函数和成员类型.包直接包含了顶级类和接
* 口,但不包含其子包。其他类型的元素目前不被认为包含任何元素;然而,随着这个API或编程语
* 言的发展,这些元素可能会改变
*/
List<? extends Element> getEnclosedElements();
/**
* 当给定的参数和当前类代表同一个元素时返回true,否则,返回false.
* 注意,元素的标识涉及不能直接从元素的方法中访问的隐式状态,包括关于不相关类型的存在的
* 状态。即使“同一个”元素正在被建模,由这些接口的不同实现创建的元素对象也不应该期望相
* 等;这类似于通过不同的类加载器加载的同一个类文件的Class对象是不同的
*
*/
@Override
boolean equals(Object obj);
/**
* 基于Object.hashCode
*/
@Override
int hashCode();
/**
* 获得直接声明在该元素上的注解
* 如果要获得继承的注解,使用Elements#getAllAnnotationMirrors(Element)方法.
*/
@Override
List<? extends AnnotationMirror> getAnnotationMirrors();
@Override
<A extends Annotation> A getAnnotation(Class<A> annotationType);
<R, P> R accept(ElementVisitor<R, P> v, P p);
}
TypeElement
TypeElement
表示一个类或接口程序元素(如上面的class Foo)。提供对有关类型及其成员的信息的访问。 而 DeclaredType
表示申明类型,你申明的是一个接口还是一个类等等,它也可以拥有泛型参数。这种区别对于一般的类型是最明显的,对于这些类型,单个元素可以定义一系列完整的类型。 例如,元素java.util.Set
对应于参数化类型java.util.Set<String>
和java.util.Set<Number>
(以及其他许多类型),还对应于原始类型java.util.Set
。这块确实很绕,不过我们用一用就知道大概什么意思了。如果你还想继续深入研究,戳 这里
三:getSupportedSourceVersion()方法
用来指定你使用的java
版本。通常这里会直接放SourceVersion.latestSupported()
即可。
四:getSupportedAnnotationTypes():
这里你必须指定,该注解器解析器是注册给哪个注解的。
注意
从jdk 1.7开始,可以使用如下注解来代替getSupporedAnnotationTypes()
和getSupportedSourceVersion()
方法:
@SupportedSourceVersion(SourceVersion.latestSupported())
@SupportedAnnotationTypes({
// 合法注解全名的集合
})
举个例子
有了上面的知识点 ,我们就可以开始我们的自定义注解之旅了。
###第一:需求
假设有一个披萨店,他们有如如下的披萨。MargheritaPizza
,CalzonePizza
,Tiramisu
每个产品具有不同的价格。
第二:一般的实现方式
定义产品
因为后续可能会有其他新的产品陆续上线。所以我们定一个接口 Meal.如下
public interface Meal {
public float getPrice();
}
然后 MargheritaPizza
,CalzonePizza
,Tiramisu
三个产品类的定义如下。
public class CalzonePizza implements Meal {
@Override
public float getPrice() {
return 8.5f;
}
}
public class MargheritaPizza implements Meal {
@Override
public float getPrice() {
return 6.0f;
}
}
public class Tiramisu implements Meal {
@Override
public float getPrice() {
return 4.5f;
}
}
定义披萨店
public class PizzaStore {
public Meal order(String mealName) {
if (mealName == null) {
throw new IllegalArgumentException("Name of the meal is null!");
}
if ("Margherita".equals(mealName)) {
return new MargheritaPizza();
}
if ("Calzone".equals(mealName)) {
return new CalzonePizza();
}
if ("Tiramisu".equals(mealName)) {
return new Tiramisu();
}
throw new IllegalArgumentException("Unknown meal '" + mealName + "'");
}
public static void main(String[] args) throws IOException {
PizzaStore pizzaStore = new PizzaStore();
Meal meal = pizzaStore.order(readConsole());
System.out.println("Bill: $" + meal.getPrice());
}
对的,披萨店有个order
方法,会根据顾客点的披萨品种,计算价格。也就是代码中那个长长的if else
语句。虽然功能是实现了,但每次增加新品,都需要修改order
方法,又在那长长的if else 语句中添加新的判断。首先这无形中增加了我们的维护成本,也不够美观。当然我们可以使用工厂模式,对它进行封装,这样看起来就好了很多。
####使用工厂模式简化代码
定义 MealFactory
public class MealFactory {
public Meal create(String id) {
if (id == null) {
throw new IllegalArgumentException("id is null!");
}
if ("Calzone".equals(id)) {
return new CalzonePizza();
}
if ("Tiramisu".equals(id)) {
return new Tiramisu();
}
if ("Margherita".equals(id)) {
return new MargheritaPizza();
}
throw new IllegalArgumentException("Unknown id = " + id);
}
}
定义披萨店
public class PizzaStore {
private MealFactory factory = new MealFactory();
public Meal order(String mealName) {
return factory.create(mealName);
}
public static void main(String[] args) throws IOException {
PizzaStore pizzaStore = new PizzaStore();
Meal meal = pizzaStore.order(readConsole());
System.out.println("Bill: $" + meal.getPrice());
}
}
现在披萨店的代码看起来简洁了很多,但是长长的if else 语句仍然存在(只是转移到了工厂类里面)。这些是很机械性的代码,如果有几百个品种,那可以写到怀疑人生了。所以有请我们的注解,下一篇我们用自定义注解自动生成MealFactory
的代码。github 地址