Java自定义注解及解析

Java自定义注解及解析目录 注解定义注解注解处理器运行时解析注解编译时解析注解自动生成 class 代码总结注解注解为我们在代码中添加信息提供一种形式化的方法 使我们可以在源码 编译时 运行时非常方便的使用这些数据 注解是在 JAVASE5 中引入的 注解让代码更干净易读并且可以实现编译期类型检查等 当创建描述性质的类或接口时 如果有重复性的工作 就可以考虑使用注解来简化或自动化

目录:

注解

注解为我们在代码中添加信息提供一种形式化的方法,使我们可以在源码、编译时、运行时非常方便的使用这些数据。

注解是在JAVA SE5中引入的,注解让代码更干净易读并且可以实现编译期类型检查等。当创建描述性质的类或接口时,如果有重复性的工作,就可以考虑使用注解来简化或自动化该过程。我们可以让注解保存在源代码中,并且利用Annotation API处理注解,得到我们想要的数据并加以处理,注解的使用比较简单,JAVA SE5内置了3种:

  • @Override 表示当前类中的方法将覆盖父类中的方法,如果不写也不会有错,但是@Override可以起到检查作用,如方法名拼写错误,编译器就会报警告信息。
  • @Deprecated 表示被标注的方法已经被废弃了,如果使用编译器会发出警告信息。
  • @SuppressWarnings 关闭不当的编译器警告信息。除非你确定编译器的警告信息是错误的,否则最好不要使用这个注解。

定义注解

先来看内置注解@Override是怎么被定义的,它位于package java.lang之下:

@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { } 

@Target、@Retention称为元注解,元注解负责注解其他的注释,如:@Target定义声明的注解的作用域(作用在类上还是方法上),@Retention定义注解在哪个级别可用,在源代码中(SOURCE)、类文件中(CLASS)、还是运行时(RUNTIME)。除了@Target、@Retention还有@Documented及@Inherited,下面用一个表格来分别列出他们各自的作用:

元注解 作用
@Target 表示注解作用在什么地方,CONSTRUCTOR 声明在构造器、FIELD 域声明、METHOD 方法声明、PACKAGE 包声明、TYPE 类、接口或者enum声明、PARAMETER参数声明、LOCAL_VALABLE局部变量声明
@Retention 表示在什么级别保存注解信息,SOURCE注解在编译器编译时丢弃、CLASS注解在编译之后的class文件中存在,但会被VM丢弃、RUNTIME VM将在运行期也保留注解,因此可以用反射读取注解的信息
@Documented 将此注解包含在JavaDoc中
@Inherited 允许子类继承父类中的注解

@Retention作用范围如下图所示:

注解作用范围

注解处理器

首先来自定义一个注解:

@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AnnotationInfo { String[] value(); int requestCode() default 0; } 
  • 注解中定义的方法没有参数,且返回类型仅限于原始类型,字符串,枚举,注解或以上类型的集合
  • 注解中定义的方法可以有默认值

运行时解析注解

@Target(ElementType.METHOD)指明了我们的注解是作用在方法上的

@Retention(RetentionPolicy.RUNTIME)表示注解在程序运行时期也会存在,即注解信息也会加载到虚拟机VM中,所以可以通过反射来获取注解的相关信息:

编写一个类,声明方法,并在方法上声明我们的自定义注解,如下:

public class AnnotationExample { / * 注解模拟请求权限 */ @AnnotationInfo(value = {"android.permission.CALL_PHONE", "android.permission.CAMERA"}, requestCode = 10) public void requestPermission() { //其他逻辑 } } 

接着来编写一个运行时解析注解的Java类:AnnotationRuntimeProcessor.java

public class AnnotationRuntimeProcessor { public static void main(String[] args) { try { //获取AnnotationExample的Class对象 Class<?> cls = Class.forName("com.javastudy.Annotation.AnnotationExample"); //获取AnnotationExample类中的方法 Method[] methods = cls.getDeclaredMethods(); for (Method method : methods) { //过滤不含自定义注解AnnotationInfo的方法 boolean isHasAnnotation = method.isAnnotationPresent(AnnotationInfo.class); if (isHasAnnotation) { method.setAccessible(true); //获取方法上的注解 AnnotationInfo aInfo = method.getAnnotation(AnnotationInfo.class); if (aInfo == null) return; //解析注解上对应的信息 String[] permissions = aInfo.value(); System.out.println("value: " + Arrays.toString(permissions)); int requestCode = aInfo.requestCode(); System.out.println("requestCode: " + requestCode); } } } catch (Exception e) { e.printStackTrace(); } } } 

上面的逻辑很简单,反射拿到有注解对应类的Class对象,筛选含有注解的方法,最后获取方法上的注解并解析,运行结果如下:

value: [android.permission.CALL_PHONE, android.permission.CAMERA] requestCode: 10 

编译时解析注解

AbstractProcessor是javax下的API,java和javax都是Java的API(Application Programming Interface)包,java是核心包,javax的x是extension的意思,也就是扩展包。一般继承AbstractProcessor需要实现下面的几个方法:

public class ProcessorExample extends AbstractProcessor { @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { //processingEnvironment提供各种工具类 如Elements Filer Types SourceVersion等 super.init(processingEnvironment); } / * 扫描 评估和处理注解代码 生成Java代码 * * @param set 注解类型 * @param roundEnvironment 有关当前和以前的信息环境 查询出包含特定注解的被注解元素 * @return 返回true 表示注解已声明 后续Processor不会再处理 false表示后续Processor会处理他们 */ @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { return false; } @Override public SourceVersion getSupportedSourceVersion() { return super.getSupportedSourceVersion(); } @Override public Set<String> getSupportedAnnotationTypes() { return super.getSupportedAnnotationTypes(); } } 
  • init(ProcessingEnvironment env): 每一个注解处理器类都必须有一个空的构造函数。然而,这里有一个特殊的init()方法,它会被注解处理工具调用,并输入ProcessingEnviroment参数。ProcessingEnviroment提供很多有用的工具类Elements, Types和Filer。后面我们将看到详细的内容。
  • process(Set (? extends TypeElement) annotations, RoundEnvironment env): 这相当于每个处理器的主函数main()。你在这里写你的扫描、评估和处理注解的代码,以及生成Java文件。输入参数RoundEnviroment,可以让你查询出包含特定注解的被注解元素。后面我们将看到详细的内容。
  • getSupportedAnnotationTypes(): 这里你必须指定,这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称。换句话说,你在这里定义你的注解处理器注册到哪些注解上。
  • getSupportedSourceVersion(): 用来指定你使用的Java版本。通常这里返回SourceVersion.latestSupported()。然而,如果你有足够的理由只支持Java 6的话,你也可以返回SourceVersion.RELEASE_6。推荐使用前者。

下面来看一个具体的例子,我们在新建android的普通model和library工程是没有javax的,所以我们需要新建一个java工程,先来看下整个包结构:

首先先定义了注解:

@Retention(RetentionPolicy.CLASS) @Target(ElementType.FIELD) public @interface CompileAnnotation { int value() default 0; } 

可见我们的注解是定义在变量FIELD上的,接着来编写我们的解析器:

@SupportedSourceVersion(SourceVersion.RELEASE_7) @SupportedAnnotationTypes("com.suyun.aopermission.annotation.CompileAnnotation") public class AnnotationCompileProcessor extends AbstractProcessor { private Messager messager; private Filer filer; @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { //processingEnvironment提供各种工具类 如Elements Filer Types SourceVersion等 super.init(processingEnvironment); messager = processingEnv.getMessager(); filer = processingEnv.getFiler(); } / * 扫描 评估和处理注解代码 生成Java代码 * * @param annotations 注解类型 * @param roundEnvironment 有关当前和以前的信息环境 查询出包含特定注解的被注解元素 * @return 返回true 表示注解已声明 后续Processor不会再处理 false表示后续Processor会处理他们 */ @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) { messager.printMessage(Diagnostic.Kind.NOTE, "----------start----------"); for (TypeElement annotation : annotations) { Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(annotation); for (Element element : elements) { if (element.getKind() != ElementKind.FIELD) { messager.printMessage(Diagnostic.Kind.ERROR, "Only FIELD can be annotated with AnnotationInfo"); return true; } //获取注解 CompileAnnotation annotationInfo = element.getAnnotation(CompileAnnotation.class); //获取注解中的值 int value = annotationInfo.value(); messager.printMessage(Diagnostic.Kind.NOTE, "value: " + value); } } return true; } } 

一个简单的注解解析器就写完了,看代码知道我们只是简单的把注解里的值打印出来,接下来要做的就是把我们的@CompileAnnotation注解定义到我们类中的变量FILED上了,直接上代码:

public class MainActivity extends AppCompatActivity { @CompileAnnotation(value = 200) private TextView tv_text; ....省略其他代码.... } 

OK,接下来就是见证奇迹的时刻了,当我们迫不及待的按下编译按钮后,发现什么都没有发生,注解里的信息并没有打印出来,what fu!!!不要方,其实还少一步操作,我们只是定义了注解解析器,但是并没有把解析器注册到javac中,怎么注册呢,在main目录下新建resources->META-INF->services->javax.annotation.processing.Processor文件,文件里指定解析器的全路径,我的全路径是:

com.suyun.aopermission.processor.AnnotationCompileProcessor 

写好之后的目录如下:

在这里插入图片描述

接着再来编译一下,这次有了结果:

注: ----------start---------- 注: value: 200 注: ----------start---------- 

成功了,如果觉得上述的配置比较繁琐的话,可以选择使用Google开发的service库来代替上述配置,在build.gradle中配置:

compile 'com.google.auto.service:auto-service:1.0-rc2' 

然后我们的解析器中这样写:

@AutoService(Processor.class) public class AnnotationCompileProcessor extends AbstractProcessor { ....其他逻辑.... } 

没错,我们在注解解析器里又定义了@AutoService(Processor.class)注解,这样和上述配置是一样的效果

自动生成.class代码

编译时期我们可以根据需要自动生成.class代码,跟我们手动写.java代码编译生成的.class代码是一样的,自动生成有一样好处就是一些公共的或者重复的逻辑我们可以通过自动生成来减轻我们的工作了,通常自动生成.class代码需要用到JavaFileObject类,如下:

try { //packageName是包名 JavaFileObject source = mFiler.createSourceFile(packageName); Writer writer = source.openWriter(); //classStr代表的类里所有的字符 writer.write(classStr); writer.flush(); writer.close(); } catch (Exception e) { e.printStackTrace(); } 

具体JavaFileObject的用法大家可以去搜下,因为也不复杂,这里就不多说了,因为整个类都需要我们手动写,一是比较麻烦,二是容易出错,square做了一个开源的javapoet库来帮我们减少工作量,javapoet地址:https://github.com/square/javapoet

来看简单的一个栗子:

 //创建method方法类 MethodSpec methodSpec = MethodSpec.methodBuilder("getValue") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(int.class) .addStatement("return " + proAnnotation.value()) .build(); //创建.class类 TypeSpec typeSpec = TypeSpec.classBuilder("autoGenerate") .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addMethod(methodSpec) .build(); String packageName = processingEnv.getElementUtils(). getPackageOf(element).getQualifiedName().toString(); try { JavaFile javaFile = JavaFile.builder(packageName, typeSpec) .addFileComment("this is auto generated") .build(); javaFile.writeTo(filer); } catch (Exception e) { e.printStackTrace(); } 

编译我们的代码,然后再build->generated->source->apt->debug目录下就可以看到自动生成的.class类了:

// this is auto generated package org.ninetripods.mq.javastudy; public final class autoGenerate { public static int getValue() { return 100; } } 

更多用法请去javapoet的github上查看。

总结

自定义注解在一些优秀的三方库(如:EventBus ButterKnife等)中很常见,用于简化我们的代码,可以通过编译时解析注解生成.class类,统一去处理,所以学习自定义注解还是很有必要的。

上述源码地址:https://github.com/crazyqiang/JavaStudy

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/230500.html原文链接:https://javaforall.net

(0)
上一篇 2026年2月19日 上午11:01
下一篇 2026年2月19日 上午11:22


相关推荐

  • Golang中实现PHP的Addslashes和Stripslashes

    Golang中实现PHP的Addslashes和Stripslashespackagemain addslashes 函数返回在预定义字符之前添加反斜杠的字符串 预定义字符是 单引号 双引号 反斜杠 funcAddslash strstring string tmpRune rune strRune rune str for ch rangestr

    2026年3月26日
    2
  • 网站被恶意刷流量解决方案

    网站被恶意刷流量解决方案很多站长朋友可能会经常遇到被同行竞争对手恶意刷流量的情况,而且流量ip来路是随机的,全国各地乃至全世界的ip都有,根本没办法查出来是谁干的。一般出现这种情况都是对方用流量宝或者流量精灵来刷你网站的,目的很明显,对方要么就是用这些垃圾流量来掩盖自己的ip,从而达到攻击入侵等不可告人的目的,要么就是想用恶意刷流量的方式让你合作的广告联盟帐号被封禁。大部分站长都会对此束手无策,有些甚至被吓得撤下广告,关…

    2026年4月17日
    3
  • 彻底搞懂Python的字符编码

    彻底搞懂Python的字符编码前言 中文编码问题一直是程序员头疼的问题 而 Python2 中的字符编码足矣令新手抓狂 本文将尽量用通俗的语言带大家彻底的了解字符编码以及 Python2 和 3 中的各种编码问题 一 什么是字符编码 要彻底解决字符编码的问题就不能不去了解到底什么是字符编码 计算机从本质上来说只认识二进制中的 0 和 1 可以说任何数据在计算机中实际的物理表现形式也就是 0 和 1 如果你将硬盘拆开 你是看不到所谓的数字 0 和 1 的 你能看

    2026年3月17日
    2
  • source insight 序列号_gxworks3安装序列号

    source insight 序列号_gxworks3安装序列号2019独角兽企业重金招聘Python工程师标准>>>…

    2026年4月17日
    4
  • JDBC-三层架构

    JDBC-三层架构

    2021年10月3日
    43
  • 用户 不在 sudoers 文件中。此事将被报告。

    用户 不在 sudoers 文件中。此事将被报告。文章目录背景解决方案背景普通linux用户使用sudo命令执行只有root用户才可以执行的命令时出现了该错误,如下图示:简单说明一下操作。命令$ll/etc/sudoers表示查看文件的属性,属性包括有:文件拥有者、文件所属组以及其他用户组对该文件拥有的读写权限和文件的类型等,上图的/etc/sudoers文件表示拥有者和所属组都是root且只能读取,其他用户组的没有任何读写权限。命…

    2022年6月20日
    46

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号