优雅解析民族代码java_那些高端、优雅的注解是怎么实现的<4> -- 使用Annotaion Processing Tool 解析注解...

概述

注解的解析可以通过反射,但反射的性能较低。所以在移动平台上,如安卓端使用,那是得不偿失的。那么Android 端如何解析注解的呢?Android 端使用 apt 解析注解。然后使用自动生成的代码实现需要的逻辑。

自定义注解系列文章

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,返回参数化类型C。类型实用程序接口有更一般的方法来获取元

* 素定义的所有类型的范围。

*/

TypeMirror asType();

/**

* 返回该元素的类型

*/

ElementKind getKind();

/**

* 返回该元素的修饰符,包括注解.

* 隐式修饰符也包含,比如接口方法中的public和static

*/

Set getModifiers();

/**

* 返回该元素的简单名称.泛型类型的名称不包括对其正式类型参数的任何引用。

* 举例,java.util.Set的简单名称是Set.

* 如果该元素代表的是未命名包,则返回一个空 Name.

* 如果代表的是构造器,则返回所对应的Name.如果代表的是静态代码块,则返回的是

* 如果代表的是匿名类或者是初始代码块,则返回一个空 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 getAnnotation(Class annotationType);

R accept(ElementVisitor v, P p);

}

TypeElement

TypeElement 表示一个类或接口程序元素(如上面的class Foo)。提供对有关类型及其成员的信息的访问。 而 DeclaredType 表示申明类型,你申明的是一个接口还是一个类等等,它也可以拥有泛型参数。这种区别对于一般的类型是最明显的,对于这些类型,单个元素可以定义一系列完整的类型。 例如,元素java.util.Set对应于参数化类型java.util.Set和java.util.Set(以及其他许多类型),还对应于原始类型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 地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Eclipse平台JAVA实现 1. 实验内容 用基本增量算法和Bresenham算法画直线 2.实验目的 1)理解在显示器上画图与在纸上画图的本质区别; 2)掌握直线的光栅扫描转换过程; 3)掌握不同算法绘制直线的思路和优缺点。 3. 实验要求 1)将像素网格表现出来,建立网格坐标系; 2)用橡皮筋的形式输入参数; 3)鼠标移动时,显示鼠标当前位置; 4)显示判别式的计算过程和下一点的选择策略; 5)记录生成点的坐标,建议用表的形式; 6)图形生成过程可以重复进行。 1. 实验内容 用正负法和Bresenham算法画圆弧 2.实验目的 1)掌握圆及圆弧的光栅扫描转换过程; 2)掌握不同算法绘制圆弧的技巧和优缺点。 3. 实验要求 1)将像素网格表现出来,建立网格坐标系; 2)用橡皮筋的形式输入参数; 3)鼠标移动时,显示鼠标当前位置; 4)显示判别式的计算过程和下一点的选择策略; 5)记录生成点的坐标,建议用表的形式; 6)图形生成过程可以重复进行。 1. 实验内容 用Cohen-SutherLand算法和liang _barsky算法进行线段裁剪 2.实验目的 1)理解裁剪的相关概念 2)掌握直线段的一般裁剪过程; 3)理解并掌握Cohen-SutherLand 算法的编码思想; 4)理解并掌握Liang_Barsky算法的参数化裁剪思想; 3. 实验要求 1)将像素网格表现出来,建立网格坐标系; 2)用橡皮筋的形式输入剪裁线段和裁剪窗口; 3)鼠标移动时,显示鼠标当前位置; 4)对于线段裁剪,线段被窗口的四条边裁剪的过程要显示出来; 6)裁剪过程可以重复进行。 1. 实验内容 用Sutherland-Hodgman算法进行多边形裁剪 2.实验目的 1)理解多边形裁剪与直线段裁剪的区别; 2)掌握多边形的裁剪过程; 3)理解并掌握Sutherland-Hodgman算法的裁剪思想。 3. 实验要求 1)将像素网格表现出来,建立网格坐标系; 2)用橡皮筋的形式输入剪裁多边形和裁剪窗口; 3)鼠标移动时,显示鼠标当前位置; 4)多边形被窗口的四条边裁剪的过程以及多边形顶点增删的过程要显示出来; 5)裁剪过程可以重复进行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值