Java基础教程:注解

Java基础教程:注解

本篇文章参考的相关资料链接:

  • 维基百科:https://zh.wikipedia.org/wiki/Java%E6%B3%A8%E8%A7%A3
  • 注解基础与高级应用:http://linbinghe.com/2017/ac8515d0.html
  • 秒懂注解:https://blog.csdn.net/briblue/article/details/73824058 

概述

  这篇文章参考了很多其他文章的写作思路和篇章内容,主要用来帮助我们更好的理解Java中注解的使用,解开注解的神秘面纱。

  维基百科上对注解的解释是这样的:

  Java注解又称Java标注,是Java语言5.0版本开始支持加入源代码的特殊语法元数据[1]。Java语言中的类、方法、变量、参数和包等都可以被标注。和Javadoc不同,Java标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码Java虚拟机可以保留标注内容,在运行时可以获取到标注内容[2]。 当然它也支持自定义Java标注[3]

  我们可以这样理解,就是我们在类、方法、变量、参数等元素上面贴一个标签,并且我们能够在运行时动态的获取到这些标签

 

  我们在方法上贴了一个名为RoleCheck的标签,它里面有一个标签的描述信息为level。在运行该方法时,我们同时可以获取到这个标签及里面的描述信息。具体的语法和更多的内容我们将会在下面的文章中分享到。

内置注解

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

作用在代码的注解是

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

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

  • @Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问
    • SOURCE:注解将被编译器丢弃
    • CLASS:注解在class文件中可用,但会被JVM丢弃
    • RUNTIME:JVM将在运行期间保留注解,因此可以通过反射机制读取注解的信息。
  • @Documented - 标记这些注解是否包含在用户文档中。
  • @Target - 标记这个注解应该是哪种 Java 成员
  • @Inherited - 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)

关于Target:

你可以这样理解,当一个注解被 @Target 注解时,这个注解就被限定了运用的场景

类比到标签,原本标签是你想张贴到哪个地方就到哪个地方,但是因为 @Target 的存在,它张贴的地方就非常具体了,比如只能张贴到方法上、类上、方法参数上等等。@Target 有下面的取值

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

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

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

自定义注解

  内置注解我们可以直接用到就只有lang中的几个,annotation中几个注解的都是作用在其他注解上的,我们称之为元注解其他注解就是我们的自定义注解

定义注解

  使用@interface自定义注解,会自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。

  在定义注解时,不能继承其他的注解或接口

  @interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。

public @interface RoleCheck {
    int[] level() default 0;
}

  比如上面这个代码中,我们定义一个名叫RoleCheck的注解(标签),和一个名为level配置参数(一个标签描述信息,是数值型的),并且默认值为0

描述注解

  但是现在这个注解并不能直接使用,因为我们还没有用元注解去描述这个注解。

  1.这个注解在哪里保存呢?

    @Retention,当然是RUNTIME:JVM将在运行期间保留注解,因此可以通过反射机制读取注解的信息。

  2.这个注解的运用场景是哪里?也就是说它被写在哪里?

    @Target,我们选择 ElementType.METHOD ,它可以给方法进行注解。

  3.用不用@Document?

    @Documented 表示含有该注解类型的元素(带有注释的)会通过javadoc或类似工具进行文档化,Documented是一个标记注解(类似@Override 这种只需要一个简单的声明即可的注解即为标记注解),没有成员。我们这里用或不用都可以,因为我们不涉及文档化处理,但是还是写上了。

  最终注解将变成如下的样子:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RoleCheck {
    int[] level() default 0;
}

 

说明:

  如果只有一个参数成员,最好把参数名称设为”value”,后加小括号。注解在只有一个元素且该元素的名称是value的情况下,在使用注解的时候可以省略“value=”,直接写需要的值即可。也就是下面这样。

public @interface RoleCheck {
    int value() default 0;
}
//用的时候,不用写参数名称了,直接写值
@RoleCheck(1)
public void doSomeThing()
{
  .....  
}

使用注解

//注解没有属性
@RoleCheck
public void do(){.....}

//注解只有一个参数为value的属性
@RoleCheck(1)
public void do(){.....}

//注解有一到多个属性
@RoleCheck(level=1,name="MS")
public void do(){.....}

注解处理器

  看完以上的内容,我们其实只是做了一件事,就是给不同的物件贴上标签,但是只贴上标签,如果我们不会根据标签进行处理的话,就毫无意义了,和注释毫无区别

  注解处理器就是我们的处理环节,注解处理器可以在运行时通过反射机制读取到标签的信息从而进行处理

1、首先可以通过 Class 对象的 isAnnotationPresent() 方法判断它是否应用了某个注解

public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}

2、然后通过 getAnnotation() 方法来获取 Annotation 对象。

public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {}

或者是 getAnnotations() 方法。

public Annotation[] getAnnotations() {}

前一种方法返回指定类型的注解,后一种方法返回注解到这个元素上的所有注解

一个实例

  我们做的事情是什么,通过class对象的 isAnnotationPresent()判断我Test类是不是有@RoleCheck注解,如果有的话就把这个注解的描述信息打印出来,这里打印出的是默认值。

@RoleCheck
public class Test {

    public static void main(String[] args) {

        boolean hasAnnotation = Test.class.isAnnotationPresent(RoleCheck.class);

        if ( hasAnnotation ) {
            RoleCheck testAnnotation = Test.class.getAnnotation(RoleCheck.class);
            System.out.println("id:"+roleCheck.level());
        }
    }

}

  上面的例子中,只是检阅出了注解在类上的注解,其实属性、方法上的注解照样是可以的。同样还是基于反射。

     //返回HelloController类中所有定义的方法
        Method[] methods = HelloController.class.getDeclaredMethods();
        //遍历每一个方法
        for(Method method:methods)
        {
            //对于标有注解,且名称为doSomething的方法进行处理
            RoleCheck roleCheck =method.getAnnotation(RoleCheck.class);
            if(roleCheck!=null&&method.getName().equals("doSomething"))
            {
                //..........
            }
        }
 **********************************************************       
        //返回HelloController类中所有定义的字段
        Field[] fields = HelloController.class.getDeclaredFields();
        //遍历每一个字段
        for(Field field :fields)
        {
            RoleCheck roleCheck = field.getAnnotation(RoleCheck.class);
            if(roleCheck!=null&&field.getName().equals("level"))
            {
                //.......
            }
        }

注解的使用场景

  当开发者使用了Annotation 修饰了类、方法、Field 等成员之后,这些 Annotation 不会自己生效,必须由开发者提供相应的代码来提取并处理 Annotation 信息。这些处理提取和处理 Annotation 的代码统称为 APT(Annotation Processing Tool)。我们可以给自己答案了,注解有什么用?给谁用?给 编译器或者 APT 用

  比如举一个注解使用实例,JUNIT测试框架:

public class ExampleUnitTest {
    @Test
    public void addition_isCorrect() throws Exception {
        assertEquals(4, 2 + 2);
    }
}

  @Test 标记了要进行测试的方法 addition_isCorrect().

  再举一个注解使用实例,SpringMVC

@RequestMapping(value = "/add.do")
public String addOrder(Order order) {
        try {
            orderService.add(order);
            return "操作成功";
        } catch (Exception e) {
            e.printStackTrace();
            return "操作失败,请重试";
        }
}

  @RequestMapping表明了要进行地址映射的方法。

 

posted @ 2018-11-03 15:00  子烁爱学习  阅读(556)  评论(0编辑  收藏  举报