java强大灵活的注解学习笔记

一、什么是注解

在Java代码中使用注释是为了提升代码的可读性,也就是说注释是给人看的(对于编译器来说没有意义)。注解可以看做是注释的“强力升级版”,它可以向编译器、虚拟机等传递一些信息(也就是说注解对编译器等工具也是“可读”的)。比如我们非常熟悉的@Override注解,它的作用是告诉编译器它所注解的方法是重写的父类中的方法,这样编译器就会去检查父类是否存在这个方法,以及这个方法的签名与父类是否相同。

注解是描述Java代码的代码,它能够被编译器解析,注解处理工具在运行时能够解析注解。

注解功能:

  • 标记,用于告诉编译器一些信息
  • 编译时动态处理,如动态生成代码
  • 运行时动态处理,如得到注解信息

注解的语法和定义形式
(1)以@interface关键字定义
(2)注解包含成员,成员以无参数的方法的形式被声明。其方法名和返回值定义了该成员的名字和类型。
(3)成员赋值是通过@Annotation(name=value)的形式。
(4)注解需要标明注解的生命周期,注解的修饰目标等信息,这些信息是通过元注解实现。

@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = { ElementType.ANNOTATION_TYPE } )
public @interface Target
{
    ElementType[] value();
}

元注解Target 源码分析如下:
第一:元注解@Retention,成员value的值为RetentionPolicy.RUNTIME。
第二:元注解@Target,成员value是个数组,用{}形式赋值,值为ElementType.ANNOTATION_TYPE
第三:成员名称为value,类型为ElementType[]
另外,需要注意一下,如果成员名称是value,在赋值过程中可以简写。如果成员类型为数组,但是只赋值一个元素,则也可以简写。如上面的简写形式为:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)

可以使用注解来描述我们的意图,然后让注解解析工具来解析注解,以此来生成一些”模板化“的代码。比如Hibernate、Spring等框架大量使用了注解,来避免一些重复的工作。注解是一种”被动“的信息,必须由编译器或虚拟机来“主动”解析它,它才能发挥自己的作用

二、元注解

元注解用于定义注解时用来描述注解的注解,比如以下代码中我们使用“@Target”元注解来说明MethodInfo这个注解只能用于对方法进行注解:

@Target(ElementType.METHOD)
    public @interface MethodInfo { 
        ...
    }   

下面我们来具体介绍一下几种元注解。
1. Retention
这个元注解表示一个注解的生命周期,有源码阶段,编译器阶段,运行阶段,比如以下代码表示Developer注解会被保留到运行时(也就是说在运行时依然能发挥作用):

@Retention(RetentionPolicy.RUNTIME)
public @interface Developer {
    String value();
}

@Retention元注解的定义如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

我们在使用@Retention时,后面括号里的内容即表示它的取值,从以上定义我们可以看到,取值的类型为RetentionPolicy,这是一个枚举类型,它可以取以下值:

  • SOURCE:表示注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;
  • CLASS:表示注解被保留到class文件,jvm加载class文件时候被遗弃。这是默认的生命周期。
  • RUNTIME:表示注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在,保存到class对象中,可以通过反射来获取。

我们从以上代码中可以看到,定义注解使用@interface关键字,这就好比我们定义类时使用class关键字,定义接口时使用interface关键字一样,注解也是一种类型。关于@Documented和@Target的含义,下面进行介绍。

  1. Documented

当一个注解被@Documented元注解所修饰时,那么无论在哪里使用这个注解,都会被Javadoc工具文档化。我们来看一下它的定义:

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

这个元注解被@Documented修饰,表示它本身也会被文档化。@Retention元注解的值RetentionPolicy.RUNTIME表示Documented这个注解能保留到运行时;@Target元注解的值ElementType.ANNOTATION_TYPE表示Documented这个注解只能够用来修饰注解类型。

  1. Inherited

表明被修饰的注解类型是自动继承的。如果你想让一个类和它的子类都包含某个注解,就可以使用@Inherited来修饰这个注解。也就是说,假设Parent类是Child类的父类,那么我们若用被@Inherited元注解所修饰的某个注解对Parent类进行了修饰,则相当于Child类也被该注解所修饰了。这个元注解的定义如下:


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

我们可以看到这个元注解类型被@Documented所注解,能够保留到运行时,只能用来修饰注解类型。被注解的类他的子类自动被注解修饰

  1. Target

这个元注解说明了被修饰的注解的应用对象(类,方法,字段,形参,构造函数,接口, 包等),也就是被修饰的注解可以用来注解哪些程序元素,它的定义如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)public @interface Target {
    ElementType[] value();
}

从以上定义我们可以看到它也会保留到运行时,而且它的取值是为ElementType[]类型(一个数组,意思是可以指定多个值),ElementType是一个枚举类型,它可以取以下值:

  • TYPE:表示可以用于类、接口、注解类型或枚举类型上的注解;
  • PACKAGE:可以用于包上的注解;
  • PARAMETER:可以用于参数的注解;
  • ANNOTATION_TYPE:可以用来修饰注解类型;
  • METHOD:可以用来修饰方法的注解;
  • FIELD:可以用来修饰属性(包括枚举常量)注解;
  • CONSTRUCTOR:可以用来修饰构造器的注解;
  • LOCAL_VARIABLE:可用来修饰局部变量。

三、常见内建注解

Java本身内建了一些注解,下面我们来介绍一下我们在日常开发中比较常见的注解:@Override、@Deprecated、@SuppressWarnings。相信我们大家或多或少都使用过这三个注解,下面我们一起再重新认识一下它们。

  1. @Override注解

我们先来看一下这个注解类型的定义:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

从它的定义我们可以看到,这个注解只可以用来修饰方法,并且它只在编码时有效,一旦进入编译期就失效,在编译后的class文件中便不再存在。这个注解的作用我们大家都不陌生,那就是告诉编译器被修饰的方法是重写的父类中的相同签名的方法,编码时编译器会对此做出检查,若发现父类中不存在这个方法或是存在的方法签名不同,则会报错。

  1. @Deprecated

    这个注解的定义如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

从它的定义我们可以知道,它会被文档化,能够保留到运行时,能够修饰构造方法、属性、局部变量、方法、包、参数、类型。这个注解的作用是说明被修饰的程序元素已被“废弃”,不再建议用户使用。

  1. @SuppressWarnings

这个注解我们也比较常用到,先来看下它的定义:

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

它能够修饰的程序元素包括类型、属性、方法、参数、构造器、局部变量,只能存活在源码时,取值为String[]。只在源码中有效;它的作用是告诉编译器忽略指定的警告信息,它可以取的值如下所示:

  • deprecation:忽略使用了废弃的类或方法时的警告;
  • unchecked:执行了未检查的转换;
  • fallthrough:swich语句款中case忘加break从而直接“落入”下一个case;
  • path:类路径或原文件路径等不存在;
  • serial:可序列化的类缺少serialVersionUID;
  • finally:存在不能正常执行的finally子句;
  • all:以上所有情况产生的警告均忽略。

这个注解的使用示例如下:

注解定义时候定义了一个函数value ,使用时候给他一个字符串数组,取值可以上面这些。

@SuppressWarning(value={"deprecation", "unchecked"})
public void myMethos() {...} 

通过使用以上注解,我们告诉编译器请忽略myMethod方法中“使用了废弃的类或方法或是做了未检查的转换”而产生的警告。

四、自定义注解

我们可以创建我们自己的注解类型并使用它。请看下面的示例:

package org.vincent.maven.annotation.anno;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义注解
 * 
 * @author PengRong
 *
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
public @interface MethodInfo {

    String author() default "Vicent";//指定了默认值,也可以不指定

    String date() default "2017/03/03";

    int version() default 1;
}

在自定义注解时,有以下几点需要我们了解:

注解类型是通过”@interface“关键字定义的;
在”注解体“中声明注解成员,所有成员以无参数函数形式声明;方法名和返回值类型表示该自定义注解属性名字和属性可设置值类型;没有方法体且只允许public和abstract这两种修饰符号(不加修饰符缺省为public),注解方法不允许有throws子句;注解方法的返回值只能为以下几种:原始数据类型, String, Class, 枚举类型, 注解和它们的一维数组,可以为方法指定默认返回值。

我们再把上面提到过的@SuppressWarnings这个注解类型的定义拿出来看一下,这个注解类型是系统为我们定义好的,它的定义如下:

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

我们可以看到,它只定义了一个注解方法value(),它的返回值类型为String[],没有指定默认返回值。我们使用@SuppressWarnings这个注解所用的语法如下:

@SuppressWarnings(value={"value1", "value2", ...})也就是在注解类型名称后的括号内为每个注解方法指定返回值就可以使用这个注解。由于@SuppressWarnings只包含一个注解方法,所以我们使用时可以也简写为“@SuppressWarnings(“value1”, “value2”)”。下面我们来看看怎么使用我们自定义的注解类型@MethodInfo:

package org.vincent.maven.annotation;

import org.vincent.maven.annotation.anno.MethodInfo;

/**
 * Hello world!
 *
 */
public class App {
    @MethodInfo(author = "Cindy", date = "day", version = 2)
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

我们使用的自定义注解对于编译器或是虚拟机来说是有意义的吗(编译器或是虚拟机能读懂吗)?显然我们什么都不做的话,编译器是读不懂我们的自定义注解的。下面我们介绍一下注解的解析,让编译器或虚拟机能够读懂我们的自定义注解。

五、注解的解析

  1. 编译时解析

编译时注解指的是@Retention的值为CLASS的注解。对于这类注解的解析,我们只需做好以下两件事儿:

  • 自定义一个派生自 AbstractProcessor的“注解处理类”;
  • 重写process 函数。实际上,javac中包含的注解处理器在编译时会自动查找所有继承自 AbstractProcessor 的类,然后调用它们的 process 方法。因此我们只要做好上面两件事,编译器就会主动去解析我们的编译时注解。现在,我们把上面定义的MethodInfo的Retention改为CLASS,我们就可以按照以下代码来解析它:
package org.vincent.maven.annotation.anno;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;

//SupportedAnnotationTypes指出了MyProcessor将要解析的注解的完整名字(全限定名称)
//注释处理器支持的注释:MethodInfo
@SupportedAnnotationTypes({ "org.vincent.maven.annotation.anno.MethodInfo" })
@SupportedSourceVersion(SourceVersion.RELEASE_8) // 注释处理器支持的JDK版本:8
public class MyProcess extends AbstractProcessor {

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        // TODO Auto-generated method stub
        super.init(processingEnv);
        System.out.println(processingEnv.toString());
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

        Map<String, String> map = new HashMap<String, String>();

        for (TypeElement e : annotations) {
            MethodInfo mi = e.getAnnotation(MethodInfo.class);
            map.put(e.getEnclosingElement().toString(), mi.author());
            System.out.println(map);
            System.out.println("xxxx");
        }
        if (!roundEnv.processingOver()) {
            processingEnv.getMessager().printMessage( // 注释处理器的报告
                    Diagnostic.Kind.NOTE, "Hello World MethodInfo!");
        }
        return true;
    }

}

@SupportedAnnotationTypes注解指出了MyProcess向要解析的注解的完整名字(全限定名称)。process 函数的annotations参数表示待处理的注解集,通过env我们可以得到被特定注解所修饰的程序元素。process函数的返回值表示annotations中的注解是否被这个Processor接受。

  1. 运行时注解解析

首先我们把MethodInfo注解类型中Retention的值改回原来的RUNTIME,接下来我们介绍如何通过反射机制在运行时解析我们的自定义注解类型。

java.lang.reflect包中有一个AnnotatedElement接口,这个接口定义了用于获取注解信息的几个方法:

T getAnnotation(Class annotationClass) //返回该程序元素的指定类型的注解,若不存在这个类型的注解则返回null
Annotation[] getAnnotations() //返回修饰该程序元素的所有注解
Annotation[] getDeclaredAnnotations() //返回直接修饰该元素的所有注解
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) //当该程序元素被指定类型注解修饰时,返回true,否则返回false

解析上面自定义注解MethodInfo在使用过程中使用的参数相关示例代码如下:


package org.vincent.maven.annotation;

import java.lang.reflect.Method;

import org.vincent.maven.annotation.anno.MethodInfo;

public class AnnotationParser {

    public static void main(String[] args) {
        Class<? extends App> class1 = App.class;
        for (Method method : class1.getMethods()) {
            MethodInfo methodInfo;
            methodInfo = method.getAnnotation(MethodInfo.class);// 返回注解这个方法的指定注解
            if (methodInfo != null) {
                System.out.println(method.getName());
                System.out.println(methodInfo.author());// 获取指定注解指定方法的值
                System.out.println(methodInfo.date());
                System.out.println(methodInfo.version());

            }
        }

    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章