• Java基础知识18Java 注解Annotation


    1.Java注解概述

    Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。

    Java 语言中的类、方法、变量、参数和包等都可以被注解。和 Javadoc 不同,Java 注解可以通过反射获取注解内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留注解内容,在运行时可以获取到注解内容 。 当然它也支持自定义 Java 注解。

    通俗理解如下:在程序中,可以把注解看成一种特殊的标记,一般是用来标记类,方法或者接口等,这些标记有一些特点,比如可以在编译的时候,(javac命令把java源文件编译成字节码文件class),类加载和运行的时候(使用java命令执行字节码文件的时候,类的生命周期开始,从加载到卸载)被读取到(一般是有专门的程序去读区这些注解,利用反射技术去解析注解,然后根据得到的信息做相应的处理)。

    2.Java注解的简单分类

    Java 定义了一套注解,共有 7 个,3 个在 java.lang 中,剩下 4 个在 java.lang.annotation 中。

    作用在代码的注解是

    • @Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
    • @Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
    • @SuppressWarnings - 指示编译器去忽略注解中声明的警告。

    作用在其他注解的注解(或者说 元注解)是:

    • @Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
    • @Documented - 标记这些注解是否包含在用户文档中。
    • @Target - 标记这个注解应该是哪种 Java 成员。
    • @Inherited - 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)

    从 Java 7 开始,额外添加了 3 个注解:

    • @SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
    • @FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。
    • @Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次。

    3 注解的基础知识

    3.1 注解的定义

    注解的本质是个接口。

    public @interface Main {
    }

    显而易见,定义注解的关键字就是@interface,它和接口的定义就是多了一个@,但是注解的定义却不仅仅是如此!

    使用 @interface 定义注解时,意味着它实现了 java.lang.annotation.Annotation 接口,即该注解就是一个Annotation。

    定义 Annotation 时,@interface 是必须的。

    注意:它和我们通常的 implemented 实现接口的方法不同。Annotation 接口的实现细节都由编译器完成。通过 @interface 定义注解后,该注解不能继承其他的注解或接口。

    3.1.1 元注解

    元注解就是标记注解的注解

    比如我们定义的这个Main注解,我们规定它只能用来标记方法,那么可以这样做:

    我们在上面加了一个注解@Target,后面还有参数(下面会讲),这个参数ElementType.METHOD就代表我们这个注解是用于注解方法的,来,试一下:

     

     你看,可以用在我们的main方法上,那么是不是不能用于类呢?我们试下:

    报错了,看来是不行,所以这个@Target就是一个元注解,可以用来注解注解,也就是标记注解的注解。

    关于元注解,一般有以下主要的几个:

    1. @Documented 用于制作文档
    2. @Target 指定注解的使用位置,不指定的话任何位置都可以使用
    3. @Retention(注解的保留策略)

    这里单独提一下最后一个也就是声明注解的保留策略@Retention,这个是什么意思呢?

    这个保留策略啊,简单来讲就是说你这个注解可以在哪个时间段起作用,这个就得说说我们的代码从写出来,然后编译到执行的主要三个阶段了,画个图就是这样的:

     这个我已经画的很清楚了吧,一般来说,我们的注解都是要保留到运行期间的,所以一般就是这样:

     到这里你可能发现,这个注解里面可以有参数?当然是可以的,我这里简单演示下,下面讲到注解的语法的时候你就知道了:

     然后再看下使用:

    3.2 注解的组成部分

    java Annotation 的组成中,有 3 个非常重要的主干类。它们分别是:

    3.2.1 Annotation

    package java.lang.annotation;
    public interface Annotation {
    
        boolean equals(Object obj);
    
        int hashCode();
    
        String toString();
    
        Class<? extends Annotation> annotationType();
    }

    3.2.2 ElementType

    package java.lang.annotation;
    
    public enum ElementType {
        TYPE,               /* 类、接口(包括注释类型)或枚举声明  */
    
        FIELD,              /* 字段声明(包括枚举常量)  */
    
        METHOD,             /* 方法声明  */
    
        PARAMETER,          /* 参数声明  */
    
        CONSTRUCTOR,        /* 构造方法声明  */
    
        LOCAL_VARIABLE,     /* 局部变量声明  */
    
        ANNOTATION_TYPE,    /* 注释类型声明  */
    
        PACKAGE             /* 包声明  */
    }

    3.2.3 RetentionPolicy

    package java.lang.annotation;
    public enum RetentionPolicy {
        SOURCE,            /* Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息了  */
    
        CLASS,             /* 编译器将Annotation存储于类对应的.class文件中。默认行为  */
    
        RUNTIME            /* 编译器将Annotation存储于class文件中,并且可由JVM读入 */
    }

    4.注解的基本使用语法

    对于注解,我们知道了如何去定义它,比如简单定义一个注解:

     这很简单,我们继续去看,对于注解还可以定义属性:

     虽然这个属性看起来很像方法,但是人家就是属性,注解还是比较特殊的,那么现在我们来使用下这个注解:

     这个时候它会报错,告诉我们需要一个value值,其实也好理解,你的注解定义中定义的有一个value属性,那么你在使用的时候就需要把这个属性值给用上,那你说我可不可以不用,可以的,那定义注解属性的时候就需要给属性添加默认值,就是这样:

     可以设置成一个空字符串也可以设置成具体的值。除此之外我们还可以设置多个属性值,像这样:

     这里就有知识点了,如果你在使用的时候只是给一个属性值赋值,那么在使用的时候可以这样:

    那有人可能疑问,我这个hello对应的是value还是name啊,默认对应的都是value,所以这个要牢记。

    但是给多个属性值赋值的时候就必须指明具体的属性名称了,就是这样:

    通过上面的介绍我们会发现注解一个比较奇怪的地方,就是对于注解而言,我们可以定义属性,但是注解的属性长得真的像方法,但是在注解里面,它就是属性,就可以直接赋值,这里需要注意下!

    属性的类型

    上面简单介绍了注解的属性,那么这些属性都是可以取哪些类型值呢?大致有如下这么多:基本数据类型、String、枚举、Class、注解类型数组(以上类型的一维数组)

     关于数组的看个例子,比如这样:

     使用的时候也是同样的道理:

     5.注解的深入理解

    我们平常对于注解之所以忽视的原因在于,很多地方只需要我们去使用,比如这样:

    至于注解是怎么定义的以及注解是怎么起作用的都不太了解,好像需要我们自定义注解的也都很少,所以不去系统化的学习注解的话,会忽略掉注解的很多东西,只会使用,也就是@XXX

    对于注解而言,它一定有如下三个流程:

    • 定义注解
    • 使用注解
    • 读取并执行相应流程

    下面我们就以@Repository这个注解来看看这三个流程,首先是定义注解,这个我们可以在IDEA中按住Ctrl点进去@Repository来看,是这样的:

     

     这个就是@Repository注解的定义,接着我们看看@Repository的使用:

    我们一般就是使用注解,对于注解的定义和读取这块一般都是框架什么的给我们搞定了,我们不看源码一般不知道是怎么回事的,也就不清楚注解到底是怎么运行起来的,简单的理解就是注解需要靠反射去读取,然后做相应的处理。

    但是我想你一定和我一样好奇,为啥加了个@Repository注解之后,这个UserBean就被装载进Spring容器中生成了一个bean呢?

    注解是需要有专门的程序去读取的,然后根据读取到的注解获取的信息去执行相应的操作。

    5.1 注解的读取(注解如何起作用)

    下面以Spring的一个例子来加以说明。

    对于Spring简单的大家都知道IOC吧,直白点就是不用你new对象,需要什么直接从Spring容器中获取,那么首先就需要把我们的bean注册到Spring容器中吧,这个一般有xml配置方式和注解方式,当然我们这里要说的是注解方式,也就是使用@+注解名称的形式,举个简单的例子,如下:

     这个注解熟悉吧,它就是可以把我们的Person类注册到Spring容器中去,当然,这里就是在对这个注解的使用,我们点进去看看这个注解是怎么定义的:

    这个定义我们应该已经熟悉了,对于@Component也是一个注解,它其实是最基础的把类注册到Spring容器中的注解,后来的像我们现在说的@Repositoy以及@Service和@Controller这些都是在@Component的基础上发而来。

    这里就需要注意了,其实这几个注解不管是哪个,都要清楚明白的一点就是,要它们啥用,之所以需要这些注解,就是希望在哪个类上使用这些注解,就自动把这个类注册到Spring容器中,这个要比我们写xml配置简单的多,我们就在一个类上写个@Repositoy,它就被注册到Spring容器中了?

     还记得之前说的注解三大步骤嘛?首先你需要定义一个注解,然后就是使用注解,那么注解是怎么起作用的就需要有程序去读注解,这个注解就好比一个标志,一个标签一样,比如这里的@Repositoy,当一个类被这个注解标志,那么当特有的程序去读到这个注解的时候,这个程序就知道,哦,原来是要把这类注册到Spring容器中啊,那么程序怎么知道要把这个类注册到Spring容器中呢?这就是 @Repositoy 告诉它的。另外我们知道注解一般可以设置一个value属性值,可以通过反射技术拿到之类的,那么在具体的将这个类注册到Spring容器的过程中可能就会用到这个value属性值,比如设置成bean的名字。

    我们一般使用了注解,在Spring配置文件中就需要配置注解的包扫描:

    <context:component-scan base-package="com.ithuangqing.*"/>

    这个其实就是在扫描,看看哪个类上使用到了@Repositoy这些注解,扫描到的就需要特殊处理将其注册到Spring容器。想一下,这里Spring其实就会对这个标签进行解析,核心代码:

    registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());

    然后具体的处理流程就是在ComponentScanBeanDefinitionParser处理,代码如下:

    @Override
        public BeanDefinition parse(Element element, ParserContext parserContext) {
            String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
            basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
            String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
                    ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
    
            // Actually scan for bean definitions and register them.
            ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);    //得到扫描器
            Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);             //扫描文件,并转化为spring bean,并注册
            registerComponents(parserContext.getReaderContext(), beanDefinitions, element);       //注册其他相关组件
    
            return null;
        }

    上述代码的主要作用就是扫描base-package 下的文件,然后把它转换为Spring中的bean结构,接着将其注册到容器中……

    5.2 获取注解的属性

    注解使用最终是需要依靠程序去读取注解,得到注解的一些信息,然后才判断接下来应该去做什么事情,那么接下来我们就要知道注解的属性值该如何获取。

    其实注解的属性,用到的技术就是反射。

    接下来我们来看如何使用反射来获取注解的属性。,主要就是一下三个基本的方法:

    /**是否存在对应 Annotation 对象*/
    public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) 
    {
        return GenericDeclaration.super.isAnnotationPresent(annotationClass);
    }
    
    /**获取 Annotation 对象*/
    public <A extends Annotation> A getAnnotation(Class<A> annotationClass) 
    {
        return (A) annotationData().annotations.get(annotationClass);
    }
    
    /**获取所有 Annotation 对象数组*/   
    public Annotation[] getAnnotations() 
    {
        return AnnotationParser.toArray(annotationData().annotations);
    }

    然后接下来看一段简单的代码:演示利用注解获取注解属性

    public class Test {
        public static void main(String[] args) throws Exception {
            Class<Test> testClass = Test.class;
            Method toGetString = testClass.getMethod("toGetString");
            //获取注解对象
            Main main = toGetString.getAnnotation(Main.class);
            System.out.println(main.value());
    
        }
    
        @Main("这是自定义注解的value值")
        public static String toGetString() {
            return "";
        }
    }

    参考文献:

    https://zhuanlan.zhihu.com/p/202586806 ----推荐

    https://www.runoob.com/w3cnote/java-annotation.html

  • 相关阅读:
    CMake学习笔记
    右键添加"在此处打开命令窗口"菜单
    设置默认python模块源
    添加到附加组
    Unity宏处理
    挂载windows共享文件夹
    MacOS长按无效问题
    中文Locale
    笔记本用作无线路由器
    C# Winfrom iTextSharp 导出pdf 带二维码 表格嵌套 简单Dome
  • 原文地址:https://www.cnblogs.com/luckyplj/p/15772426.html
Copyright © 2020-2023  润新知