注解入门指南

简介: 注解入门指南

注解概述

  • 注解是 JDK1.5 的新特性。
  • 标记(注解)可以加在包,类,字段,方法,方法参数以及局部变量上。
  • 注解是给编译器或 JVM 看的,编译器或 JVM 可以根据注解来完成对应的功能。

注解的作用:

  • 使用javadoc生成帮助文档:里边可以包含注解@author和@version
  • 编译检查@Override @FunctionalInterface
  • 框架的配置(框架=代码+配置)

自定义注解

定义没有属性的注解

格式:

public @interface 注解名{ }

注意:

  • 注解使用的也是.java文件,编译生成的也是.class文件
  • 注解和类和接口和枚举都是同一个层次的,都是一种数据类型

定义有属性的注解

注解中没有成员变量,也没有成员方法

注解中可以包含属性,属性看成和抽象方法一个格式,但是可以包含默认值

定义格式:

public @interface 注解名{
    修饰符 数据类型 属性名();
    修饰符 数据类型 属性名() default 默认值;
}

注意:

  • 注解的属性修饰符省略不写则默认为 public abstract ;建议写出,增强语句的可读性
  • 注解属性的数据类型:

    • 基本数据类型(4类8种):byte,short,int,long,float,double,char,boolean
  • 引用数据类型:String类型,反射类型,注解类型,枚举类型

    • 以及以上所有类型的一维数组

示例:

public @interface MyAnno02 {
    //定义一个int类型的属性
    //public abstract int a();
    //abstract int a();
    int a();

    //定义一个double类型的属性,给属性添加默认值8.8
    public abstract double d() default 8.8;

    //定义一个String类型的数组属性
    public abstract String[] arr();

    //定义反射类型的属性(了解)
    public abstract Class clazz();
    //定义注解类型的属性(了解)
    public abstract MyAnno01 my01();
    //定义枚举类型的属性(了解)
    public abstract Color c();
}

定义包含特殊属性 value 的注解

  • 注解中只有一个属性,并且叫 value
  • 注解中有其他的属性,但是属性必须有默认值

示例

public @interface MyAnno03 {
    public abstract String value();
    public abstract int aaa() default 10;
}

@AliasFor 注解:声明别名

@AliasFor 是 Spring 框架的一个注解,用于声明注解属性的别名。

它有两种不同的应用场景:

  • 注解内的别名
  • 元数据的别名

两者主要的区别在于是否在同一个注解内。

注解内的别名

可以使用 @AliasFor 将注解的两个属性互相声明为别名,互为别名的属性使用是等价的。

注意:

  • 组成别名对的每个属性都必须加上注释 @AliasFor,且必须使用 attribute() 或 value() 属性引用该别名对中另一个属性
  • 别名属性必须声明相同的返回类型
  • 别名属性必须声明一个默认值
  • 别名属性必须声明相同的默认值

示例:

import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogAnnotation {
    @AliasFor("methodDesc")
    String value();
    @AliasFor("value")
    String methodDesc() default "";
}

元数据的别名

把多个元注解的属性组合在一起形成新的注解

创建一个 @MyAnnotationB 注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@MyAnnotationA
public @interface LogAnnotationB {
   @AliasFor(annotation = LogAnnotation.class,value = "methodDesc")
   String value() default "";
}

这时 @LogAnnotation(“vvv”) 就和 @LogAnnotationB(methodDesc=“vvv”) 等价。

可以理解成,注解 LogAnnotationB 的 value 属性重写了注解 LogAnnotation 的 methodDesc 属性,注意属性的返回类型必须相同。

SpringBoot这种用法很多,例如最常见的 @Component 和 @Configuration

使用自定义注解

注解可以使用的位置:包上,类上|接口上,成员变量上,成员方法上,构造方法上,局部变量上,方法的的参数上...

注意:

  • 同一个位置,同名的注解只能使用一次
  • 不同的位置,同名的注解可以多次使用

注解的使用格式:

  • 没有属性的注解,通过 @注解名 可以直接使用。例如:@MyAnno01
  • 有属性的注解:必须使用键值对的方式,给注解中所有的属性都赋值之后,才能使用注解

    格式:

    ​ @注解名(属性名=属性值,属性名=属性值,属性名=属性值,...属性名=属性值)

    注:

    ​ a.有默认值的属性,可以不同赋值,使用默认值

    ​ b.给多个属性赋值,中间要使用逗号隔开

    ​ c.属性的数据类型是数组,属性的值需要使用 { } 包裹起来,说明这一组值是一个数组的属性值

    ​ 数组只有一个值,可以省略 { }

    ​ 示例:arr = {"a","b","c"} arr = {"a"} ==> arr = "a"

    ​ d.注解中只有一个属性,并且叫 value;或者注解中有其他的属性,但是属性均有默认值

    ​ 那么我们在使用注解的时候,给属性赋值,可以省略属性名,直接写属性值

    ​ 示例:(任意的数据类型)value="aaa" ==> "aaa"

示例

@MyAnno01    // 没有属性的注解
@MyAnno02(a = 10, arr={"aaa","bbb","ccc"})    // 有多个属性的注解
public class UseMyAnno {
    @MyAnno01
    @MyAnno02(a=100, d=1.1, arr="aaa")    // arr属性的数据类型是数组,只有一个值的情况,可省略 {}
    private String name;

    @MyAnno01
    @MyAnno03(value="aaa")
    public UseMyAnno() {
    }

    @MyAnno01
    @MyAnno03("www")    // 可省略属性名的注解
    public UseMyAnno(String name) {
        this.name = name;
    }

    @MyAnno01
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

元注解

元注解:java已经定义好的注解,可以使用元注解修饰自定义的注解

  • @Target

    java.lang.annotation.Target

    作用:用来标识注解使用的位置,如果没有使用该注解标识,则自定义的注解可以使用在任意位置。

    属性:

    • ElementType[] value :只有一个属性,属性名叫value(使用的时候,就可以省略属性名,直接写属性值)
    • java.lang.annotation.ElementType:是一个枚举,枚举中的变量都是常量,可以通过枚举名.变量名直接使用

      ElementType枚举中的常量:

      TYPE,        // 类,接口
      FIELD,        // 成员变量
      METHOD,     // 成员方法
      PARAMETER,             // 方法参数
      CONSTRUCTOR,         // 构造方法
      LOCAL_VARIABLE        // 局部变量
  • @Retention

    java.lang.annotation.Retention

    作用:用来标识注解的生命周期(有效范围)

    属性:

    • RetentionPolicy value: 只有一个属性,属性名叫value(使用的时候,就可以省略属性名,直接写属性值)
    • java.lang.annotation.RetentionPolicy:是一个枚举,枚举中的变量都是常量,可以通过枚举名.变量名直接使用

      RetentionPolicy枚举中的常量:

      SOURCE,      // 注解只作用在源码(.java)阶段,生成的字节码文件(.class)中不存在
      CLASS,        // 注解作用在源码阶段,字节码文件阶段,运行阶段不存在,默认值
      RUNTIME        // 注解作用在源码阶段,字节码文件阶段,运行阶段

示例

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

//声明自定义注解Book可以使用的位置:类上|接口上,构造方法上,成员变量上
//@Target(value={ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.FIELD})
@Target({ElementType.TYPE,ElementType.CONSTRUCTOR,ElementType.FIELD})
//声明自定义注解Book:在.java文件中,在.class文件中和在内存中都有效
@Retention(RetentionPolicy.RUNTIME)
public @interface Book {
    //书名
    public abstract String value();
    //价格,默认值为 100
    public abstract double price() default 100;
    //多位作者
    public abstract String[] authors();
}

注解解析

作用:获取注解的属性值

原理:注解的解析底层使用的反射技术

java.lang.reflect.AnnotatedElement接口:在接口中定义了注解解析的方法

AnnotatedElement接口的实现类都重写了接口中的方法,都可以使用这些方法

​ 实现类:AccessibleObject,Class,Constructor,Field,Method,Package

AnnotatedElement接口中的常用方法:

boolean isAnnotationPresent(Class<?> annotationClass)    // 判断指定的对象(Class,Method...)上,是否包含指定的注解
/* 参数:
        Class<?> annotationClass:判断哪个注解,就传递哪个注解的class文件对象
                                  判断类上,方法上有没有Book注解,就需要传递Book.class
   返回值:boolean
         有指定的注解,返回true
         没有指定的注解,返回false
*/
    
T getAnnotation(Class<T> annotationClass)    // 获取对象(Class,Method...)上指定的注解
/* 参数:
        Class<T> annotationClass:获取哪个注解,就传递哪个注解的class文件对象
                                  获取类上,方法上的Book注解,就需要传递Book.class
   返回值:
        T:返回获取到的注解,获取的注解不存在,返回null
*/
    
Annotation[] getAnnotations()             // 返回此元素上存在的所有公共注释。
Annotation[] getDeclaredAnnotations()     // 返回直接存在于此元素上的所有注释,包含其他修饰符的注解

示例

import org.junit.Test;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;

@Book(value = "西游记",authors = {"吴承恩"})
public class Demo01ParseAnnotation {
    @Book(value = "水浒传",authors = {"施耐庵","林冲"},price = 88.8)
    public void method(){}

    /* 解析类上的注解:获取类上注解的属性值 */
    @Test
    public void show01() throws ClassNotFoundException {
        //1.获取当前类Demo01ParseAnnotation的class文件对象
        Class clazz = Class.forName("com.itheima.demo09Annotation.Demo01ParseAnnotation");
        //2.使用class文件对象中的方法isAnnotationPresent判断类上是否包含指定的Book注解
        boolean b = clazz.isAnnotationPresent(Book.class);
        System.out.println(b);//true
        if(b){
            //3.如果类上包含Book注解,使用class文件对象中的方法getAnnotation获取Book注解
            Book book = (Book)clazz.getAnnotation(Book.class);
            //4.使用注解名.属性名(),获取属性值
            String value = book.value();
            System.out.println(value);
            double price = book.price();
            System.out.println(price);
            String[] authors = book.authors();
            System.out.println(Arrays.toString(authors));
        }
    }

    /* 解析方法上的注解:获取方法上注解的属性值 */
    @Test
    public void show02(){
        //1.获取当前类Demo01ParseAnnotation的class文件对象
        Class clazz = Demo01ParseAnnotation.class;
        //2.使用class文件对象中的方法getMethods获取类中所有的方法,返回一个Method数组
        Method[] methods = clazz.getMethods();
        //3.遍历Method数组,获取每一个Method对象
        for (Method method : methods) {
            //4.使用Method对象中的方法isAnnotationPresent判断方法上是否包含指定的Book注解
            boolean b = method.isAnnotationPresent(Book.class);
            System.out.println(method.getName()+"-->"+b);
            if(b){
                //5.如果方法上有Book注解,使用Method对象中的方法getAnnotation,获取Book注解
                Book book = method.getAnnotation(Book.class);
                //6.使用注解名.属性名(),获取属性的值
                System.out.println(book.value());
                System.out.println(book.price());
                System.out.println(Arrays.toString(book.authors()));
            }
        }
    }
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Book {
    //书名
    public abstract String value();
    //价格,默认值为 100
    public abstract double price() default 100;
    //多位作者
    public abstract String[] authors();
}

注解和反射的综合案例

import java.lang.reflect.Method;

/*
    注解和反射的综合案例
    需求:
        模拟Junit单元测试的@Test注解:作用可以单独的运行某一个方法
        方法上添加了@Test注解,可以运行
        方法上没有添加@Test注解,不可以运行
 */
public class Demo01Test {
    public static void main(String[] args) throws Exception {
        //3.获取测试类的class文件对象
        Class clazz = Class.forName("com.itheima.demo10test.DemoMyTest");
        //4.使用class文件对象中的方法newInstance实例化对象
        Object obj = clazz.newInstance();
        //5.使用class文件对象中的方法getMethods,获取类中所有的成员方法,返回一个Method数组
        Method[] methods = clazz.getDeclaredMethods();//只会获取本类的方法,包含任意修饰符的,没有父类的方法
        //6.遍历数组,获取每一个Method对象
        for (Method method : methods) {
            //7.使用Method对象中的方法isAnnotationPresent判断Method对象上是否有MyTest注解
            boolean b = method.isAnnotationPresent(MyTest.class);
            //8.如果Method对象上有MyTest注解,使用invoke运行方法
            if(b){
                method.invoke(obj);
            }
        }
    }
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

//1.定义一个注解叫MyTest,使用元注解修饰(a.只能在方法上使用,b.运行时有效)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
}
//2.定义一个测试类,在测试类中定义多个方法,让部分方法使用MyTest注解修饰
public class DemoMyTest {
    @MyTest
    public void show01(){
        System.out.println("show01方法");
    }
    public void show02(){
        System.out.println("show02方法");
    }
    public void show03(){
        System.out.println("show03方法");
    }
    @MyTest
    public void show04(){
        System.out.println("show04方法");
    }
}
相关文章
|
Kubernetes 负载均衡 监控
记一次k8s压测发生SLB 499的串流问题
对k8s集群中的pod进行压测,压测方式是直接访问k8s前的SLB, 压测表现是 SLB (CLB 7层监听)偶发返回499报错。 最终确认问题根因是五元组复用导致串流。 关键词: 偶发499 、压测、k8s
2038 4
记一次k8s压测发生SLB 499的串流问题
|
运维 Cloud Native Devops
「译文」什么是 SRE(站点可靠性工程师)?SRE 是做什么的?
「译文」什么是 SRE(站点可靠性工程师)?SRE 是做什么的?
|
机器学习/深度学习 传感器 人工智能
AI:国内外人工智能产业应用图谱应用层/基础层详解
AI:国内外人工智能产业应用图谱应用层/基础层详解
AI:国内外人工智能产业应用图谱应用层/基础层详解
|
3月前
|
SQL 人工智能 安全
深度复盘MCP安全风暴:一个工单如何演变成数据库“特洛伊木马”危机?
近期,安全公司 General Analysis 披露的MCP安全漏洞在技术圈引发了巨大震动。这个"特洛伊木马"式的安全漏洞暴露了一个现实:AI时代,传统的数据库访问方式已经无法满足安全需求。阿里云数据管理DMS新推出的DMS MCP Server,正是为AI时代的数据库安全访问而生,它不仅完美解决了传统MCP的安全隐患,更为企业提供了一个安全、智能、高效的数据访问新范式。
386 5
|
8月前
|
数据安全/隐私保护
基于simulink的PEM燃料电池控制系统建模与仿真,对比PID,积分分离以及滑模控制器
本课题基于Simulink对PEM燃料电池控制系统进行建模与仿真,对比了PID、积分分离及滑模控制器的性能。系统使用MATLAB 2022a版本,仿真结果无水印输出。PEM燃料电池作为一种高效能量转换装置,其控制系统的优化设计至关重要。PID控制器通过比例、积分、微分作用处理静态误差和动态响应;滑模控制则以其快速响应和强鲁棒性在非线性系统中表现出优势;积分分离PID能有效避免积分饱和。实际应用中需结合多种控制策略,以提升系统性能和效率。
|
10月前
|
Java Linux 数据库
探索安卓开发:打造你的第一款应用
在数字时代的浪潮中,每个人都有机会成为创意的实现者。本文将带你走进安卓开发的奇妙世界,通过浅显易懂的语言和实际代码示例,引导你从零开始构建自己的第一款安卓应用。无论你是编程新手还是希望拓展技术的开发者,这篇文章都将为你打开一扇门,让你的创意和技术一起飞扬。
197 13
|
10月前
|
存储 网络协议 编译器
【C语言】深入解析C语言结构体:定义、声明与高级应用实践
通过根据需求合理选择结构体定义和声明的放置位置,并灵活结合动态内存分配、内存优化和数据结构设计,可以显著提高代码的可维护性和运行效率。在实际开发中,建议遵循以下原则: - **模块化设计**:尽可能封装实现细节,减少模块间的耦合。 - **内存管理**:明确动态分配与释放的责任,防止资源泄漏。 - **优化顺序**:合理排列结构体成员以减少内存占用。
815 14
|
数据挖掘 数据处理 索引
一文秒懂Pandas中的crosstab与pivot
一文秒懂Pandas中的crosstab与pivot
407 0
|
缓存 NoSQL 应用服务中间件
缓存击穿和雪崩常用解决方案
缓存击穿和雪崩常用解决方案
562 0
|
Linux
kali2023.1更新内核
kali2023.1更新内核
845 0