资讯专栏INFORMATION COLUMN

从APT中获取运行时类信息

levinit / 2326人阅读

摘要:概述从开始提供了一个新的被称为的工具,使用其提供的我们可以通过类似数据结构的方式来访问被编译的的源代码。例如我们定义了一个注解该注解接受一个的参数,注意该注解仅可见于源代码编译过程。

概述

从JDK1.6开始提供了一个新的被称为APT(Annotation Processing Tool)的工具,使用其提供的APT我们可以通过类似数据结构的方式来访问被编译的Java的源代码。

利用这个新的工具提供的API我们可以在编译Java源代码的同时对现有代码进行增强和生成代码,比之以往通过运行时的反射以及通过Java的动态代理或者运行时字节码修改来增强要来的更简单并且运行时的效率要更高(指启动时间)

问题描述

在Java程序运行时可以通过反射来获取类的Meta信息,但是在APT中处理的是Java源代码,此时无法直接获取定义在Annotation里面的类型信息,因为没有反射API可以使用。
例如我们定义了一个注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Service {

    Class[] value();
}

该注解接受一个Class的参数,注意该注解仅可见于源代码编译过程。

如果想在APT中获取Service.value,下面代码是不可行的:

...
Service service = ...
Class[] cls = service.value();
...

运行这段代码会在编译时抛出异常,因为在编译时类型信息无法直接获取,只有Native数据和String可以直接获取。

解决方案

如何解决该问题呢?我们需要使用APT提供的*Mirror API获取Class的相关信息。

获取AnnotationMirror对象

首先获取AnnotationMirror对象:

AnnotationMirror svcAnnoMirror =
  MoreElements.getAnnotationMirror(classElement, Service.class).get();

MoreElements是Google Auto库里面的一个工具类,getAnnotationMirror方法比较简单,它返回Service Annotation对应的AnnotationMirror对象。

上面的classElement是一个javax.lang.model.element.Element实例,在这里它代表了申明了Service这个Annotation的类的元素。

在APT里面,Java源代码会被解释称类似XML的结构,比如类,字段,方法,方法的参数等都会被解释称一个元素,每个元素都是Element的实例

获取Annotation定义的元素列表

然后获取该Annotation里面定义的所有元素的列表:

Set elementSet = svcAnnoMirror.getElementValues().entrySet();

过滤出key是value的那个元素

if (entry.getKey().getSimpleName().toString().equals("value")) {
  AnnotationValues annoValue = entry.getValue();
}
获取TypeElement

有了AnnotationValue,我们就可以取得内部的值:

List values = (List) annoValue.getValue();

因为我们定义了Service.value是一个Class数组,所以这里我们需要把返回值强制转换成List。

为了获取Service.value里面定义的Class信息,我们需要使用TypeMirror API

DeclaredType declaredType = (DeclaredType) value.getValue();
TypeElement typeElement = (TypeElement) declaredType.asElement();

有了TypeElement,我们就可以访问该类型相关的信息了,比如获取其完整类名:

typeElement.getQualifiedName().toString();

当然获取它的实现的接口或者定义在它内部的方法或者字段都是可以的。

完整代码
AnnotationMirror svcAnnoMirror =
    MoreElements.getAnnotationMirror(classElement, Service.class).get();
List types = new ArrayList<>();
Observable.from(svcAnnoMirror.getElementValues().entrySet())
      .filter(entry -> "value".equals(entry.getKey().getSimpleName().toString()))
      .map(Map.Entry::getValue)
      .flatMap(annoValue -> Observable.from((List) annoValue.getValue()))
      .map(annoValue -> (DeclaredType) annoValue.getValue())
      .map(declaredType -> (TypeElement) declaredType.asElement())
      .map(typeElem -> typeElem.getQualifiedName().toString())
      .subscribe(types::add, logger::error);

上面代码使用rxJava以及Lambda表达式来简化代码。

参见我的项目源码:这里

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/65799.html

相关文章

  • 四大组件以及Application和Context的全面理解

    摘要:关于后称的操作我们知道其实就是文件,所以这里的操作有获取移动删除。操作启动停止重启绑定解绑获取系统服务以及多用户操作。权限操作检查本是否有某种权限检查某是否有某种权限检查权限授予权限等等。 先放一张图吧 showImg(https://segmentfault.com/img/remote/1460000015839908); 2.用处 1.Context的实现类有很多,但是Cont...

    William_Sang 评论0 收藏0
  • 记一次Docker构建失败

    摘要:之所以在本地构建,而没有使用仓库的,是因为,我们的镜像采用了国内阿里云的源,再加上某些很奇妙的网络因素,在中自动构建时,升级总会失败。然而,在本地再次构建成功。 见字如晤。 前段时间,Node.js 官方发布了Node 8.9.3 LTS版本,并且官网首页提示新版本有重要安全更新,Important security releases, please update now! ,然后我立...

    joyqi 评论0 收藏0
  • 关于Apt注解实践与总结【包含20篇博客】

    摘要:使用实现功能运行期注解案例使用简单的注解,便可以设置布局,等效于使用实现路由综合型案例比较全面的介绍从零起步,一步一步封装简易的路由开源库。申明注解用的就是。返回值表示这个注解里可以存放什么类型值。 YCApt关于apt方案实践与总结 目录介绍 00.注解系列博客汇总 01.什么是apt 02.annotationProcessor和apt区别 03.项目目录结构 04.该案例作用 ...

    gnehc 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<