初识 Java 注解

492 阅读8分钟

正在学习的小白。不当之处,请多多指教

目录

  • 前言
  • 自定义注解的使用
  • 元注解
  • 注解的解析反射
  • 解析注解的作用(引入IOC概念)

前言

java中有四大类型,其中三个是:枚举,接口,类。

今天简单认识一下第四个类型:注解。

什么是注解

Annotation 这里是"注解"的意思。
除此之外,这个单词患有一个“注释”的意思。我们都知道,注释是给程序员看的。那么注解呢?
注解是给程序看的,所以Annotation既有注解也有注释的意思

我们很早就见过一些注解:jdk中的@Override,@FunctionalInterface,Junit框架的@Test等。

注解的基本语法

枚举,接口,类,这个三个类型我们都写过(他们的源码 ),基本语法,想必大家都知道。

那么注解有源码吗?答案是肯定的,jvm没有那么厉害,不可能仅凭一个@+一个单词就知道程序想表达什么。

我们以@FunctionalInterface(关于这个注解不清楚的可以参考函数式编程的内容)为例:

@FunctionalInterface
interface UsB{
    void show();
}

按住Ctrl点击注解进入,就可以看到@FunctionalInterface的源码:

package java.lang;
import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}

先不着急认识这些是什么意思,看懂其构成就可以,然后我们照猫画虎,自己写一个:

/**
* 自定义注解
*/
@interface MyAnnotation{ }
  1. 于是,我们完成了一个自定义注解,由此,可以基本知道注解的基本构成:

@interface 注解名字{ }

那么,注解有像其他普通类一样的属性吗?

  1. 有,但是,注解的属性不太一样,他是这样写的,

String name(); 注解中 这是属性,不是接口中的方法。

  1. 既然是属性,那就可以赋值:

注解属性赋值语法:String name() default "属性";

(ps:可以赋值,但一般不再内部赋值,给外部使用者赋值)

  1. 那么属性这样写,注解有方法吗?有的话的方法该怎么写?

不好意思,注解是没有方法的!

  1. 普通类中的属性,可以是任意类型,那么注解也一样吗??

注解对属性类型是有要求的:

8个基本数据类型 / 字符串类型 / Class类型 / 注解类型 / 枚举类型 及其一维数组

  1. 属性补充:

注解中只有一个属性, 那么请将该属性定义为 value 名称. 好处: 使用该注解时可以省略 value=

以上6点就是注解的基本语法。

自定义注解的使用

之前我们使用注解,都是固定的,比如@Override只能在(重写)方法使用,@FunctionalInterface只能在(有且仅有一个要实现的方法的)接口使用。乳沟随便使用,就会立刻编译报错。 那么,使用我们刚刚自定义的注解,使用上有限制吗?

@MyAnnotation("省略了value")//可以用在类上
public class AnnotationDemo {
    @MyAnnotation("zhangsan") String name;//可以用在属性上
    @MyAnnotation("show") //可以用在方法上
    public void show(){}
}

//自定义注解
@interface MyAnnotation{
    //属性
   String value();
}

元注解

这样看来,我们目前自定义的注解是没有任何使用位置的限制的,再回头看看,前面@FunctionalInterface注解的源码,或者@Override的源码,发现我们自定义的注解少了几样东西。没错,少了一些注解。准确说,少了一些 “元注解”

package java.lang;
import java.lang.annotation.*;
//三个元注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}

什么是元注解,“元”意为:最开始,初始的意思,那么元注解就是其实的注解,或者叫,注解的注解。是用来修饰说明注解的。

元注解都来自于:java.lang.annotation.* 下

先来认识一下元注解:

@Target

意为目标,也就是说明注解的使用范围

来看一下@Target的源码:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}

可以看到,他也有自己的元注解,和一个一维数组属性ElementType[],进入ElementType[],我们可以看到,ElementType是一个枚举类(控制文本长度,去除了所有源码的注释):

public enum ElementType {
    TYPE,
    FIELD,
    METHOD,
    PARAMETER,
    CONSTRUCTOR,
    LOCAL_VARIABLE,
    ANNOTATION_TYPE,
    PACKAGE,
    TYPE_PARAMETER,
    TYPE_USE
}

这些枚举类型很容易看出,Target注解的value值,可以是这些枚举元素,例如:FIELD表示使用在属性上,METHOD可以使用在方法上,等等,不在一一说明。

使用: 、

@Target({ElementType.FIELD,ElementType.METHOD}) 注意,多个值用中括号,属性名为value,可省略

可以加在我们前面写的自定义注解上看看效果。

@Retention

意为,保留策略,又称之为生命周期。

我们继续进入@Retention的源码:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    RetentionPolicy value();
}

也只有一个属性,进入RetentionPolicy查看:

public enum RetentionPolicy {
    SOURCE,
    CLASS,
    RUNTIME
}

RetentionPolicy也是一个枚举类,只有三个值。

这三个值很好理解,他与java程序的声明周期是一一对应的:

源码阶段(SOURCE),编译阶段(CLASS),运行阶段(RUNTIME)。

@Retention的属性不是数组,所以只能选择一个值

如:@Retention(RetentionPolicy.CLASS)

ps : RetentionPolicy.RUNTIME 最常用,因为通常和反射结合使用,而反射是在运行时操作类。

注解的反射解析

(不了解反射可以参考我的另一篇笔记:Java 反射机制那些事

我们通过代码的方式,简单说明下,如何利用反射解析注解

先来准备一个 Student类:

public class Student {
    //一个属性
    public String name;
    public Student() { }
    //一个构造
    public Student(String name) { this.name = name; }
    //一个方法
    public void show(String msg){
        System.out.println("show方法 = " + msg);
    }
    //重写toString
    @Override
    public String toString() {
        return "Student{name= "+name+"}';
    }
}

再来写一个自定义注解:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.CONSTRUCTOR})
public @interface MyAnnotation{
   String value();
}

然后,根据自定义注解的作用范围,在Student类上加上我们自定义的注解,然后赋上一些值:

    @MyAnnotation("小明")
    public Student(String name) {
        this.name = name;
    }
    @MyAnnotation("小红来了")
    public void show(String msg){
        System.out.println("show方法 = " + msg);
    }

在新建一个类,实现我们的反射部分代码,这里我的类就叫做:AnnotationDemo

补充:

注解 某种意义上讲可以作为 一种配置文件(常见的配置文件 .properties 或者 .xml)

​ 既然写了一个文件,就要去对他进行一些 读写 操作,如果不去读取并使用他的内容,呢这个(配置)文件存在在程序中有什么意义??文件是保存信息数据的,所以文件不读出来,是没有意义的。

​ 而读出配置文件的内容 称之为 解析!

​ 注解有个解析技术,叫做: 反射!

​ 写了注解,就相当于,写了配置文件不读取!是没有意义的。

反射是在运行时,操作Class对象,注解写在Student类中,所以,反射可以操作Student的Class对象。

我们就先AnnotationDemo类中在利用反射获取一个Student里面的自定义注解:

public class AnnotationDemo {
	public static void main(String[] args) throws NoSuchMethodException {
        //1.获取Student的Class对象
        Class<?> clazz = Student.class;
        //2.先从构造器开刀,找到构造器
        Constructor<?> constructor = clazz.getConstructor(String.class);
        /**
         * 3.使用构造起的方法 isAnnotationPresent(),
         *   方法意为:有没有(参数)注解存在?注意:(一个方法。类等可以有多个注解)
         *   参数:注解类型的class对象
         *   返回值:存在(true),
         */
        boolean annotation = constructor.isAnnotationPresent(MyAnnotation.class);
        //如果存在,来获取这个注解
        if (annotation) {
            /*
                getAnnotation(注解.class)获取注解
                此时获取到 Student满参构造上的注解
             */
            MyAnnotation myAnnotation = constructor.getAnnotation(MyAnnotation.class);
            //注解有个属性叫value
            String value = myAnnotation.value();
            System.out.println("value = " + value);        //value = 小明
        }
    }
}

以上代码,就将 Student满参构造上的注解的属性值读取出来了。

解析注解作用(引入IOC概念)

还是和反射一样的问题?这样解析注解有什么用?

这要结合具体场景,有些项目中可能要自己定义注解使用,而最多使用的地方就是框架。

题外话——引入IOC概念

上面的示例代码我们可以看到,AnnotationDemo类中的一系列代码,就获取到了Student的满参构造的注解的value值。那么获取到注解的属性值,我们就可以将值反转到(传入)这个构造里面去。

这个就叫做控制反转(IOC)

怎么传值呢?继续看代码!

//部分代码和上面一样,注释省略
public static void main(String[] args)
            throws NoSuchMethodException, IllegalAccessException,
            InvocationTargetException, InstantiationException {
        Class<?> clazz = Student.class;
        Constructor<?> constructor = clazz.getConstructor(String.class);
        boolean annotation = constructor.isAnnotationPresent(MyAnnotation.class);
        if (annotation) {
            MyAnnotation myAnnotation = constructor.getAnnotation(MyAnnotation.class);
            String value = myAnnotation.value();
            System.out.println("value = " + value);        //value = 小明
            
            /*
                利用  newInstance 方法,就可以获得Student实例
             */
            Object obj = constructor.newInstance(value);
            System.out.println("obj = " + obj);

        }
    }

​ 我们可以想一下,假如AnnotationDemo类和自定义注解,不是我们所写,是一种别人写的框架,自己从来不知道这样一些代码,而只是用一个注解,传了个值,就构造出了一个实例对象。这就是框架技术的一部分底层原理。

​ 接下来解析Student的show方法的注解:

public class AnnotationDemo {
    public static void main(String[] args)
            throws NoSuchMethodException, IllegalAccessException,
            InvocationTargetException, InstantiationException {
        //获取Class对象
        Class<?> clazz = Student.class;
        //获取方法
        Method show = clazz.getMethod("show", String.class);
        boolean b = show.isAnnotationPresent(MyAnnotation.class);
        if (b) {
            //每个反射对象都有这样一个方法,获取注解
            MyAnnotation annotation = show.getAnnotation(MyAnnotation.class);
            String value = annotation.value();
           show.invoke(clazz.newInstance(), value);
           //运行,查看结果
                //show方法 = 小红来了
        }
    }
}