Java Annotation注解简述

53 阅读5分钟

0. 前言

新工作上手就是要做Spring项目,对于注解一直是比较模糊,趁此机会,学习一下注解。

1 了解注解

注解是Java5.0引入的,我们可以可以通过自定义这些标签,并通过反射机制获取类中标注的注解,完成特定功能。

注解是代码的附属信息,注解不能干扰代码的运行。即不论增加或删除注解,代码都可正常运行。Java语言解释器会忽略这些注解,而由第三方工具负责对注解进行处理,第三方可以利用代码中注解间接控制代码的运行,他们通过反射机制读取注解的信息,根据这些信息更改目标程序的逻辑。

2 一个例子

 package com.baobaotao.aspectj.anno;  
 import java.lang.annotation.ElementType;  
 import java.lang.annotation.Retention;  
 import java.lang.annotation.RetentionPolicy;  
 import java.lang.annotation.Target;  
   
 @Retention(RetentionPolicy.RUNTIME) //①声明注解的保留期限  
 @Target(ElementType.METHOD)//②声明可以使用该注解的目标类型  
 public @interface NeedTest {//③定义注解  
     boolean value() default true;//④声明注解成员  
 }  
  1. 成员声明 Java新语法规定使用@interface修饰符定义注解类,如③所示,一个注解可以拥有多个成员。成员声明有如下限制:
  • 成员以无入参、无抛出异常的方式声明,如boolean value(String str)、boolean value() throws Exception等方式是非法的;
  • 可以通过default为成员指定一个默认值,如String level() default "LOW_LEVEL"、int high() default 2是合法的,当然也可以不指定默认值;
  • 成员类型是受限的,合法的类型包括原始类型及其封装类、String、Class、enums、注解类型,以及上述类型的数组类型。如ForumService value()、List foo()是非法的。
  1. 元注解:在①和②处我们看到的是Java预定义的注解,称为元注解。 @Retention(RetentionPolicy. RUNTIME)表示NeedTest这个注解可以在运行期被JVM读取,注解的保留期限类型在java.lang.annotation.Retention类中定义,介绍如下:
  • SOURCE:注解信息仅保留在目标类代码的源码文件中,但对应的字节码文件将不再保留;
  • CLASS:注解信息将进入目标类代码的字节码文件中,但类加载器加载字节码文件时不会将注解加载到JVM中,也即运行期不能获取注解信息;
  • RUNTIME:注解信息在目标类加载到JVM后依然保留,在运行期可以通过反射机制读取类中注解信息。

@Target(ElementType.METHOD)表示NeedTest这个注解只能应用到目标类的方法上,注解的应用目标在java.lang.annotation.ElementType类中定义:

  • TYPE:类、接口、注解类、Enum声明处,相应的注解称为类型注解;
  • FIELD:类成员变量或常量声明处,相应的注解称为域值注解;
  • METHOD:方法声明处,相应的注解称为方法注解;
  • PARAMETER:参数声明处,相应的注解称为参数注解;
  • CONSTRUCTOR:构造函数声明处,相应的注解称为构造函数注解;
  • LOCAL_VARIABLE:局部变量声明处,相应的注解称为局域变量注解;
  • ANNOTATION_TYPE:注解类声明处,相应的注解称为注解类注解,ElementType. TYPE包括ElementType.ANNOTATION_TYPE;
  • PACKAGE:包声明处,相应的注解称为包注解。

如果注解只有一个成员,则成员必须命名为value(),在使用时可以忽略成员名和复制号(=),如@NeedTest(true)。

注解类拥有多个成员时,如果仅对value成员进行赋值则也可不使用赋值号,如果同时对多个成员进行赋值,则必须使用赋值号,如DeclareParents (value = "NaiveWaiter" , defaultImpl = SmartSeller.class)。

注解类可以没有成员,没有成员的注解称为标识注解,解释程序以标识注解存在与否进行相应的处理。

此外,所有的注解类都隐式继承于java.lang.annotation.Annotation,但注解不允许显式继承于其他的接口。

3 使用注解

 package com.baobaotao.aspectj.anno;  
 public class ForumService {  
     @NeedTest(value=true) ①  
     public void deleteForum(int forumId){  
         System.out.println("删除论坛模块:"+forumId);  
     }  
     @NeedTest(value=false) ②  
     public void deleteTopic(int postId){  
         System.out.println("删除论坛主题:"+postId);  
     }     
 }

由于NeedTest注解的保留限期是RetentionPolicy.RUNTIME类型,因此当ForumService被加载到JVM时,仍就可通过反射机制访问到ForumService各方法的注解信息。

如果注解类和目标类不在同一个包中,需要通过import引用的注解类。

如果成员是数组类型,可以通过 {} 进行赋值,如boolean数组的成员可以设置为{true,false,true}。下面是几个注解标注的例子:

e.g. 1. 多成员注解:

 @AnnoExample(id= 2868724, synopsis = "Enable time-travel",  
 engineer = "Mr. Peabody", date = "4/1/2007")  

e.g. 2. 一个成员的注解,成员名为value。可以省略成员名和赋值符号:

 @Copyright("2011 bookegou.com All Right Reserved")  

e.g. 3. 无成员的注解:

 @Override

e.g. 4. 成员为字符串数组的注解:

 @SuppressWarnings(value={"unchecked","fallthrough"})  

e.g. 5. 成员为注解数组类型的注解:

 @Reviews({@Review(grade=Review.Grade.EXCELLENT,reviewer="df"),        
            @Review(grade=Review.Grade.UNSATISFACTORY,reviewer="eg",                
                     comment="This method needs an @Override annotation")})  

@Reviews注解拥有一个@Review注解数组类型的成员,@Review注解类型有三个成员,其中reviewer、comment都是String类型,但comment有默认值,grade是枚举类型的成员。

4 访问注解

在JDK5.0里,Package、Class、Constructor、Method以及Field等反射对象都新增了访问注解信息的方法:<T extends Annotation>T getAnnotation(Class<T> annotationClass),该方法支持通过泛型直接返回注解对象。

 package com.baobaotao.aspectj.anno;  
 import java.lang.reflect.Method;  
 public class TestTool {  
     public static void main(String[] args) {  
                   
                //①得到ForumService对应的Class对象  
         Class clazz = ForumService.class;   
   
                 //②得到ForumSerivce对应的Method数组  
         Method[] methods = clazz.getDeclaredMethods();   
   
         System.out.println(methods.length);  
         for (Method method : methods) {  
   
                         //③获取方法上所标注的注解对象  
             NeedTest nt = method.getAnnotation(NeedTest. class);  
             if (nt != null) {  
                 if (nt.value()) {  
                     System.out.println(method.getName() + "()需要测试");  
                 } else {  
                     System.out.println(method.getName() + "()不需要测试");  
                 }  
             }  
         }  
     }  
 }

在③处,通过方法的反射对象,我们获取了方法上所标注的NeedTest注解对象,接着就可以访问注解对象的成员,从而得到ForumService类方法的测试需求。运行以上代码,输出以下的信息:

deleteForum()需要测试
deleteTopic()不需要测试

5 参考

跟着开涛学Spring系列教程..www.iteye.com/blog/user/j…