JavaCore-深入理解注解-Annotation

概述

目前大部分框架(如Spring)都使用了注解简化代码并提高编码的效率,因此掌握并深入理解注解对于一个Java工程师是来说是很有必要的事。看起来基本上的框架都用了注解,springboot里面的注解更是多,不理解注解真的是框架时代寸步难行。
还有人说框架就是注解+设计模式+反射,对于我这个小白来讲,赶紧理解注解才是正道。只为提高自己,脱离增删改查工程师,尽快转型自带光环的programmer。

解释

官方
Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。Java 注解是从 Java5 开始添加到 Java 的。
官方的定义,很难理解,为了解释注解这个概念有引入了元数据的概念,这就是找事了。

民间
我们民间的解释就容易多了
这里我参考了很多的博客,这几篇文章写的很详细,我在此基础上做自己的总结
秒懂,Java 注解 (Annotation)你可以这样学
深入理解Java注解类型(@Annotation)
java中的注解,使用、面试再也不用愁了

理解
这个比方很简单容易理解,注解就是标签,通常我们给人贴上学霸、学渣的标签,其实这就是帮助别人快速理解这个东西了。标签是对事物行为的某些角度的评价与解释。
① jdk 5.0 新增的功能
② Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。通过使用 Annotation,程序员可以在不改变原逻辑的情况下, 在源文件中嵌入一些补充信息。
③在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/Android中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码和XML配置等。

框架 = 注解 + 反射机制 + 设计模式 这句话好重要,我就是冲着这句话来写这篇文章的,为以后的理解框架做好基础,拒绝只做增删改查的coder,励志成为programmer

语法

注解的基本语法就是这样,用 @interface来标识这是一个注解,注解和class、interface是同一个等级的,不要认为注解不重要。

public @interface MyAnnotation {
    String value() ;
}

我们使用了@interface声明了MyAnnotation 注解,从代码上看注解的定义很像接口的定义,确实如此,毕竟在编译后也会生成Test.MyAnnotation 文件

元注解

这玩意他娘的有意思了,没听过的真不好理解这东西,元注解是标识注解的注解,字面意思就是给标签加的标签,元注解也是一张标签,但是它是一张特殊的标签,它的作用和目的就是给其他普通的标签进行解释说明的。
元注解有五种分别是: @Retention、@Documented、@Target、@Inherited、@Repeatable 。

@Retention

/**
 * Indicates how long annotations with the annotated type are to
 * be retained.  If no Retention annotation is present on
 * an annotation type declaration, the retention policy defaults to
 * {@code RetentionPolicy.CLASS}.
 *
 * <p>A Retention meta-annotation has effect only if the
 * meta-annotated type is used directly for annotation.  It has no
 * effect if the meta-annotated type is used as a member type in
 * another annotation type.
 *
 * @author  Joshua Bloch
 * @since 1.5
 * @jls 9.6.3.2 @Retention
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

看源码人家是怎么写的,也是声明了一个@interface,名字叫Retention的注解,他里面需要一个RetentionPolicy这个类型的value();也就是使用的时候需要指明RetentionPolicy这个枚举使用的哪一个,或者有默认的值,这个时候再去看看RetentionPolicy的源码

/*
 * Copyright (c) 2003, 2004, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
package java.lang.annotation;

/**
 * Annotation retention policy.  The constants of this enumerated type
 * describe the various policies for retaining annotations.  They are used
 * in conjunction with the {@link Retention} meta-annotation type to specify
 * how long annotations are to be retained.
 *
 * @author  Joshua Bloch
 * @since 1.5
 */
public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间。
它的取值如下:
RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视
RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。
RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。

@Documented

看源码

/**
 * Indicates that annotations with a type are to be documented by javadoc
 * and similar tools by default.  This type should be used to annotate the
 * declarations of types whose annotations affect the use of annotated
 * elements by their clients.  If a type declaration is annotated with
 * Documented, its annotations become part of the public API
 * of the annotated elements.
 *
 * @author  Joshua Bloch
 * @since 1.5
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

人家注释说的多清楚它的作用是能够将注解中的元素包含到 Javadoc 中去。

@Target

看源码

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}

ElementType[] value();这里这个玩意是什么意思呢,他是说你使用这个注解的时候可以指定值是ElementType这个类型的数组,elementType这个的源码就不贴了,ElementType他本身也是一个枚举enum,解释一下

@Target 指定了注解运用的地方。
你可以这样理解,当一个注解被 @Target 注解时,这个注解就被限定了运用的场景。类比到标签,原本标签是你想张贴到哪个地方就到哪个地方,但是因为 @Target 的存在,它张贴的地方就非常具体了,比如只能张贴到方法上、类上、方法参数上等等。

@Target 有下面的取值
ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
ElementType.CONSTRUCTOR 可以给构造方法进行注解
ElementType.FIELD 可以给属性进行注解
ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
ElementType.METHOD 可以给方法进行注解
ElementType.PACKAGE 可以给一个包进行注解
ElementType.PARAMETER 可以给一个方法内的参数进行注解
ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举

@Inherited

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

Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。
说的比较抽象。代码来解释。

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}

@Test
public class A {}

public class B extends A {}

注解 Test 被 @Inherited 修饰,之后类 A 被 Test 注解,类 B 继承 A,类 B 也拥有 Test 这个注解。
可以这样理解:
老子非常有钱,所以人们给他贴了一张标签叫做富豪。
老子的儿子长大后,只要没有和老子断绝父子关系,虽然别人没有给他贴标签,但是他自然也是富豪。
老子的孙子长大了,自然也是富豪。
这就是人们口中戏称的富一代,富二代,富三代。虽然叫法不同,好像好多个标签,但其实事情的本质也就是他们有一张共同的标签,也就是老子身上的那张富豪的标签。

@Repeatable 。

Repeatable 自然是可重复的意思。@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。
什么样的注解会多次应用呢?通常是注解的值可以同时取多个。
举个例子,一个人他既是程序员又是产品经理,同时他还是个画家。这个代码是抄的。。。

@interface Persons {
	Person[]  value();
}

@Repeatable(Persons.class)
@interface Person{
	String role default "";
}

@Person(role="artist")
@Person(role="coder")
@Person(role="PM")
public class SuperMan{
}

@interface Persons {
	Person[]  value();
}

这个解释太好了
按照规定,它里面必须要有一个 value 的属性,属性类型是一个被@Repeatable 注解过的注解数组,注意它是数组。
如果不好理解的话,可以这样理解。Persons 是一张总的标签,上面贴满了 Person 这种同类型但内容不一样的标签。把 Persons 给一个 SuperMan 贴上,相当于同时给他贴了程序员、产品经理、画家的标签。
我们可能对于 @Person(role=“PM”) 括号里面的内容感兴趣,它其实就是给 Person 这个注解的 role 属性赋值为 PM ,大家不明白正常,马上就讲到注解的属性这一块。

预置注解

java预置的注解其实还是比较多的,但是我们只要挑出几个比较重要的就好了。

@Deprecated

这个注解是用来标记过时的元素,编译器在编译阶段遇到这个注解时会发出提醒警告,告诉开发者正在调用一个过时的元素比如过时的方法、过时的类、过时的成员变量。

@Override

这个注解再熟悉不过了,提示该方法是接口方法的实现或者是子类重写的父类的方法。

@SuppressWarnings

阻止警告的意思,上面说过调用被@Deprecated注解的方法后,编译器会警告提醒,而有时候开发者会忽略这种警告,他们可以在调用的地方通过@SuppressWarnings达到目的。这个注解具体怎么执行的就是编译器的问题了,关于这个注解是怎么执行的都是反射的问题,java中用起来也是反射,spring中用起来也是反射,springboot中的注解更多。理解这个注解很重要。

@SafeVarargs

参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生unchecked这样的警告,它是在Java 1.7的版本中加入的。

JDK8中注解的新特性:可重复注解、类型注解
可重复注解:
① 在MyAnnotation上声明@Repeatable,成员值为MyAnnotations.class
② MyAnnotation的Target和Retention等元注解与MyAnnotations相同。
类型注解:
ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中(如:泛型声明。ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。

注解的属性

注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
		int id();
		String msg();
}

上面代码定义了 TestAnnotation 这个注解中拥有 id 和 msg 两个属性。在使用的时候,我们应该给它们进行赋值。

赋值的方式是在注解的括号内以 value="" 形式,多个属性之前用 ,隔开。

@TestAnnotation(id=3,msg="hello annotation")
public class Test {
}

需要注意的是,在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组。

注解中属性可以有默认值,默认值需要用 default 关键值指定。比如:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
		public int id() default -1;
		public String msg() default "Hi";
}

TestAnnotation 中 id 属性默认值为 -1,msg 属性默认值为 Hi。它可以这样应用。

@TestAnnotation()
public class Test {}

因为有默认值,所以无需要再在 @TestAnnotation 后面的括号里面进行赋值了,这一步可以省略。

另外,还有一种情况。如果一个注解内仅仅只有一个名字为 value 的属性时,应用这个注解时可以直接接属性值填写到括号内。

public @interface Check {
	String value();
}

上面代码中,Check 这个注解只有 value 这个属性。所以可以这样应用。

@Check("hi")
int a;

这和下面的效果是一样的

@Check(value="hi")
int a;

最后,还需要注意的一种情况是一个注解没有任何属性。比如

public @interface Perform {}

那么在应用这个注解的时候,括号都可以省略。

@Perform
public void testMethod(){}

如果注解有成员,在使用注解时,需要指明成员的值。
为自定义注解加深理解
自定义注解必须配上注解的信息处理流程(使用反射)才意义。
自定义注解通过都会指明两个元注解:Retention、Target

注解怎么用

获取注解

也就是我们通过反射获取类 、函数或成员上的运行时注解信息,从而实现动态控制程序运行的逻辑。举个例子,看看我们如何通过反射来控制程序运行的逻辑。反射可以让我们在运行时获取类的属性,方法,构造方法、父类、接口等信息,通过反射还可以让我们在运行期实例化对象、调用方法、即使方法或属性是私有的的也可以通过反射的形式调用。反射的原理就是读取编译后的class文件,啥都有了,读出来还不容易么,不管你怎么写的,私有的或者什么的方法,我都能读到。

注解使用

import java.lang.annotation.*;
/**
 * @Description 自定义一个注解 修饰属性
 * @Author WangWenpeng
 * @Date 9:38 2020/4/15
 * @Param
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value = ElementType.FIELD)
public @interface Fields {
    int sort() default 0;
    String value();
}

/**
 * @Description 修饰类
 * @Author WangWenpeng
 * @Date 10:54 2020/4/15
 * @Param 
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface ConsAnnotation {
    String[] request();//表明可以声明多个string
}

/**
 * @Description 给类和属性加注解
 * @Author WangWenpeng
 * @Date 10:54 2020/4/15
 * @Param 
 */
@ConsAnnotation(request = {"java", "小鸟程序员"})
class User {
    @Fields("张三")
    private String userName;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }
}

获取这些注解

package com.atguigu.java1;

import java.lang.reflect.Field;
import java.util.Arrays;

/**
 * @ClassName ValueTest
 * @Description: annotationtest
 * @Author: WangWenpeng
 * @date: 10:39 2020/4/15
 * @Version 1.0
 */
public class ValueTest {
    public static void main(String[] args) throws Exception {
        User user = new User();
        // 1、 获取 User类上的注解 @ConsAnnotation
        ConsAnnotation anno = user.getClass().getAnnotation(ConsAnnotation.class);
        String[] arr = anno.request();
        System.out.println(Arrays.toString(arr));

        // 2、 获取User类中 private String userName; 变量上的注解 @Field
        Field f = user.getClass().getDeclaredField("userName");
        Fields anno2 = f.getAnnotation(Fields.class);
        user.setUserName(anno2.value());
        System.out.println(user.getUserName());
    }
}

看输出
查看输出

总结

提供信息给编译器: 编译器可以利用注解来探测错误和警告信息
编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。
运行时的处理: 某些注解可以在程序运行的时候接受代码的提取
注解就是一个标签,你也可以当成一个便利贴,在哪使用就看你是否需要这个便利贴了。

公众号,关注一下
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值