关于Java中的注解类型深入剖析

关于注解的使用,用过应该java的应该都不陌生,最著名的莫过于@Override,所以这篇文章主要是讲讲注解背后的那些事情,至于普通的使用等可以参考下面的相关文章。

一、注解的基本内容

做什么事之前我们首先要搞清为什么?那么注解的出现是为什么?如果你受够了Spring和Hibernate的配置文件中繁琐的配置,因为写错一个字母整个程序都运行不起来,那么你确实有理由去了解注解,它可以:

  • 使代码更加干净简洁,有时候一个注解表达的意思超过整个配置文件;
  • 编译器可以在编译期间对类型进行检查,想想一个字母出错的情况吧!
  • 可以在运行期间方便的进行读取,多亏了java的反射机制;

由于注解的这些特点,一些流行的框架都既支持注解配置方式又支持文件配置方式,比如SpringHibernate等,

Mybatis是个坑,两种支持的方式并不相同,有些事情只能XML配置文件来做,而注解是做不来的,比如复杂的ResultMap、批量插入和更新等;

1. 注解的本来面目

沿袭关于Java 中Enum 的全方位剖析这篇文章的做法,我们探究一下究竟注解是什么?我们先自定义一个注解,关于如果自定义注解的详细内容下面将会说,这里只是简单的作为一个例子!如下Path.java

@Target({ ElementType.FIELD })
@Retention(RUNTIME)
public @interface Path {
    String a = "abc";
    String value();
}

我们通过javac编译这个注解之后也会生成对应的字节码文件,然后通过javap反编译,结果如下:

这里写图片描述

发现了一个事实,其实所谓的注解其实在底层是一个继承自java.lang.annotation.Annotation接口的接口,那么结果就明晰了,接口有的特性注解应该都会有:

  • 可以声明方法,但是不能有实现,而且所有的方法都默认是public abstract
  • 可以声明字段,但是所有字段必须进行初始化,因为所有字段都默认是public static final

但是对于Java8中新出现的接口中的默认方法是不可以在注解中使用的。除了带有接口的特性,还可以直接使用java.lang.annotation.Annotation中的方法。

2. 自定义注解

在分析注解的实际内容之后,我们有必要去了解怎么使用注解?或者说怎么写我们自己的注解?从上面可以看出注解的定义@interface和接口很相似(实际上就是接口),编译之后也会生成一个class文件。

2.1 元注解

所谓元注解就是用来描述其他注解类型的,跟所谓的元数据是相同的意思。Java默认提供的元注解有四种:

  • @Target:表明该注解可以用在类的哪些元素上,可用的值包含在java.lang.annotation.ElementType枚举类下,一般常用的包括用在类上、字段上、方法上等;
  • @Retention:表明该注解存在的阶段,有三个值:
    • RetentionPolicy.SOURCE:该注解只存在于源代码中,编译之后则会消失;
    • RetentionPolicy.CLASS:该注解在编译之后仍然存在,但是在运行时不需要虚拟机来保存,当注解没有使用@Retention,该值也是默认值;
    • RetentionPolicy.RUNTIME:该注解在编译之后仍然存在,在运行时需要虚拟机来保存,因为要通过反射获取,这也是我们常用的;
  • @Inherited:表明被该元注解标识的注解可以被继承,例如如果上面的@Path注解被@Inherited标识,当它被用在父类上时,当在子类上通过反射获取注解时也会获取到;
  • @Documented:表明在生成Javadoc文档时也会包括该注解;

注意:在使用注解时,可以通过静态导入import static的方式只导入我们需要的依赖,而不是全部,例如:

import static java.lang.annotation.RetentionPolicy.RUNTIME;

2.2 注解的使用

在一个注解中也可以包含方法和字段,就像我们在上面说到的一样,但是也可以不包含任何元素,这样的注解称为标记注解,例如@FunctionalInterface。常见的注解使用一般都是通过键值对,即一个名称对应一个属性。但是在这其中还是有一些细节值得注意:

  • value()方法:默认情况下,如果注解中包含且只包含value()方法时,使用注解时可以不加value这个属性名;

  • 默认值default:注解中的方法与接口中的方法不同的是,虽然也没有实现,但是可以设置默认值,当使用注解的时候没有给出该方法的数据时,则注解处理器使用该默认值,另外:

    • 默认值必须是常量,不能是不确定的值,包括不能是null;
    • 如果想要通过默认值指定热定的状态,则可以通过约定-1或者”“等值;

    例子如下:

    @Target({ ElementType.FIELD })
    @Retention(RUNTIME)
    public @interface Path {
        String value() default "";
    }
  • 注解中的元素类型是有限定的,只能是如下几种:

    • 所有的基本类型:
    • String:
    • 枚举类型:典型的就是几个元注解;
    • 注解类型:没错,注解中也能使用注解类型的元素,例如JPA中的@AttributeOverrides中包含@AttributeOverride数组;
    • Class类:
    • 上述元素组成的数组:

    如果使用其他的类型在编译阶段就会报错,另外基本类型的包装类型也不可以在注解中使用。

  • 在类中一个元素中可以使用多个注解,但是在Java 8 之前同一个注解是不能够重复使用在同一个目标上的;

  • 注解类型之间是没有继承关系的,所以extends关键字在注解中是不允许出现的;

3. 注解的处理和获取

注解的获取和获取也可以分为两类:

  • 一类是静态处理:这种方式直接操作的是源文件,而不是编译之后的字节码文件,可以使用apt工作完成,但是我从来没用过;
  • 一类是动态处理:这就是我们常用的,通过反射的方式读取注解的信息,注意此时注解的@Retention必须是RetentionPolicy.RUNTIME

如果使用注解时没有对应的处理器,则该注解也没有神马用!所有要明确的是,注解的目的是提供一种标记的手段,具体这种标记的提取还是要靠处理器完成。

提取注解的时候,首先要明确注解应用在什么地方,我们非常熟悉的是Class类、Field类和Method类,通过

其中的getAnnotation(...)获取对应的注解,但是其实Java给我们提供了一个公共的接口用来描述这种元素,为java.lang.reflect.AnnotatedElement,其中的API可以分为几类:

  • isAnnotationPresent():判断是否被相应的注解标识;

  • 获取不包括父类的注解:

    • getAnnotation(Class<T> annotationClass):获取指定类型的注解;
    • getAnnotations():获取所有的注解;
    • getAnnotationsByType(Class<T> annotationClass):Java 8新提出的API
  • 获取包括父类的注解,与上边的功能是一一对应的:
    • getDeclaredAnnotation(Class<T> annotationClass)
    • getDeclaredAnnotations()
    • getDeclaredAnnotationsByType(Class<T> annotationClass)

上面获取注解的API中getAnnotationsByType(Class<T> annotationClass)getDeclaredAnnotationsByType(Class<T> annotationClass)都是Java 8 新提出的,为的是支持新特性在同一个元素上重复的使用相同的注解,这种使用方式在Java 8之前是不允许的,如下例:

public class Test{
    @Path("a")
    @Path("b")
    private String name;
}

此时如果想要获取name字段上的注解只能使用getAnnotationsByType(Class<T> annotationClass)getDeclaredAnnotationsByType(Class<T> annotationClass)这两个API

除了在java.lang.reflect.AnnotatedElement中定义的API外,其实现类中也有很多的其他工具,比如Class类中的isAnnotation()等。


相关文章:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值