小言_互联网的博客

框架的灵魂之注解基础篇

272人阅读  评论(0)
学习不是挖掘某人潜力的方式,而是发展这种潜力的方式。

什么是注解?

注解(Annotation),又称作元数据。翻开之前我写过关于Java语言发展史的博客:那些年Java走过的路,找到其中的<Java语言发展状况>章节便可知,它是属于JDK5.0引入的技术。距今已有17年历史了,可见他还是个少年,或许他也曾梦想仗剑走天涯,可惜bug多没去。


说起注解,大家可能会想到词汇表注解、古文注解或者注释。


我们第一次接触注解,可能是使用子类继承父类的方法(任何对象的父类都是Object),看到重写的注解@Override

package annotation;

/**
 * @ClassName Son
 * @Description 注解演示
 * @Author 古阙月
 * @Date 2021/4/8 19:00
 * @Version 1.0
 */
public class Son {
   

    @Override // 重写的注解
    public String toString() {
   
        return super.toString();
    }
}

到后来Spring等框架中普遍使用了各种各样的注解。注解的语法格式非常简单:@ + 注解名,但是偏偏一个小小的注解,却能在框架中实现各种各样的功能,让人不禁觉得灰常奇妙,而且简单优雅!!!

注解的作用

注解(Annotation)主要有两个作用:

  1. 注解(Annotation)就像注释(Comment)一样,它不属于程序本身,但是可以对程序作出解释。就比如我们一看到@Override注解,就知道该注解下的方法为重写父类的方法。
  2. 我们可以通过其他程序,如编译器读取注解,然后再对注释下的程序进行检查和约束。或者我们可以利用反射技术来读取注解,从而实现各种各样的功能。 如重写后的方法必须和父类的方法同名,我们试着改变方法名,就会出现报错提醒:

内置注解

JDK内置了三个注解,下面来简单介绍一下。

@Override

@Override注解的简单使用在之前的章节已经介绍过了。它位于java.lang包下,官方解释,看下面的文档截图:表示一个方法声明打算重写超类中的另一个方法声明。如果方法利用此注释类型进行注解但没有重写超类方法,则编译器会生成一条错误消息。 是不是跟我之前介绍的一样?

我们再点进去@Override来看一下它的源代码:

package java.lang;

import java.lang.annotation.*;

/**
 * @author  Peter von der Ah&eacute;
 * @author  Joshua Bloch
 * @jls 9.6.1.4 @Override
 * @since 1.5
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
   
}

感觉似乎很懵,没有关系,之后给你细细讲解。

@Deprecated

@Deprecated注解 同样位于java.lang包下,官方解释为:@Deprecated 注释的程序元素,不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择。在使用不被赞成的程序元素或在不被赞成的代码中执行重写时,编译器会发出警告。

此注解用于修饰构造方法(CONSTRUCTOR)、属性(FIELD)、局部变量(LOCAL_VARIABLE)、方法(METHOD)等,如我们用一个方法举例子:

 	/**
     * 废弃的方法
     */
    @Deprecated
    public static void deprecatedMethod() {
   
        System.out.println("我是一个被废弃的方法...");
    }

当我们试图使用deprecatedMethod()方法时,便会出现下划线废弃警告,表示不建议使用,危险或者有更好的选择。

当然,你要是想强行使用,也是可以的:

package annotation;

import lombok.ToString;

/**
 * @ClassName Son
 * @Description 注解演示
 * @Author 古阙月
 * @Date 2021/4/8 19:00
 * @Version 1.0
 */
public class Son {
   

    /**
     * 废弃的方法
     */
    @Deprecated
    public static void deprecatedMethod() {
   
        System.out.println("我是一个被废弃的方法...");
    }

    @Override // 重写的注解
    public String toString() {
   

        return super.toString();
    }

    public static void main(String[] args) {
   
     	// 不建议使用的方法,不安全或者有更好的选择
        deprecatedMethod();
    }
}

运行,得:

同样,这里贴出@Deprecated的源代码:

package java.lang;

import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;

/**
 * @author  Neal Gafter
 * @since 1.5
 * @jls 9.6.3.6 @Deprecated
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={
   CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
   
}

如果看不懂,没关系,之后讲解。

@SuppressWarnings

@SuppressWarnings看字面意思很好理解:镇压警告 的意思。和之前的两兄弟@Override以及@Deprecated一样,位于java.lang包下。官方意思是:用于取消注释元素中指定显示的编译器警告,详情可见下图文档截图:

这样看来似乎有些小抽象,没关系,我们来举一个例子:

	public void suppressWarningsMethod() {
   
	
		double num = 3.14;
	}

上文是一个方法,如果suppressWarningsMethod()方法以及num数值没有被使用过的话,是会被暗化显示的:

我们完全可以用@SuppressWarnings注解来取消这个编译器警告:

	@SuppressWarnings("unused") // 取消未使用便做暗化处理的编译器警告
    public void suppressWarningsMethod() {
   

        double num = 3.14;
    }

效果如下:

这个时候有细心的小伙伴可能就会发现了:之前使用重写注解@Override以及废弃注解@Deprecated时是直接使用的,可使用这个抑制警告注解@SuppressWarnings时,怎么是这么用的:@SuppressWarnings("unused") ,这个括号里面双引号里面的 unused 是啥意思?

不要急,我们先来看一下@SuppressWarnings注解的源码

package java.lang;

import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;

/**
 * @author Josh Bloch
 * @since 1.5
 * @jls 4.8 Raw Types
 * @jls 4.12.2 Variables of Reference Type
 * @jls 5.1.9 Unchecked Conversion
 * @jls 5.5.2 Checked Casts and Unchecked Casts
 * @jls 9.6.3.5 @SuppressWarnings
 */
@Target({
   TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
   
  
    String[] value();
}

这个时候,我们就会发现@SuppressWarnings注解跟之前两个注解一个很大的不同,多了一个:

	String[] value();

这个value()像方法又没有方法体,类型还是个String数组,好像让人有些看不懂!

这里提前透露一下,这个value()就是这个@SuppressWarnings的配置参数,value为这个配置参数的参数名,而value()是注解中配置参数的声明形式。而String[] 则是这个名为value的配置参数的类型!!!

所以,当我们使用@SuppressWarnings注解时,后面需要加上参数。由上文可知,这个参数还可以为多个:

	@SuppressWarnings({
   "unused", "null"}) // 取消编译器警告
    public void suppressWarningsMethod() {
   

        double num = 3.14;
    }

那么,这个参数值是不是随便加的呢?那倒也不是,不同的参数有不同的含义,负责镇压不同的警告,具体含义如下表所示:

参数值 含义
all to suppress all warnings(抑制所有警告)
boxing to suppress warnings relative to boxing/unboxing operations(要抑制与箱/非装箱操作相关的警告)
cast to suppress warnings relative to cast operations(为了抑制与强制转换操作相关的警告))
dep-ann to suppress warnings relative to deprecated annotation(要抑制相对于弃用注释的警告)
deprecation to suppress warnings relative to deprecation(要抑制相对于弃用的警告)
fallthrough to suppress warnings relative to missing breaks in switch statements(在switch语句中,抑制与缺失中断相关的警告)
finally to suppress warnings relative to finally block that don’t return(为了抑制警告,相对于最终阻止不返回的警告)
hiding to suppress warnings relative to locals that hide variable(为了抑制本地隐藏变量的警告)
incomplete-switch to suppress warnings relative to missing entries in a switch statement (enum case)(为了在switch语句(enum案例)中抑制相对于缺失条目的警告)
nls to suppress warnings relative to non-nls string literals(要抑制相对于非nls字符串字面量的警告)
null to suppress warnings relative to null analysis(为了抑制与null分析相关的警告)
rawtypes to suppress warnings relative to un-specific types when using generics on class params(在类params上使用泛型时,要抑制相对于非特异性类型的警告)
restriction to suppress warnings relative to usage of discouraged or forbidden references(禁止使用警告或禁止引用的警告)
rawtypes to suppress warnings relative to un-specific types when using generics on class params(在类params上使用泛型时,要抑制相对于非特异性类型的警告)
serial to suppress warnings relative to missing serialVersionUID field for a serializable class(为了一个可串行化的类,为了抑制相对于缺失的serialVersionUID字段的警告)
static-access o suppress warnings relative to incorrect static access(o抑制与不正确的静态访问相关的警告)
synthetic-access to suppress warnings relative to unoptimized access from inner classes(相对于内部类的未优化访问,来抑制警告)
unchecked to suppress warnings relative to unchecked operations(相对于不受约束的操作,抑制警告)
unqualified-field-access to suppress warnings relative to field access unqualified(为了抑制与现场访问相关的警告)
unused oto suppress warnings relative to unused code(抑制没有使用过代码的警告)

当大家看到最后一个unused时,是不是瞬间就知道了为什么本博主之前用的是@SuppressWarnings("unused")了呢?当然了,抑制警告注解@SuppressWarnings虽然是强迫症的福利,但是平时工作中使用较少,大家了解下就好,也不必深究。

元注解

在之前我们已经了解了JDK内置的三个注解重写注解@Override废弃注解@Deprecated以及抑制警告注解@SuppressWarnings,想必大家已经比较清楚它们的作用了,那么注解的作用仅限于此吗?

当然不是,看看框架里面那些注解,比如@RestController@Autowired等,就知道注解没那么简单了!!!

当然了,我们也可以自定义属于自己的注解。这么一说,是不是有意思多了呢?

至于怎么自定义属于自己的注解呢?这就不得不说到元注解了。

那么,什么是元注解呢?我们先来看之前重写注解@Override的源码:

package java.lang;

import java.lang.annotation.*;

/**
 * @author  Peter von der Ah&eacute;
 * @author  Joshua Bloch
 * @jls 9.6.1.4 @Override
 * @since 1.5
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
   
}

我们会发现在重写注解@Override的上方有这样两行代码:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)

这个@Target以及@Retention就是所谓的元注解了!

元注解就是注解的注解,用于给其他注解作出说明!

在jdk1.5中总共定义了四个元注解供我们使用,他们分别是:

  1. @Target:用于描述注解的使用范围。
  2. @Retention:用于描述注解的生命周期,指示注解要保留多久。
  3. @Documented:说明该注解被包含在javadoc中。
  4. @Inherited:表示子类可以继承父类的该注解。

然后,在jdk1.8中又新定义le两个元注解:

  1. @Native: 表示被注解的内容是原生(本机)相关的。(说白了就是去调用非Java语言实现的代码,比如C)
  2. @Repeatable:表示该注解可以重复使用。

大家看到这里,想必也有些懵了:6个元注解,该怎么用呀?

不必担心,简化思维,我们重点关注前面两个元注解就好了:@Target以及@Retention,而且这两个元注解里面的值也是规定好了的枚举值。为什么说这两个注解很重要呢?因为其他注解都可以少,唯独这两个注解必不可少!!!

@Target

含义之前已经讲过了:用于描述注解的使用范围。

我们来看一下@Target的源代码:

package java.lang.annotation;

/**
 * @since 1.5
 * @jls 9.6.4.1 @Target
 * @jls 9.7.4 Where Annotations May Appear
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
   
   
    ElementType[] value();
}

这样我们就会发现注解@Target里面的参数值是个ElementType数组:

ElementType[] value();

点开ElementType的源代码一看,这是个枚举类型

package java.lang.annotation;

/**
 *
 * @author  Joshua Bloch
 * @since 1.5
 * @jls 9.6.4.1 @Target
 * @jls 4.1 The Kinds of Types and Values
 */
public enum ElementType {
   
    /** Class, interface (including annotation type), or enum declaration */
    TYPE, // 类

    /** Field declaration (includes enum constants) */
    FIELD, // 属性

    /** Method declaration */
    METHOD, // 方法

    /** Formal parameter declaration */
    PARAMETER, // 正式参数

    /** Constructor declaration */
    CONSTRUCTOR, // 构造函数

    /** Local variable declaration */
    LOCAL_VARIABLE, // 局部变量

    /** Annotation type declaration */
    ANNOTATION_TYPE, // 注解类型

    /** Package declaration */
    PACKAGE, // 包

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER, // 键入参数

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE // 使用类型
}

ElementType内各个值的含义已经写在注释上了,比如如果我们想某个注解能够使用的类(TYPE)、方法(METHOD)以及属性(FIELD)上,则应该在该注解上加上:

@Target({
   ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})

当然了,如果不使用@Target注解,该注解可以作用于任何元素上!!!

@Retention

同样,该注解的含义之前也已经讲过了:用于描述注解的生命周期,指示注解要保留多久。

我们也来看一下它的源码:

package java.lang.annotation;

/**
 * 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();
}

发现它的参数值也是一个枚举值RetentionPolicy

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
}

里面有三个值,它们的含义以及范围大小关系为:

SOURCE(源码) < CLASS(编译) < RUNTIME(运行时)

当然了,我们实际使用过程中 一般用runtime而如果当我们不使用@Retention注解时,默认为CLASS,也就是注解保留到编译阶段。

小小的总结一下:@Target以及@Retention注解是我们最常用到的注解,但是你不加上也没关系,因为有默认值,但是我们一般会加上以便加以某种限制。

自定义注解

好了,在上一个章节,我们介绍了元注解,这里我们可以来学着自定义注解了,首先来介绍一下自定义注解的格式:

元注解
修饰符 @interface 注解名 {
   
	
	配置参数类型 配置参数名(); // 配置参数可以有多个,也可以没有
}

比如@SuppressWarnings注解:

@Target({
   TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
   
  
    String[] value();
}

当我们声明了一个自定义注解,则自动继承了java.lang.annotation.Annotation接口,如我们按照之前的语法规则自定义一个名为myAnnotation的注解:

// 该注解保留在运行时
@Retention(RetentionPolicy.RUNTIME)

// 该注解可作用在类、属性、方法上
@Target({
   ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@interface myAnnotation {
   

    String[] value();
}

不过需要注意的是:配置参数的类型只能是基本数据类型、String、enum、注解类型,而不能是其他,比如Object、Integer等。

我们来简单使用一下这个注解吧:

package annotation;

import java.lang.annotation.*;

/**
 * @ClassName MyAnnotationTest
 * @Description 自定义注解测试类
 * @Author 古阙月
 * @Date 2021/4/11 16:23
 * @Version 1.0
 */
@myAnnotation("MyAnnotationTest")
public class MyAnnotationTest {
   

    @myAnnotation("main")
    public static void main(String[] args) {
   

    }
}

// 该注解保留在运行时
@Retention(RetentionPolicy.RUNTIME)

// 该注解可作用在类、属性、方法上
@Target({
   ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@interface myAnnotation {
   

    String[] value();
}

此处,还有一点细节,如果自定义注解内配置参数仅有一个时并且配置参数名为value,配置参数名可省略:

@myAnnotation("MyAnnotationTest")
public class MyAnnotationTest {
   

    @myAnnotation("main")
    public static void main(String[] args) {
   

    }
}

但是如果有多个,或者配置参数名为一个且不为value,则需要加上参数名,如:

// 该注解保留在运行时
@Retention(RetentionPolicy.RUNTIME)

// 该注解可作用在类、属性、方法上
@Target({
   ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@interface myAnnotation {
   

    String[] value();

    // 修饰符
    String modifier();
}

应当加上参数名:

@myAnnotation(value = "MyAnnotationTest", modifier = "public")
public class MyAnnotationTest {
   

    @myAnnotation(value = "main", modifier = "public")
    public static void main(String[] args) {
   

    }
}

但是,在配置参数后面可以加上默认值,语法为:

配置参数类型 配置参数名() default 默认值;

如果有默认值的话,我们在使用注解时,可以不手动加入配置参数值,如:

// 该注解保留在运行时
@Retention(RetentionPolicy.RUNTIME)

// 该注解可作用在类、属性、方法上
@Target({
   ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@interface myAnnotation {
   

    String[] value() default "methodName";

    // 修饰符
    String modifier() default "public";
}

可以不加配置参数值,后面的括号也可有可无:

@myAnnotation()
public class MyAnnotationTest {
   

    @myAnnotation
    public static void main(String[] args) {
   

    }
}

总之,得保证一定有值就对了!!!

尾声

好了,到这里这篇介绍注解的博客就结束了。如果有一定基础的小伙伴,估计会说:就这?那框架里面怎么使用的注解呀?你这啥功能都没实现,都没有呀!那要这注解有何用?

其实我真的挺冤枉的:博客陆陆续续写了一个多礼拜,也肝了近一万五千字了。只来得及写一些简单的介绍以及基本语法,实在是肝不动了。至于注解如何结合Spring框架的IOC以及AOP思想,实现种种神奇的功能?比如我们常见的事务。

欲知后事如何,请听下回分解!!!

能看到这里,并且看完我的碎碎念的 估计是神人了,我猜是没有多少人看到这里的。创作不易,如果觉得不错的话,顺手给个点赞、评论以及收藏加关注呗!如果有哪里讲的不对的地方,也非常欢迎评论指正哦!


转载:https://blog.csdn.net/Qizhi_Hu/article/details/115526440
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场