前言

在使用Spring Boot的时候,大量使用注解的语法去替代XML配置文件,十分好用。

然而,在使用注解的时候只知道使用,却不知道原理。直到需要用到自定义注解的时候,才发现对注解原理一无所知,所以要学习一下。

特别注意的是,注解是Java提供的语法,而不是Spring特殊的语法。Java自5.0版本开始引入注解之后,注解就成为了Java平台中非常重要的一部分。

什么是注解(Annotation)

注解(Annotation)是一种应用于类、方法、参数、变量、构造器及包声明中的特殊修饰符。它是一种由JSR-175标准选择用来描述元数据的工具。

用一个词来描述注解,那就是元数据(描述数据的数据)。 那么就可以说,注解是源代码的元数据。

@Override
public String toString() {
return "良辰美景奈何天";
}

在上面的代码中,我重写了toString()方法并在方法上使用了@Override注解。但是,即使我不使用@Override注解标记代码,程序也能正常执行。那么为什么还要使用注解呢?事实上,@Override注解会告诉编译器这个方法是一个重写的方法(描述方法的元数据),如果父类中不存在该方法,编译器便会报错,提示该方法没有重写父类中的方法。但是如果我不小心拼写错误,例如将toString()写成了toSpring(),而且我也没有使用@Override注解,那么程序依然能够编译运行,虽然运行结果会和我期望的大不相同。到这里我们就可以明白什么是注解,还有使用注解既可以帮助编译器检查代码,也可以帮助我们阅读程序。

注解的用途

注解仅仅是元数据,和业务逻辑无关。但是元数据的用户可以通过读取这些元数据并实现必要的逻辑(第三方代码,注解不能干扰源代码的编译运行),以此来附加相应的业务操作。

生成文档。比如我们用的IDE里面会自动加上的@param,@return,@author等注解。

编译时检查格式。比如@Override,@SuppressWarnings等注解。

跟踪代码依赖性,实现替代配置文件功能。比如@Configuration,@Bean等注解。

Java类库提供的元注解

Java类库中提供了java.lang.annotation包,包中包含了元注解和接口,定义自定义注解所需要的所有内容都在这个包中。

比如java.lang.annotation.Annotation是所有注解自动继承的接口,不需要定义时指定,类似于所有类都自动继承Object一样。

@Documented,@Retention,@Target,@Inherited是Java提供的4个元注解。

@Documented注解表示将此注解包含在javadoc中。

@Documented // 生成文档
@Retention(RetentionPolicy.RUNTIME) // 注解在运行期级别保留注解信息
@Target(ElementType.ANNOTATION_TYPE) // 注解放置的目标位置,ANNOTATION_TYPE是可注解在注解定义上
public @interface Documented { }

使用此注解的类会被javadoc工具提取成文档。在doc文档中的内容会因为此注解的信息内容不同而不同,相当于@see,@param等。

@Retention注解表示在什么级别保留该注解信息。

@Documented // 生成文档
@Retention(RetentionPolicy.RUNTIME) // 注解在运行期级别保留注解信息
@Target(ElementType.ANNOTATION_TYPE) // 注解放置的目标位置,ANNOTATION_TYPE表示是可注解在注解定义上
public @interface Retention { RetentionPolicy value(); // 在哪个阶段保留注解信息的级别,必须参数(无默认值)
}

可选的参数值在枚举类型RetentionPolicy中。

public enum RetentionPolicy {
/**
* 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文件中可用,但会被VM抛弃
*/
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.
* VM将在运行期也保留注释,因此可以通过反射机制读取注解信息
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}

@Target注解表示该注解用于什么地方(可以注解的目标位置)。

@Documented // 生成文档
@Retention(RetentionPolicy.RUNTIME) // 注解在运行期级别保留注解信息
@Target(ElementType.ANNOTATION_TYPE) // 注解放置的目标位置,这里表示可以注解在注解定义上
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value(); // 注解的目标位置,必须参数(无默认值)
}

可能的参数值在枚举类型ElementType中。

public enum ElementType {
/**
* Class, interface (including annotation type), or enum declaration
* 类,接口(包括注解类型)或enum声明
*/
TYPE, /**
* Field declaration (includes enum constants)
* 域声明(包括enum实例)
*/
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
* 类型变量声明
* @since 1.8 新特性
*/
TYPE_PARAMETER, /**
* Use of a type
* 使用类型的任何语句(声明、泛型和强制转换语句中的类型)
* @since 1.8 新特性
*/
TYPE_USE
}

@Inherited注解表示允许子类继承父类中的注解。

@Documented // 生成文档
@Retention(RetentionPolicy.RUNTIME) // 注解在运行期级别上保留注解信息
@Target(ElementType.ANNOTATION_TYPE) // 注解放置的目标位置,ANNOTATION_TYPE是可注解在注解定义上
public @interface Inherited { }

自定义注解 

使用@interface关键字来自定义注解时,自动继承java.lang.annotation.Annotation接口(隐式继承),由编译程序自动完成其它细节。在定义注解时,不能显式继承其它的注解或接口。@interface关键字用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum),可以通过default来声明参数的默认值。

定义注解的通用格式:

public @interface 注解名 {
访问修饰符 返回值类型 参数名() default 默认值;
}

注解参数成员的可支持数据类型:

1.所有基本数据类型(int,float,boolean,byte,double,char,long,short)

2.String类型

3.Class类型

4.enum类型

5.Annotation类型

6.以上所有类型的数组

注解参数的规范:

1.只能用public或默认(default)这两个访问修饰符修饰。一般使用default,即不指定访问修饰符。

2.参数成员只能用上面列出的、注解参数支持的数据类型。

3.如果只有一个参数成员,最好把参数名称设为value。

简单使用例子:

首先定义注解。

@Documented
@Target({ElementType.METHOD, ElementType.TYPE}) // 注解放置的目标位置,这里可以添加在METHOD上,也可以添加在TYPE上
@Retention(RetentionPolicy.RUNTIME) // 注解在运行期保留注解信息,在运行期可以通过反射获取注解信息
public @interface MyAnnotation { String name() default ""; // 姓名,默认值空字符串 String sex() default "男"; // 性别,默认值男
}

将注解使用在类上。

@MyAnnotation(name = "谢小猫")
public class MyAnnotationOnClass { }

将注解使用在方法上。

public class MyAnnotationOnMethod {

    @MyAnnotation(name = "赵小虾")
public void zhaoShrimp() { } @MyAnnotation(name = "李小猪", sex = "女")
public void liPig() { }
}

最后通过反射获取注解信息并打印出来。

public class MyAnnotationTest {

    public static void main(String[] args) {
// 调用Class的isAnnotationPresent方法,检查类MyAnnotationUse是否含有@AnnotationTest注解
if (MyAnnotationOnClass.class.isAnnotationPresent(MyAnnotation.class)) {
// 若存在就获取注解
MyAnnotation myAnnotation = MyAnnotationOnClass.class.getAnnotation(MyAnnotation.class);
System.out.println("完整注解:" + myAnnotation);
// 获取注解属性
System.out.println("性别:" + myAnnotation.sex());
System.out.println("姓名:" + myAnnotation.name());
System.out.println("---------------------------------------------------------");
} // 调用Class的getDeclaredMethods方法,获取MyAnnotationUse的所有方法声明
Method[] methods = MyAnnotationOnMethod.class.getDeclaredMethods();
for (Method method : methods) {
System.out.println("方法声明:" + method);
// 调用Method的isAnnotationPresent方法,检查方法是否含有@AnnotationTest注解
if (method.isAnnotationPresent(MyAnnotation.class)) {
MyAnnotation myAnnotationInMethod = method.getAnnotation(MyAnnotation.class);
System.out.println("方法名:" + method.getName()
+ ",姓名:" + myAnnotationInMethod.name()
+ ",性别:" + myAnnotationInMethod.sex() + ")");
}
}
}
}

查看结果。

通过上面的简单使用例子,我们对注解的使用基本有些了解了:注解的使用与反射是离不开的。我们可以通过Java反射机制读取注解的信息,并根据这些信息更改目标程序的逻辑。

这样,我们就可以利用代码中的注解,达到间接控制程序代码运行的目的。

注解的总结

1.Java中所有注解都隐式继承(自动继承)于java.lang.annotation.Annotation,注解不允许显式继承其他接口。

2.注解不能直接干扰程序代码的运行,无论增加或删除注解,代码都能够正常运行。Java语言解释器会忽略这些注解,而由第三方工具负责对注解进行处理。

3.一个注解可以拥有多个成员,成员声明和接口方法声明类似,成员声明有规范限制。

java注解和自定义注解的简单使用的更多相关文章

  1. Java注解-元数据、注解分类、内置注解和自定义注解|乐字节

    大家好,我是乐字节的小乐,上次说过了Java多态的6大特性|乐字节,接下来我们来看看Java编程里的注解. Java注解有以下几个知识点: 元数据 注解的分类 内置注解 自定义注解 注解处理器 Ser ...

  2. Java中的注解及自定义注解你用的怎么样,能不能像我这样应用自如?

    Java注解提供了关于代码的一些信息,但并不直接作用于它所注解的代码内容.在这个教程当中,我们将学习Java的注解,如何定制注解,注解的使用以及如何通过反射解析注解. Java1.5引入了注解,当前许 ...

  3. Springboot--元注解及自定义注解(表单验证)

    本文简单说明一下元注解,然后对元注解中的@Retention做深入的讨论,在文章最后使用元注解写一个自定义注解来结尾. 一.结论: @Target:注解的作用目标 @Target(ElementTyp ...

  4. Java反射与自定义注解

    反射,在Java常用框架中屡见不鲜.它存在于java.lang.reflact包中,就我的认识,它可以拿到类的字段和方法,及构造方法,还可以生成对象实例等.对深入的机制我暂时还不了解,本篇文章着重在使 ...

  5. java内置注解、元注解和自定义注解

    注解的作用: 1.生成文档 2.跟踪代码依赖性 3.编译时进行格式检查 ---------------------------------------------------------------- ...

  6. Java:深入自定义注解(Annotation)

    在网上找了很多资料也有写的比较好的,但是总有有一点半点的细节没有写出来,在这里自己总结下使用. 使用Java的自定义注解,首先个人需要了解下Java为我们提供的元注解和相关定义注解的语法.(这个我在网 ...

  7. Java注解(自定义注解、view注入)

    注解这东西虽然在jdk1.5就加进来了,但他的存在还是因为使用Afinal框架的view注入才知道的.一直觉得注入特神奇,加了一句就可以把对应view生成了. 下面我们来认识一下注解这个东西 一.注解 ...

  8. Java中的自定义注解

    ## 元注解 要声明一个注解, 我们需要元注解, 元注解是指注解的注解,包括@Retention, @Target, @Document, @Inherited. @Retention 注解的保留位置 ...

  9. Spring注解之-自定义注解

    1.自定义注解,先自定义三个水果属性的注解 元注解: java.lang.annotation提供了四种元注解,专门注解其他的注解(在自定义注解的时候,需要使用到元注解):   @Documented ...

随机推荐

  1. jq stop()和:is(":animated")用法区别

    stop(true,true): 表示停止匹配元素正在进行的动画并跳转到末状态,清空未执行完的动画队列.常用于”解决光标移入移出得过快导致的动画效果与光标动作不一致“问题! jQuery stop() ...

  2. 三、安装cmake,安装resin ,tars服务,mysql 安装介绍,安装jdk,安装maven,c++ 开发环境安装

    三.安装cmake,安装resin 2018年07月01日 21:32:05 youz1976 阅读数:308   开发环境说明: centos7.2 ,最低配置:1核cpu,2G内存,1M带宽 1. ...

  3. MyBatis知识点总结(一)

    前言:本篇主要记录在MyBatis学习过程中的主要知识点. 1.mybatis环境的搭建,通过maven可以快速的进行环境的搭建. <!--文件版本--> <properties&g ...

  4. 2018年6月,最新php工程师面试总结

    面试经常被问到的问题总结 1.字符串函数 2.数组函数 3.cookie和session的区别 4.状态码以及其功能

  5. 【足迹C++primer】32、定制操作_2

    版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/cutter_point/article/details/32301839 定制操作_2 完整的big ...

  6. 简单的C#TCP协议收发数据示例

    参考:http://www.cnblogs.com/jzxx/p/5630516.html 一.原作者的这段话很好,先引用一下: Socket的Send方法,并非大家想象中的从一个端口发送消息到另一个 ...

  7. 【转】如何判断ARP欺骗?该怎么防护?

    因为在进行通信的时候,数据是通过MAC地址与IP地址的对应关系来进行转发的.若其中MAC地址与IP地址对应的关系出错,就会导致数据错误转发,影响正常通信.通过某种手段,来更改MAC与IP地址的对应关系 ...

  8. TFT1.44显示屏

    下载这个库 普通arduino的接口 链接 UTFT myGLCD(LPH9135,6,5,2,3,4); mega2560的接口连接 UTFT myGLCD(QD_TFT180A,A2,A1,A5, ...

  9. (predicted == labels).sum().item()作用

    ⚠️(predicted == labels).sum().item()作用,举个小例子介绍: # -*- coding: utf-8 -*-import torch import numpy as ...

  10. p1012拼数题解

    #include<iostream> #include<algorithm> using namespace std; bool cmp(string b,string a) ...