注解,其实就是这么简单

深入理解注解

1、基本介绍

基本概念:注解,顾名思义,就是对某一事物进行添加注释说明,会存放一些信息,这些信息可能对以后某个时段来说是很有用处的。Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。Java 语言中的类、方法、变量、参数和包等都可以被标注(添加某些信息)。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以通过反射的方式获取到标注内容 。 当然它也支持自定义 的Java 标注。

注解与注释的区别

定义不同:注解与类、接口在同一层次的,是一种描述数据的数据,可以理解为注解就是源代码的元数据。注释则是对源代码的介绍,方便开发者理解代码的所撰写的文字。

作用不同:注解是Java 编译器可以理解的部分,是给编译器看的。通过标记包、类、字段、方法、局部变量、方法参数等元数据,告诉jvm这些元数据的信息。注释是程序员对源代码做一些记忆或提示性描述,是给人来看的。它能告诉开发者这段代码的逻辑、说明、特点等内容,对代码起到解释、说明的作用。

使用范围不同:使用范围不同:注解 ,参与代码编译,以@开头的,与工具一起使用。对于位置、语法、内容有一定的限制。注释 ,可以随意在任务位置填写内容,对代码任何没有影响。

总之,注解可以理解为对类、变量、方法和接口进行规范和约束,注释则理为开发者对代码进行解释而撰写的文字。

注解可以根据来源可以分为系统注解、自定义注解和第三方注解,系统注解根据用途可以分为内置注解和元注解,在下面的文章中,我们主要讲解内置注解、元注解和自定义注解。

2 内置注解

在java.lang包下存在着我们经常看到的注解,分别是@Deprecated、@Override和和@SuppressWarnings

2.1@Deprecated注解

@Deprecated可以修饰类、方法和变量,被@Deprecated修饰后表示不建议使用,它的存在仅仅是为了兼容以前的程序,由于不能直接把它抛弃,所以将它设置为过时。但是被这个注解修饰的类、方法在高版本的JDK中使用时了可能会出现错误。

源代码如下

@Documented@Retention(RetentionPolicy.RUNTIME)@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})public@interfaceDeprecated {}复制代码

其中@Documented、@Retention和@Target是元注解,我们在下文介绍

2.2@Override注解

它表明了被注解的方法需要重写父类中的方法,如果某个方法使用了该注解,却没有覆写超类中的方法,编译器就会报出错误。在子类中重写父类或接口的方法,@Overide并不是必须的。但是还是建议使用这个注解,因为在某些情况下,假设你修改了父类的方法的名字,那么之前重写的子类方法将不再属于重写,如果没有@Overide,你将不会察觉到这个子类的方法。有了这个注解修饰,编译器则会提示你这些信息。

源代码如下

@Target(ElementType.METHOD)@Retention(RetentionPolicy.SOURCE)public@interfaceOverride {}复制代码

2.3@SuppressWarnings注解

@SuppressWarnings用来抑制编译器生成警告信息,可以修饰的元素为类,方法,方法参数,属性,局部变量。它可以达到抑制编译器编译时产生警告的目的,使用@SuppressWarnings注解,采用就近原则,比如一个方法出现警告,尽量使用@SuppressWarnings注解这个方法,而不是注解方法所在的类。所属范围越小越好,因为范围大了,不利于发现该类下其他方法的警告信息。 但是我们通常不建议使用@SuppressWarnings注解,使用此注解,开发人员看不到编译时编译器提示的相应的警告,不能选择更好、更新的类、方法或者不能编写更规范的编码。同时后期更新JDK、jar包等源码时,使用@SuppressWarnings注解的代码可能受新的JDK、jar包代码的支持,出现错误,仍然需要修改。

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})@Retention(RetentionPolicy.SOURCE)public@interfaceSuppressWarnings {String[]value();}复制代码

3、元注解简介

在上面的代码中,我们看到了注解上还有注解,这种修饰注解的注解被称为元注解。事实上,元注解(meta-annotation)的作用就是注解其它的注解,Java在java.lang.annotation包中定义了4个标准的元注解类型,分别为@Target、@Retention、@Documented和@Inherited

3.1@Target

@Target用于描述注解的使用范围,即被描述的注解可以用到什么地方,@Target注解内定义了ElemenType[]数组,数组以枚举类的形式定义了注解的修饰范围。通过下面的源代码我们可以看出,该注解能够用于类、接口、构造器、属性和方法、参数声明、注解声明等

源代码如下

@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)public@interfaceTarget {ElementType[]value();}复制代码

publicenum ElementType {/** Class, interface (including annotation type), or enum declaration */TYPE,/** Field declaration (includes enum constants) */FIELD,/** Method declaration */METHOD,/** Formal parameter declaration */PARAMETER,/** Constructor declaration */CONSTRUCTOR,/** Local variable declaration */LOCAL_VARIABLE,/** Annotation type declaration */ANNOTATION_TYPE,/** Package declaration */PACKAGE,/**    * Type parameter declaration    *    *@since1.8    */TYPE_PARAMETER,/**    * Use of a type    *    *@since1.8    */TYPE_USE}复制代码

3.2@Rentention

@Rentention表示需要在什么级别保存该注释信息,用于描述注解的生命周期,通过源代码,我们可以看出,注解内有个RetentionPolicy的值,我们继续深入往下看,RetentionPolicy是个枚举类型的值,它有三个值可供选择,SOURCE是在源代码层面,在编译是将会失效;CLASS作用在class文件中,但是在运行时失效;RUNTIME在运行时依旧保留该注解,因此可以通过反射机制来读取注解内的信息。因此,这三个值对应的生命周期大小为:SOURCE<CLASS<RUNTIME,如果不手动添加的话,则默认为CLASS。

源代码如下

@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)public@interfaceRetention {/**

    * Returns the retention policy.

    * @return the retention policy

    */RetentionPolicyvalue();}复制代码

publicenumRetentionPolicy{/**

    * Annotations are to be discarded by the compiler.

    */SOURCE,/**

    * Annotations are to be recorded in the class file by the compiler

    * but need not be retained by the VM at run time.  This is the default

    * behavior.

    */CLASS,/**

    * Annotations are to be recorded in the class file by the compiler and

    * retained by the VM at run time, so they may be read reflectively.

    *

    * @see java.lang.reflect.AnnotatedElement

    */RUNTIME}复制代码

3.3@Documented

@Documented表明该注解将被包含在javadoc中。该注解用的相对较少。

源代码如下

@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)public@interfaceDocumented {}复制代码

3.4@Inherited

@Inherited说明子类可以继承父类中的该注解

@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)public@interfaceInherited {}复制代码

4、自定义注解

通过使用元注解的组合,我们可以按照自己的需求来自定义注解,我们通常使用@interface来自定义注解,它自动继承了java.lang.annotation.Annotation接口。接下,我们来以一段代码仔细分析下如何自定义注解:

注解一

public@interfaceMyAnnotation{}复制代码

注解一是一个最简单的自定义注解,我们可以看到,它以public修饰,以@interface用来声明一个注解,具体的格式为:public @interface 注解名{定义内容},如果要在注解内添加一个参数,该怎样定义呢?

注解二

@Retention(value = RetentionPolicy.RUNTIME)@interfacemyAnnotantion3{//参数名为vale,当注解内只有一个参数,使用注解时,参数名可省略Stringvalue();}复制代码

特别需要注意的是,注解内的方法名称就是参数的名称,而返回值类型就是参数类型,我们可以这样使用

@Retention(value = RetentionPolicy.RUNTIME)@myAnnotantion3("snow")    public void test2(){    }复制代码

因为注解内只有一个参数,所以在使用注解时,参数名称是可以省略的。

如果,我们我们想添加多个的参数值,该怎么自定义注解呢

注解三

Target(value= {ElementType.METHOD,ElementType.TYPE})@Retention(value= RetentionPolicy.RUNTIME)@interfacemyAnnotaion2{/**

    *表示注解的参数,name参数名,String 表示参数的类型

    * 参数加default,在注解内可写可不写

    */Stringname();intage();intid();}复制代码

同理,我们只需要在相应的位置引用该注解即可

@myAnnotaion2(name ="Simon",age=25,id=23)publicvoidtest1(){    }复制代码

如果我们向给注解内的参数设定默认值,我们可以这样做

注解四

@Target(value= {ElementType.METHOD,ElementType.TYPE})@Retention(value= RetentionPolicy.RUNTIME)@interfacemyAnnotaion2{/**

    *表示注解的参数,name参数名,String 表示参数的类型

    * 参数加default,在注解内可写可不写

    */Stringname()default"";intage()default0;intid()default-1;}复制代码

在实际的方法使用中,我们只需要给必须要修改的值赋值即可

@myAnnotaion2(name ="Simon")publicvoidtest1(){        System.out.println("测试注解1");    }复制代码

因此,自定义注解可以归纳如下:

@interface用来声明一个注解,格式:public @interface 注解名{定义内容}

其中每一个方法实际上声明了一个配置参数

方法的名称就是参数的名称

返回值类型就是参数的类型(返回值只能时基本类型,Class,String,enum)

可以通过default用来声明参数的默认值

如果只有一个参数成员,一般参数名为value

注解元素必须要有值,我们定义注解元素时,经常使用空字符串0作为默认值

Java学习交流群:1106441130 欢迎讨论交流,另外可免费领取一份(Java学习视频,技术文档,电子书籍,面试等资料...)

5、获取注解中的参数值

在以上的讲解中,我们使用注解都是对所修饰的类、方法、变量进行规范和约束,在大多数使用场景中,以方法为例,我们需要将注解中的信息同方法联系起来,即将注解中的参数信息的注入到方法中。

5.1参数值是基本类型

首次,我们创建一个带有参数的注解

@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})@Retention(value= RetentionPolicy.RUNTIME)@interfaceMyAnnotation5{Stringname()default" ";intage()default0;}复制代码

将该注解修饰到某一方法上

@MyAnnotation5(name ="Simon", age =25)    public void testInjectValue(String name,int age) {System.out.println("获取注解中的参数值:");System.out.println(name);System.out.println(age);    }  复制代码

反射获取注解中的参数并注入到方法中

反射获取该类的方法 通过方法获取注解中的参数值 将注解中的参数值注入到相应的方法中

//反射获取类,并得到类中的方法        Class aClass = InjectValue.class;        Method method=aClass.getMethod("testInjectValue",String.class,int.class);//获取注解中的属性值MyAnnotation5 myAnnotation5=method.getAnnotation(MyAnnotation5.class);        String name=myAnnotation5.name();intage=myAnnotation5.age();//将属性值注入到相应的方法中Object o=aClass.newInstance();        method.invoke(o,name,age);复制代码

5.2参数值是对象

前面我们讲解如何将注解中的参数为基本数据类型注入到方法中,那么如何将注解中的参数为对象注入到方法中呢?

创建一个类用于生成对象

publicclassAnimal{privateString name;privateintage;publicStringgetName(){returnname;    }publicvoidsetName(String name){this.name = name;    }publicintgetAge(){returnage;    }publicvoidsetAge(intage){this.age = age;    }}复制代码

创建一个类,类中的属性是上一个类的对象,并创建一个带参数的注解,参数的类型为对象

publicclassAnimalDao{privateAnimal animal;publicAnimalgetAnimal(){returnanimal;    }@MyAnnotation6(name ="Dog",age =12)publicvoidsetAnimal(Animal animal){this.animal = animal;    }}复制代码

获取注解中的对象

publicclassTestInjectObject{publicstaticvoidmain(String[] args)throwsException{//1.使用PropertyDescriptor得到想要注入的属性PropertyDescriptor descriptor =newPropertyDescriptor("animal", AnimalDao.class);//2.得到要想注入属性的具体对象Animal animal = (Animal) descriptor.getPropertyType().newInstance();//3.得到该属性的写方法Method method = descriptor.getWriteMethod();//4.得到写方法的注解Annotation annotation = method.getAnnotation(MyAnnotation6.class);//5.得到注解上的信息Method[] methods = annotation.getClass().getMethods();//6.将注解上的信息填充到animal对象上for(Method m : methods) {//得到注解上属性的名字String name = m.getName();//看看animal对象有没有与之对应的方法try{                PropertyDescriptor descriptor1 =newPropertyDescriptor(name, Animal.class);                Method method1 = descriptor1.getWriteMethod();//得到注解中的值Object o = m.invoke(annotation,null);//调用animal对象的setter方法,将注解上的值设置进去method1.invoke(animal, o);            }catch(Exception e) {continue;            }        }        AnimalDao animalDao =newAnimalDao();        method.invoke(animalDao, animal);        System.out.println(animalDao.getAnimal().getName());        System.out.println(animalDao.getAnimal().getAge());    }}@Target(value = {ElementType.METHOD, ElementType.TYPE})@Retention(value = RetentionPolicy.RUNTIME)@interfaceMyAnnotation6 {Stringname();intage();}复制代码

因此,将对象注入到方法中可以总结如下

创建想要获得属性的对象

根据对象获取该属性的方法

得到方法中的注解

获取注解中的信息

将注解信息注入到对象中

将对象中的属性写入到方法中

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343