注解入门指南

简介: 注解入门指南

注解概述

  • 注解是 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方法");
    }
}
相关文章
|
4天前
|
Java Spring
手写spring 框架——第三篇(注解的方式)
手写spring 框架——第三篇(注解的方式)
35 0
|
7月前
|
Java Go 开发者
《Spring框架教程》
《Spring框架教程》
49 0
|
7月前
|
Java Go 开发者
Spring框架简介
Spring框架简介
45 0
|
8月前
|
安全 Java Spring
用的挺顺手的 Spring Security 配置类,居然就要被官方弃用了?
用过 WebSecurityConfigurerAdapter的都知道对Spring Security十分重要,总管Spring Security的配置体系。但是马上这个类要废了,你没有看错,这个类将在5.7版本被@Deprecated所标记了,未来这个类将被移除。
|
12月前
|
Java Android开发
Butter Knife框架(小刀注解)_@BindView()用法
对于ButterKnife类官方的解释是: Field and method binding for Android views. Use this class to simplify finding views and attaching listeners by binding them with annotations. 翻译过来就是: Android视图的字段和方法绑定。使用此类通过将视图与注释绑定来简化查找视图和附加侦听器。
87 0
|
XML 缓存 NoSQL
JAVA开发常用框架注解与作用
JAVA开发常用框架注解与作用
|
XML Java 数据格式
Spring官网阅读(二)(依赖注入及方法注入)(2)
Spring官网阅读(二)(依赖注入及方法注入)(2)
102 0
Spring官网阅读(二)(依赖注入及方法注入)(2)
|
XML Java 数据格式
Spring官网阅读(二)(依赖注入及方法注入)(1)
Spring官网阅读(二)(依赖注入及方法注入)(1)
116 0
Spring官网阅读(二)(依赖注入及方法注入)(1)
|
XML 监控 Java
Spring官网阅读(十八)AOP的核心概念(1)
本篇文章将作为整个Spring官网阅读笔记的最后一篇。如果要谈SpringFramework必定离不开两点 1.IOC(控制反转) 2.AOP(面向切面) 在前面的文章中我们已经对IOC做过详细的介绍了,本文主要介绍AOP,关于其中的源码部分将在专门的源码专题介绍,本文主要涉及的是AOP的基本概念以及如何使用,本文主要涉及到官网中的第5、6两大章
87 0
Spring官网阅读(十八)AOP的核心概念(1)
|
Java Spring
Spring官网阅读(十八)AOP的核心概念(2)
Spring官网阅读(十八)AOP的核心概念(2)
104 0