跳至主要內容

18.注解

景天大约 6 分钟

学习目标:

  • 掌握注解的定义与使用

  • 熟悉2个元注解的作用

  • 掌握使用注解处理器获取注解信息

注解与注释

注释

  • 单行注释//
  • 多行注释/* */
  • 文档注释/** */

注释的作用:

传递额外的信息进行解释说明, 给程序员看的

注释不参与编译

注释只有语法形式, 具体内容没有要求
// 年龄在18-25之间, [18,25], 18<=age<=25
boolean judegeAge(int age){
}

什么是注解

Annotation其实是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理

注解的作用

通过使用Annotation,程序开发人员可以在不改变原有逻辑的情况下,在源文件嵌入一些补充信息

Annotation就像修饰符一样被使用,可用于修类、构造器、方法、成员变量、参数...,这些信息被存储在Annotation的“属性名=属性值”对中

注解 VS 注释

  • 相同点
    • 都是用来传递额外信息的
  • 不同点
    • 注解可以参与编译,注释不行
    • 注解有使用范围,注释没有(想咋写咋写)
    • 注解作为一种数据类型,跟class interface具有同等地位

注解定义

语法

权限修饰符 @interface 注解名字{
    // 注解体定义
    属性类型 属性名();
    属性类型 属性名();
    属性类型 属性名();
    ......
}
举例:
public @interface Override {
}

属性类型:
基本数据类型
String类型
Class类型
注解类型
枚举类型
以及以上类型的数组形式

注意:

注解不允许继承

注解和接口的关系:

注解和接口的定义都使用同一个关键字interface,而且注解体的定义也非常类似于接口中的抽象方法。

这当然不是巧合,而是因为注解本身就是一种特殊的接口。查看java.lang.annotation.Annotation接口的JDK文档,原文是:

所有注解类型都隐式扩展自该接口。但要注意,手动扩展该公共接口的接口不会定义为注解类型。还要注意此接口本身不是定义注解型。

也就是说:

  1. 当你使用"@interface"关键字定义一个注解类型时,它会自动实现java.lang.annotation.Annotation接口,即使你没有显式地声明这个继承关系。
  2. 如果使用"interface"关键字定义一个接口类型,并显式地让它继承java.lang.annotation.Annotation接口,那么这个新接口也不会被视为一个注解类型。
  3. java.lang.annotation.Annotation接口本身并不是一个注解类型,它只是一个普通的接口类型。
  4. 注解类型并不能显式地继承其他类或接口,虽然它确实隐式实现了接口java.lang.annotation.Annotation~

当然,以上概念了解即可。虽然注解和接口确实共享了同一个关键字,但它们在实际使用中具有不同的目的和功能,可谓千差万别

元注解

元注解的概念来源于元数据,什么是元数据呢?

image-20230817175904439
image-20230817175904439

图中框起来的一列数据是什么意思呢?表头的学校就负责解释这一列数据,所以这一列数据都代表某个学生的学校信息。

像“学校”这样的,用于解释数据的数据,就是元数据 meta data

元注解:描述修饰注解的注解(注解的注解)

image-20230817180042910
image-20230817180042910

常用元注解:

@Retention元注解,来定义我们自己定义的注解的保留级别.

  • RetentionPolicy.RUNTIME
  • RetentionPolicy.CLASS 默认
  • RetentionPolicy.SOURCE

@Target元注解,注解可以作用的目标

对于注解而言,可以作用的目标:

  1. 整个类 ElementType.TYPE
  2. 成员变量 ElementType.FIELD
  3. 构造方法 ElementType.CONSTRUCTOR
  4. 成员方法 ElementType.METHOD

注解的使用(重点)

类比类对象

User user = new User("zs",20);
User user2 = new User();

使用的时候注解需要通过@符号进行实例化, 对每个属性都要赋值

@注解名(属性1=属性值,属性2=属性值)

解释:

  1. "@"可以认为相当于“new”关键字,必不可少。
  2. 注解相当于给Java代码打上一个标签,所以它必须要修饰Java代码的一个结构。比如修饰一整个类,一整个方法,一个成员变量等等。
  3. 实例化注解时,必须给注解的各个属性赋值,赋值方式是:属性名 = 属性值。如果是数组类型的注解属性,用"{}"赋值。如果有多个属性,赋值时用","隔开。

注意事项:

  • 每个属性都要赋值

  • 可以不赋值,但是要有默认值, default

  • 数组形式赋值 {}

  • 如果只有1个属性, 名字叫value, 可以简化赋值

  • 如果属性类型是引用类型, 不能是null

示例


@MyAnnotation
public class Test {
    @MyAnnotation(666)
    int num;

    @MyAnnotation(value = 777, b = "777", c = {"777","aaa","bb"})
    public void test() {
    }
}

@interface MyAnnotation {
    int value() default 123;

    String b() default "abc";

    String[] c() default {"123"};
}

注解处理器

什么是注解处理器?

  • 获取注解信息, 根据注解信息进行处理

如何获取注解信息?

  • 通过反射获取注解信息
package _24annotation.com.cskaoyan._04handle;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;

/**
 * @description:
 * @author: 景天
 * @date: 2022/10/24 17:41
 **/

public class Demo {
    public static void main(String[] args) throws Exception{
        // 获取字节码文件对象
        Class<?> c = Class.forName("_24annotation.com.cskaoyan._04handle.Demo");
        // 拿到方法对象
        Method loginMethod = c.getDeclaredMethod("login");
        // 判断方法上是否使用了注解
        boolean annotationPresent = loginMethod.isAnnotationPresent(Login.class);
        if (annotationPresent) {
            // 获取注解实例
            Login loginAnnotation = loginMethod.getAnnotation(Login.class);
            // 获取属性值
            String password = loginAnnotation.password();
            String username = loginAnnotation.username();
            // 打印
            System.out.println(password);
            System.out.println(username);

        }else {
            System.out.println("没有使用注解");
        }
    }
    
    @Login
    public static void login(){
        
    }
}

// 定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Login{
    // 属性
    String username() default "admin";

    String password() default "123456";
}

练习:

定义2个注解

AgeLimit 属性 maxAge minAge

NameLimit 属性 length

定义学生类Student 年龄18-25之间 名字长度不超过5

package _24annotation.com.cskaoyan._04handle;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

/**
 * @description:
 * @author: 景天
 * @date: 2022/10/25 9:37
 **/

public class StudentFactory {
    public static Class stuCls;
    static {
        try {
            stuCls = Class.forName("_24annotation.com.cskaoyan._04handle.Student");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    // 提供一个方法 来产生学生对象
    public static Student getInstance(String name,int age) throws NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        // 名字校验
        judgeName(name);

        // 年龄校验
        judgeAge(age);

        // 获取构造方法对象
        Constructor declaredConstructor =
                stuCls.getDeclaredConstructor(String.class, int.class);
        declaredConstructor.setAccessible(true);
        // newInstance实例化对象
        Student student = (Student) declaredConstructor.newInstance(name, age);
        return student;
        // 最终返回学生对象
    }

    private static void judgeAge(int age) throws NoSuchFieldException {
        // 获取age成员变量
        Field ageField = stuCls.getDeclaredField("age");
        // 判断是否用了注解
        boolean annotationPresent = ageField.isAnnotationPresent(AgeLimit.class);
        // 如果用了注解
        if (annotationPresent) {
            // 获取注解实例
            AgeLimit ageLimit = ageField.getAnnotation(AgeLimit.class);
            // 获取属性值
            int maxAge = ageLimit.maxAge();
            int minAge = ageLimit.minAge();
            // 判断是否满足要求
            // 如果不满足要求 抛出异常
            if (age < minAge || age > maxAge) {
                throw new IllegalArgumentException("年龄不合法");
            }

        }
    }

    private static void judgeName(String name) throws NoSuchFieldException {
        // 获取name成员变量
        Field nameField = stuCls.getDeclaredField("name");
        // 判断是否用了注解
        boolean annotationPresent = nameField.isAnnotationPresent(NameLimit.class);
        // 如果用了注解
        if (annotationPresent) {
            // 获取注解实例
            NameLimit nameLimit = nameField.getAnnotation(NameLimit.class);
            // 获取属性值
            int length = nameLimit.length();
            // 判断是否满足要求

            // 如果不满足要求 抛出异常
            if (name.length() > length) {
                throw new IllegalArgumentException("名字不合法");
            }

        }
    }
}

注解VS配置文件

配置文件 优点:可配置,不用改源码。管理方便 缺点:不直观,开发效率低

注解 优点:直观开发效率高 缺点:硬编码,修改之后需要重新编译运行 难以和代码分开独立管理

注解的使用场景

SE : @Test @Override @Deprecated@FunctionalInterface

EE : @WebService

框架: @AutoWired @Service @Mapping @Data @Parm