当前位置: 移动技术网 > IT编程>开发语言>Java > 深入理解Java注解类型(@Annotation)

深入理解Java注解类型(@Annotation)

2019年07月22日  | 移动技术网IT编程  | 我要评论

java注解是在jdk5时引入的新特性,鉴于目前大部分框架(如spring)都使用了注解简化代码并提高编码的效率,因此掌握并深入理解注解对于一个java工程师是来说是很有必要的事。本篇我们将通过以下几个角度来分析注解的相关知识点

理解java注解

实际上java注解与普通修饰符(public、static、void等)的使用方式并没有多大区别,下面的例子是常见的注解:

public class annotationdemo {
  //@test注解修饰方法a
  @test
  public static void a(){
    system.out.println("test.....");
  }

  //一个方法上可以拥有多个不同的注解
  @deprecated
  @suppresswarnings("uncheck")
  public static void b(){

  }
}

通过在方法上使用@test注解后,在运行该方法时,测试框架会自动识别该方法并单独调用,@test实际上是一种标记注解,起标记作用,运行时告诉测试框架该方法为测试方法。而对于@deprecated和@suppresswarnings(“uncheck”),则是java本身内置的注解,在代码中,可以经常看见它们,但这并不是一件好事,毕竟当方法或是类上面有@deprecated注解时,说明该方法或是类都已经过期不建议再用,@suppresswarnings 则表示忽略指定警告,比如@suppresswarnings(“uncheck”),这就是注解的最简单的使用方式,那么下面我们就来看看注解定义的基本语法

基本语法

声明注解与元注解

我们先来看看前面的test注解是如何声明的:

//声明test注解
@target(elementtype.method)
@retention(retentionpolicy.runtime)
public @interface test {

} 

我们使用了@interface声明了test注解,并使用@target注解传入elementtype.method参数来标明@test只能用于方法上,@retention(retentionpolicy.runtime)则用来表示该注解生存期是运行时,从代码上看注解的定义很像接口的定义,确实如此,毕竟在编译后也会生成test.class文件。对于@target和@retention是由java提供的元注解,所谓元注解就是标记其他注解的注解,下面分别介绍

@target 用来约束注解可以应用的地方(如方法、类或字段),其中elementtype是枚举类型,其定义如下,也代表可能的取值范围

public enum elementtype {
  /**标明该注解可以用于类、接口(包括注解类型)或enum声明*/
  type,

  /** 标明该注解可以用于字段(域)声明,包括enum实例 */
  field,

  /** 标明该注解可以用于方法声明 */
  method,

  /** 标明该注解可以用于参数声明 */
  parameter,

  /** 标明注解可以用于构造函数声明 */
  constructor,

  /** 标明注解可以用于局部变量声明 */
  local_variable,

  /** 标明注解可以用于注解声明(应用于另一个注解上)*/
  annotation_type,

  /** 标明注解可以用于包声明 */
  package,

  /**
   * 标明注解可以用于类型参数声明(1.8新加入)
   * @since 1.8
   */
  type_parameter,

  /**
   * 类型使用声明(1.8新加入)
   * @since 1.8
   */
  type_use
}

请注意,当注解未指定target值时,则此注解可以用于任何元素之上,多个值使用{}包含并用逗号隔开,如下:

复制代码 代码如下:

@target(value={constructor, field, local_variable, method, package, parameter, type})

@retention用来约束注解的生命周期,分别有三个值,源码级别(source),类文件级别(class)或者运行时级别(runtime),其含有如下:

  1. source:注解将被编译器丢弃(该类型的注解信息只会保留在源码里,源码经过编译后,注解信息会被丢弃,不会保留在编译好的class文件里)
  2. class:注解在class文件中可用,但会被vm丢弃(该类型的注解信息会保留在源码里和class文件里,在执行的时候,不会加载到虚拟机中),请注意,当注解未定义retention值时,默认值是class,如java内置注解,@override、@deprecated、@suppresswarnning等
  3. runtime:注解信息将在运行期(jvm)也保留,因此可以通过反射机制读取注解的信息(源码、class文件和执行的时候都有注解的信息),如springmvc中的@controller、@autowired、@requestmapping等。

注解元素及其数据类型

通过上述对@test注解的定义,我们了解了注解定义的过程,由于@test内部没有定义其他元素,所以@test也称为标记注解(marker annotation),但在自定义注解中,一般都会包含一些元素以表示某些值,方便处理器使用,这点在下面的例子将会看到:

/**
 * created by wuzejian on 2017/5/18.
 * 对应数据表注解
 */
@target(elementtype.type)//只能应用于类上
@retention(retentionpolicy.runtime)//保存到运行时
public @interface dbtable {
  string name() default "";
}

上述定义一个名为dbtable的注解,该用于主要用于数据库表与bean类的映射(稍后会有完整案例分析),与前面test注解不同的是,我们声明一个string类型的name元素,其默认值为空字符,但是必须注意到对应任何元素的声明应采用方法的声明方式,同时可选择使用default提供默认值,@dbtable使用方式如下:

//在类上使用该注解
@dbtable(name = "member")
public class member {
  //.......
}

关于注解支持的元素数据类型除了上述的string,还支持如下数据类型

  1. 所有基本类型(int,float,boolean,byte,double,char,long,short)
  2. string
  3. class
  4. enum
  5. annotation
  6. 上述类型的数组

倘若使用了其他数据类型,编译器将会丢出一个编译错误,注意,声明注解元素时可以使用基本类型但不允许使用任何包装类型,同时还应该注意到注解也可以作为元素的类型,也就是嵌套注解,下面的代码演示了上述类型的使用过程:

package com.zejian.annotationdemo;

import java.lang.annotation.elementtype;
import java.lang.annotation.retention;
import java.lang.annotation.retentionpolicy;
import java.lang.annotation.target;

/**
 * created by wuzejian on 2017/5/19.
 * 数据类型使用demo
 */

@target(elementtype.type)
@retention(retentionpolicy.runtime)
@interface reference{
  boolean next() default false;
}

public @interface annotationelementdemo {
  //枚举类型
  enum status {fixed,normal};

  //声明枚举
  status status() default status.fixed;

  //布尔类型
  boolean showsupport() default false;

  //string类型
  string name()default "";

  //class类型
  class<?> testcase() default void.class;

  //注解嵌套
  reference reference() default @reference(next=true);

  //数组类型
  long[] value();
}

编译器对默认值的限制

编译器对元素的默认值有些过分挑剔。首先,元素不能有不确定的值。也就是说,元素必须要么具有默认值,要么在使用注解时提供元素的值。其次,对于非基本类型的元素,无论是在源代码中声明,还是在注解接口中定义默认值,都不能以null作为值,这就是限制,没有什么利用可言,但造成一个元素的存在或缺失状态,因为每个注解的声明中,所有的元素都存在,并且都具有相应的值,为了绕开这个限制,只能定义一些特殊的值,例如空字符串或负数,表示某个元素不存在。

注解不支持继承

注解是不支持继承的,因此不能使用关键字extends来继承某个@interface,但注解在编译后,编译器会自动继承java.lang.annotation.annotation接口,这里我们反编译前面定义的dbtable注解

package com.zejian.annotationdemo;

import java.lang.annotation.annotation;
//反编译后的代码
public interface dbtable extends annotation
{
  public abstract string name();
}

虽然反编译后发现dbtable注解继承了annotation接口,请记住,即使java的接口可以实现多继承,但定义注解时依然无法使用extends关键字继承@interface。

快捷方式

所谓的快捷方式就是注解中定义了名为value的元素,并且在使用该注解时,如果该元素是唯一需要赋值的一个元素,那么此时无需使用key=value的语法,而只需在括号内给出value元素所需的值即可。这可以应用于任何合法类型的元素,记住,这限制了元素名必须为value,简单案例如下

package com.zejian.annotationdemo;

import java.lang.annotation.elementtype;
import java.lang.annotation.retention;
import java.lang.annotation.retentionpolicy;
import java.lang.annotation.target;

/**
 * created by zejian on 2017/5/20.

 */
//定义注解
@target(elementtype.field)
@retention(retentionpolicy.runtime)
@interface integervaule{
  int value() default 0;
  string name() default "";
}

//使用注解
public class quicklyway {

  //当只想给value赋值时,可以使用以下快捷方式
  @integervaule(20)
  public int age;

  //当name也需要赋值时必须采用key=value的方式赋值
  @integervaule(value = 10000,name = "money")
  public int money;

}

java内置注解与其它元注解

接着看看java提供的内置注解,主要有3个,如下:

@override:用于标明此方法覆盖了父类的方法,源码如下

@target(elementtype.method)
@retention(retentionpolicy.source)
public @interface override {
}

@deprecated:用于标明已经过时的方法或类,源码如下,关于@documented稍后分析:

@documented
@retention(retentionpolicy.runtime)
@target(value={constructor, field, local_variable, method, package, parameter, type})
public @interface deprecated {
}

@suppresswarnnings:用于有选择的关闭编译器对类、方法、成员变量、变量初始化的警告,其实现源码如下:

@target({type, field, method, parameter, constructor, local_variable})
@retention(retentionpolicy.source)
public @interface suppresswarnings {
  string[] value();
}

其内部有一个string数组,主要接收值如下:

  1. deprecation:使用了不赞成使用的类或方法时的警告;
  2. unchecked:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (generics) 来指定集合保存的类型;
  3. fallthrough:当 switch 程序块直接通往下一种情况而没有 break 时的警告;
  4. path:在类路径、源文件路径等中有不存在的路径时的警告;
  5. serial:当在可序列化的类上缺少 serialversionuid 定义时的警告;
  6. finally:任何 finally 子句不能正常完成时的警告;
  7. all:关于以上所有情况的警告。

这个三个注解比较简单,看个简单案例即可:

//注明该类已过时,不建议使用
@deprecated
class a{
  public void a(){ }

  //注明该方法已过时,不建议使用
  @deprecated()
  public void b(){ }
}

class b extends a{

  @override //标明覆盖父类a的a方法
  public void a() {
    super.a();
  }

  //去掉检测警告
  @suppresswarnings({"uncheck","deprecation"})
  public void c(){ } 
  //去掉检测警告
  @suppresswarnings("uncheck")
  public void d(){ }
}

前面我们分析了两种元注解,@target和@retention,除了这两种元注解,java还提供了另外两种元注解,@documented和@inherited,下面分别介绍:

@documented 被修饰的注解会生成到javadoc中

/**
 * created by zejian on 2017/5/20.
 */
@documented
@target(elementtype.type)
@retention(retentionpolicy.runtime)
public @interface documenta {
}

//没有使用@documented
@target(elementtype.type)
@retention(retentionpolicy.runtime)
public @interface documentb {
}

//使用注解
@documenta
@documentb
public class documentdemo {
  public void a(){
  }
}

使用javadoc命令生成文档:

复制代码 代码如下:

zejian@zejiandembp annotationdemo$ javadoc documentdemo.java documenta.java documentb.java

如下:

可以发现使用@documented元注解定义的注解(@documenta)将会生成到javadoc中,而@documentb则没有在doc文档中出现,这就是元注解@documented的作用。

@inherited 可以让注解被继承,但这并不是真的继承,只是通过使用@inherited,可以让子类class对象使用getannotations()获取父类被@inherited修饰的注解,如下:

@inherited
@documented
@target(elementtype.type)
@retention(retentionpolicy.runtime)
public @interface documenta {
}

@target(elementtype.type)
@retention(retentionpolicy.runtime)
public @interface documentb {
}

@documenta
class a{ }

class b extends a{ }

@documentb
class c{ }

class d extends c{ }

//测试
public class documentdemo {

  public static void main(string... args){
    a instancea=new b();
    system.out.println("已使用的@inherited注解:"+arrays.tostring(instancea.getclass().getannotations()));

    c instancec = new d();

    system.out.println("没有使用的@inherited注解:"+arrays.tostring(instancec.getclass().getannotations()));
  }

  /**
   * 运行结果:
   已使用的@inherited注解:[@com.zejian.annotationdemo.documenta()]
   没有使用的@inherited注解:[]
   */
}

注解与反射机制

前面经过反编译后,我们知道java所有注解都继承了annotation接口,也就是说 java使用annotation接口代表注解元素,该接口是所有annotation类型的父接口。同时为了运行时能准确获取到注解的相关信息,java在java.lang.reflect 反射包下新增了annotatedelement接口,它主要用于表示目前正在 vm 中运行的程序中已使用注解的元素,通过该接口提供的方法可以利用反射技术地读取注解的信息,如反射包的constructor类、field类、method类、package类和class类都实现了annotatedelement接口,它简要含义如下(更多详细介绍可以看 深入理解java类型信息(class对象)与反射机制):

  1. class:类的class对象定义  
  2. constructor:代表类的构造器定义  
  3. field:代表类的成员变量定义
  4. method:代表类的方法定义  
  5. package:代表类的包定义

下面是annotatedelement中相关的api方法,以上5个类都实现以下的方法

返回值 方法名称 说明
<a extends annotation> getannotation(class<a> annotationclass) 该元素如果存在指定类型的注解,则返回这些注解,否则返回 null。
annotation[] getannotations() 返回此元素上存在的所有注解,包括从父类继承的
boolean isannotationpresent(class<? extends annotation> annotationclass) 如果指定类型的注解存在于此元素上,则返回 true,否则返回 false。
annotation[] getdeclaredannotations() 返回直接存在于此元素上的所有注解,注意,不包括父类的注解,调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响,没有则返回长度为0的数组

简单案例演示如下:

package com.zejian.annotationdemo;

import java.lang.annotation.annotation;
import java.util.arrays;

/**
 * created by zejian on 2017/5/20.

 */
@documenta
class a{ }

//继承了a类
@documentb
public class documentdemo extends a{

  public static void main(string... args){

    class<?> clazz = documentdemo.class;
    //根据指定注解类型获取该注解
    documenta documenta=clazz.getannotation(documenta.class);
    system.out.println("a:"+documenta);

    //获取该元素上的所有注解,包含从父类继承
    annotation[] an= clazz.getannotations();
    system.out.println("an:"+ arrays.tostring(an));
    //获取该元素上的所有注解,但不包含继承!
    annotation[] an2=clazz.getdeclaredannotations();
    system.out.println("an2:"+ arrays.tostring(an2));

    //判断注解documenta是否在该元素上
    boolean b=clazz.isannotationpresent(documenta.class);
    system.out.println("b:"+b);

    /**
     * 执行结果:
     a:@com.zejian.annotationdemo.documenta()
     an:[@com.zejian.annotationdemo.documenta(), @com.zejian.annotationdemo.documentb()]
     an2:@com.zejian.annotationdemo.documentb()
     b:true
     */
  }
}

运行时注解处理器

了解完注解与反射的相关api后,现在通过一个实例(该例子是博主改编自《tinking in java》)来演示利用运行时注解来组装数据库sql的构建语句的过程

/**
 * created by wuzejian on 2017/5/18.
 * 表注解
 */
@target(elementtype.type)//只能应用于类上
@retention(retentionpolicy.runtime)//保存到运行时
public @interface dbtable {
  string name() default "";
}


/**
 * created by wuzejian on 2017/5/18.
 * 注解integer类型的字段
 */
@target(elementtype.field)
@retention(retentionpolicy.runtime)
public @interface sqlinteger {
  //该字段对应数据库表列名
  string name() default "";
  //嵌套注解
  constraints constraint() default @constraints;
}


/**
 * created by wuzejian on 2017/5/18.
 * 注解string类型的字段
 */
@target(elementtype.field)
@retention(retentionpolicy.runtime)
public @interface sqlstring {

  //对应数据库表的列名
  string name() default "";

  //列类型分配的长度,如varchar(30)的30
  int value() default 0;

  constraints constraint() default @constraints;
}


/**
 * created by wuzejian on 2017/5/18.
 * 约束注解
 */

@target(elementtype.field)//只能应用在字段上
@retention(retentionpolicy.runtime)
public @interface constraints {
  //判断是否作为主键约束
  boolean primarykey() default false;
  //判断是否允许为null
  boolean allownull() default false;
  //判断是否唯一
  boolean unique() default false;
}

/**
 * created by wuzejian on 2017/5/18.
 * 数据库表member对应实例类bean
 */
@dbtable(name = "member")
public class member {
  //主键id
  @sqlstring(name = "id",value = 50, constraint = @constraints(primarykey = true))
  private string id;

  @sqlstring(name = "name" , value = 30)
  private string name;

  @sqlinteger(name = "age")
  private int age;

  @sqlstring(name = "description" ,value = 150 , constraint = @constraints(allownull = true))
  private string description;//个人描述

  //省略set get.....
}

上述定义4个注解,分别是@dbtable(用于类上)、@constraints(用于字段上)、 @sqlstring(用于字段上)、@sqlstring(用于字段上)并在member类中使用这些注解,这些注解的作用的是用于帮助注解处理器生成创建数据库表member的构建语句,在这里有点需要注意的是,我们使用了嵌套注解@constraints,该注解主要用于判断字段是否为null或者字段是否唯一。必须清楚认识到上述提供的注解生命周期必须为@retention(retentionpolicy.runtime),即运行时,这样才可以使用反射机制获取其信息。有了上述注解和使用,剩余的就是编写上述的注解处理器了,前面我们聊了很多注解,其处理器要么是java自身已提供、要么是框架已提供的,我们自己都没有涉及到注解处理器的编写,但上述定义处理sql的注解,其处理器必须由我们自己编写了,如下

package com.zejian.annotationdemo;
import java.lang.annotation.annotation;
import java.lang.reflect.field;
import java.util.arraylist;
import java.util.list;

/**
 * created by zejian on 2017/5/13.
 * 运行时注解处理器,构造表创建语句
 */
public class tablecreator {

 public static string createtablesql(string classname) throws classnotfoundexception {
  class<?> cl = class.forname(classname);
  dbtable dbtable = cl.getannotation(dbtable.class);
  //如果没有表注解,直接返回
  if(dbtable == null) {
   system.out.println(
       "no dbtable annotations in class " + classname);
   return null;
  }
  string tablename = dbtable.name();
  // if the name is empty, use the class name:
  if(tablename.length() < 1)
   tablename = cl.getname().touppercase();
  list<string> columndefs = new arraylist<string>();
  //通过class类api获取到所有成员字段
  for(field field : cl.getdeclaredfields()) {
   string columnname = null;
   //获取字段上的注解
   annotation[] anns = field.getdeclaredannotations();
   if(anns.length < 1)
    continue; // not a db table column

   //判断注解类型
   if(anns[0] instanceof sqlinteger) {
    sqlinteger sint = (sqlinteger) anns[0];
    //获取字段对应列名称,如果没有就是使用字段名称替代
    if(sint.name().length() < 1)
     columnname = field.getname().touppercase();
    else
     columnname = sint.name();
    //构建语句
    columndefs.add(columnname + " int" +
        getconstraints(sint.constraint()));
   }
   //判断string类型
   if(anns[0] instanceof sqlstring) {
    sqlstring sstring = (sqlstring) anns[0];
    // use field name if name not specified.
    if(sstring.name().length() < 1)
     columnname = field.getname().touppercase();
    else
     columnname = sstring.name();
    columndefs.add(columnname + " varchar(" +
        sstring.value() + ")" +
        getconstraints(sstring.constraint()));
   }


  }
  //数据库表构建语句
  stringbuilder createcommand = new stringbuilder(
      "create table " + tablename + "(");
  for(string columndef : columndefs)
   createcommand.append("\n  " + columndef + ",");

  // remove trailing comma
  string tablecreate = createcommand.substring(
      0, createcommand.length() - 1) + ");";
  return tablecreate;
 }


  /**
   * 判断该字段是否有其他约束
   * @param con
   * @return
   */
 private static string getconstraints(constraints con) {
  string constraints = "";
  if(!con.allownull())
   constraints += " not null";
  if(con.primarykey())
   constraints += " primary key";
  if(con.unique())
   constraints += " unique";
  return constraints;
 }

 public static void main(string[] args) throws exception {
  string[] arg={"com.zejian.annotationdemo.member"};
  for(string classname : arg) {
   system.out.println("table creation sql for " +
       classname + " is :\n" + createtablesql(classname));
  }

  /**
   * 输出结果:
   table creation sql for com.zejian.annotationdemo.member is :
   create table member(
   id varchar(50) not null primary key,
   name varchar(30) not null,
   age int not null,
   description varchar(150)
   );
   */
 }
}

如果对反射比较熟悉的同学,上述代码就相对简单了,我们通过传递member的全路径后通过class.forname()方法获取到member的class对象,然后利用class对象中的方法获取所有成员字段field,最后利用field.getdeclaredannotations()遍历每个field上的注解再通过注解的类型判断来构建建表的sql语句。这便是利用注解结合反射来构建sql语句的简单的处理器模型,是否已回想起hibernate?

java 8中注解增强

元注解@repeatable

元注解@repeatable是jdk1.8新加入的,它表示在同一个位置重复相同的注解。在没有该注解前,一般是无法在同一个类型上使用相同的注解的

//java8前无法这样使用
@filterpath("/web/update")
@filterpath("/web/add")
public class a {}

java8前如果是想实现类似的功能,我们需要在定义@filterpath注解时定义一个数组元素接收多个值如下

@target(elementtype.type)
@retention(retentionpolicy.runtime)
public @interface filterpath {
  string [] value();
}

//使用
@filterpath({"/update","/add"})
public class a { }

但在java8新增了@repeatable注解后就可以采用如下的方式定义并使用了

package com.zejian.annotationdemo;

import java.lang.annotation.*;

/**
 * created by zejian on 2017/5/20.
 */
//使用java8新增@repeatable原注解
@target({elementtype.type,elementtype.field,elementtype.method})
@retention(retentionpolicy.runtime)
@repeatable(filterpaths.class)//参数指明接收的注解class
public @interface filterpath {
  string value();
}

@target(elementtype.type)
@retention(retentionpolicy.runtime)
@interface filterpaths {
  filterpath[] value();
}

//使用案例
@filterpath("/web/update")
@filterpath("/web/add")
@filterpath("/web/delete")
class aa{ }

我们可以简单理解为通过使用@repeatable后,将使用@filterpaths注解作为接收同一个类型上重复注解的容器,而每个@filterpath则负责保存指定的路径串。为了处理上述的新增注解,java8还在annotatedelement接口新增了getdeclaredannotationsbytype() 和 getannotationsbytype()两个方法并在接口给出了默认实现,在指定@repeatable的注解时,可以通过这两个方法获取到注解相关信息。但请注意,旧版api中的getdeclaredannotation()和 getannotation()是不对@repeatable注解的处理的(除非该注解没有在同一个声明上重复出现)。注意getdeclaredannotationsbytype方法获取到的注解不包括父类,其实当 getannotationsbytype()方法调用时,其内部先执行了getdeclaredannotationsbytype方法,只有当前类不存在指定注解时,getannotationsbytype()才会继续从其父类寻找,但请注意如果@filterpath和@filterpaths没有使用了@inherited的话,仍然无法获取。下面通过代码来演示:

/**
 * created by zejian on 2017/5/20.
 */
//使用java8新增@repeatable原注解
@target({elementtype.type,elementtype.field,elementtype.method})
@retention(retentionpolicy.runtime)
@repeatable(filterpaths.class)
public @interface filterpath {
  string value();
}


@target(elementtype.type)
@retention(retentionpolicy.runtime)
@interface filterpaths {
  filterpath[] value();
}

@filterpath("/web/list")
class cc { }

//使用案例
@filterpath("/web/update")
@filterpath("/web/add")
@filterpath("/web/delete")
class aa extends cc{
  public static void main(string[] args) {

    class<?> clazz = aa.class;
    //通过getannotationsbytype方法获取所有重复注解
    filterpath[] annotationsbytype = clazz.getannotationsbytype(filterpath.class);
    filterpath[] annotationsbytype2 = clazz.getdeclaredannotationsbytype(filterpath.class);
    if (annotationsbytype != null) {
      for (filterpath filter : annotationsbytype) {
        system.out.println("1:"+filter.value());
      }
    }

    system.out.println("-----------------");

    if (annotationsbytype2 != null) {
      for (filterpath filter : annotationsbytype2) {
        system.out.println("2:"+filter.value());
      }
    }


    system.out.println("使用getannotation的结果:"+clazz.getannotation(filterpath.class));


    /**
     * 执行结果(当前类拥有该注解filterpath,则不会从cc父类寻找)
     1:/web/update
     1:/web/add
     1:/web/delete
     -----------------
     2:/web/update
     2:/web/add
     2:/web/delete
     使用getannotation的结果:null
     */
  }
}

从执行结果来看如果当前类拥有该注解@filterpath,则getannotationsbytype方法不会从cc父类寻找,下面看看另外一种情况,即aa类上没有@filterpath注解

/**
 * created by zejian on 2017/5/20.
 */
//使用java8新增@repeatable原注解
@target({elementtype.type,elementtype.field,elementtype.method})
@retention(retentionpolicy.runtime)
@inherited //添加可继承元注解
@repeatable(filterpaths.class)
public @interface filterpath {
  string value();
}


@target(elementtype.type)
@retention(retentionpolicy.runtime)
@inherited //添加可继承元注解
@interface filterpaths {
  filterpath[] value();
}

@filterpath("/web/list")
@filterpath("/web/getlist")
class cc { }

//aa上不使用@filterpath注解,getannotationsbytype将会从父类查询
class aa extends cc{
  public static void main(string[] args) {

    class<?> clazz = aa.class;
    //通过getannotationsbytype方法获取所有重复注解
    filterpath[] annotationsbytype = clazz.getannotationsbytype(filterpath.class);
    filterpath[] annotationsbytype2 = clazz.getdeclaredannotationsbytype(filterpath.class);
    if (annotationsbytype != null) {
      for (filterpath filter : annotationsbytype) {
        system.out.println("1:"+filter.value());
      }
    }

    system.out.println("-----------------");

    if (annotationsbytype2 != null) {
      for (filterpath filter : annotationsbytype2) {
        system.out.println("2:"+filter.value());
      }
    }


    system.out.println("使用getannotation的结果:"+clazz.getannotation(filterpath.class));


    /**
     * 执行结果(当前类没有@filterpath,getannotationsbytype方法从cc父类寻找)
     1:/web/list
     1:/web/getlist
     -----------------
     使用getannotation的结果:null
     */
  }
}

注意定义@filterpath和@filterpath时必须指明@inherited,getannotationsbytype方法否则依旧无法从父类获取@filterpath注解,这是为什么呢,不妨看看getannotationsbytype方法的实现源码:

//接口默认实现方法
default <t extends annotation> t[] getannotationsbytype(class<t> annotationclass) {
//先调用getdeclaredannotationsbytype方法
t[] result = getdeclaredannotationsbytype(annotationclass);

//判断当前类获取到的注解数组是否为0
if (result.length == 0 && this instanceof class && 
//判断定义注解上是否使用了@inherited元注解 
 annotationtype.getinstance(annotationclass).isinherited()) { // inheritable
    //从父类获取
    class<?> superclass = ((class<?>) this).getsuperclass();
  if (superclass != null) {
   result = superclass.getannotationsbytype(annotationclass);
    }
  }

  return result;
}

新增的两种elementtype

在java8中 elementtype 新增两个枚举成员,type_parameter 和 type_use ,在java8前注解只能标注在一个声明(如字段、类、方法)上,java8后,新增的type_parameter可以用于标注类型参数,而type_use则可以用于标注任意类型(不包括class)。如下所示

//type_parameter 标注在类型参数上
class d<@parameter t> { }

//type_use则可以用于标注任意类型(不包括class)
//用于父类或者接口
class image implements @rectangular shape { }

//用于构造函数
new @path string("/usr/bin")

//用于强制转换和instanceof检查,注意这些注解中用于外部工具,它们不会对类型转换或者instanceof的检查行为带来任何影响。
string path=(@path string)input;
if(input instanceof @path string)

//用于指定异常
public person read() throws @localized ioexception.

//用于通配符绑定
list<@readonly ? extends person>
list<? extends @readonly person>

@notnull string.class //非法,不能标注class
import java.lang.@notnull string //非法,不能标注import

这里主要说明一下type_use,类型注解用来支持在java的程序中做强类型检查,配合第三方插件工具(如checker framework),可以在编译期检测出runtime error(如unsupportedoperationexception、nullpointerexception异常),避免异常延续到运行期才发现,从而提高代码质量,这就是类型注解的主要作用。总之java 8 新增加了两个注解的元素类型elementtype.type_use 和elementtype.type_parameter ,通过它们,我们可以把注解应用到各种新场合中。

ok~,关于注解暂且聊到这,实际上还有一个大块的知识点没详细聊到,源码级注解处理器,这个话题博主打算后面另开一篇分析。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网