java注解和自定义注解的简单使用

前言

在使用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.一个注解可以拥有多个成员,成员声明和接口方法声明类似,成员声明有规范限制。

原文地址:https://www.cnblogs.com/yanggb/p/10374500.html