iis服务器助手广告广告
返回顶部
首页 > 资讯 > 精选 >Java怎么自定义注解
  • 216
分享到

Java怎么自定义注解

2023-07-05 14:07:07 216人浏览 安东尼
摘要

这篇文章主要介绍“Java怎么自定义注解”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Java怎么自定义注解”文章能帮助大家解决问题。注解注解为我们在代码中添加信息提供一种形式化的方法,使我们可以在

这篇文章主要介绍“Java怎么自定义注解”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Java怎么自定义注解”文章能帮助大家解决问题。

注解

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

注解是在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作用范围如下图所示:

Java怎么自定义注解

注解处理器

首先来自定义一个注解:

@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);    }        @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();    }        @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

写好之后的目录如下:

Java怎么自定义注解

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

注: ----------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库来帮我们减少工作量。

来看简单的一个栗子:

                //创建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 generatedpackage org.ninetripods.MQ.javastudy;public final class autoGenerate {  public static int getValue() {    return 100;  }}

关于“Java怎么自定义注解”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注编程网精选频道,小编每天都会为大家更新不同的知识点。

--结束END--

本文标题: Java怎么自定义注解

本文链接: https://www.lsjlt.com/news/352322.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • c#程序自启动怎么设置
    c# 程序的自启动方法有三种:注册表:在指定注册表项下创建新值,并将其设置为程序可执行文件路径。任务计划程序:创建一个新任务,并在触发器和动作部分分别指定登录时或特定时间触发,以及启动程...
    99+
    2024-05-14
    c#
  • c#怎么调用dll文件
    可在 c# 中轻松调用 dll 文件:引用 dll(使用 dllimport 特性)定义与 dll 函数签名匹配的函数原型调用 dll 函数(如同 c# 函数)附加技巧:使用 chars...
    99+
    2024-05-14
    c#
  • 如何构建 Golang RESTful API,并实现 CRUD 操作?
    通过创建 golang 项目并安装必要的包,我们可以构建一个功能齐全的 restful api。它使用 mysql 数据库进行 crud 操作:1. 创建和连接数据库;2. 定义数据结构...
    99+
    2024-05-14
    go crud mysql git golang
  • c#怎么添加类文件
    在c#中添加类文件的步骤:1. 创建新项目,2. 添加新类,3. 为类添加代码,4. 在另一个类中引用新类。using语句引用类文件所在的命名空间;new运算符创建类的新实例;点运算符访...
    99+
    2024-05-14
    c#
  • 使用 C++ 构建高性能服务器架构的最佳实践
    遵循 c++++ 中构建高性能服务器架构的最佳实践可以创建可扩展、可靠且可维护的系统:使用线程池以重用线程,提高性能。利用协程减少上下文切换和内存开销,提升性能。通过智能指针和引用计数优...
    99+
    2024-05-14
    c++ 高性能服务器架构 数据访问
  • c#怎么添加字段
    在 c# 中添加字段包括以下步骤:声明字段:在类或结构中使用 字段类型 字段名; 语法声明字段。访问修饰符:用于限制对字段的访问,如 private、public、protected 和...
    99+
    2024-05-14
    c#
  • c#中怎么添加引用
    c# 中添加引用的方法有四种:使用 nuget 包管理器添加软件包。添加项目引用以包含其他项目。手动编辑项目文件 (.csproj) 以添加引用。从编译器命令行使用 /reference...
    99+
    2024-05-14
    c#
  • c#怎么创建文本文件
    在 c# 中创建文本文件的方法包括:创建 filestream 对象以打开或创建文件。使用 streamwriter 写入文本至文件。关闭 streamwriter 对象释放资源。关闭 ...
    99+
    2024-05-14
    c#
  • c#怎么定义属性
    如何在 c# 中定义属性 属性是一种编程构造,它包含一个 get 访问器和一个 set 访问器,允许以一种类属性的方式访问字段。它们提供了一种安全且封装的方式来访问和修改类的内部数据。 ...
    99+
    2024-05-14
    c#
  • 基于 C++ 的服务器架构的安全性考虑因素
    在设计基于 c++++ 的服务器架构时,安全考虑至关重要:使用 std::string 或 std::vector 避免缓冲区溢出。使用正则表达式或库函数验证用户输入。采用输出转义防止跨...
    99+
    2024-05-14
    安全性 关键词: c++ 服务器架构 c++ lsp
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作