论坛首页 Java企业应用论坛

实战篇:设计自己的Annotation

浏览 54676 次
该帖已经被评为精华帖
作者 正文
   发表时间:2006-11-30  
OO
   Annotation在java的世界正铺天盖地展开,有空写这一篇简单的annotations的文章,算是关于Annotation入门的文章吧,希望能各位们能抛砖,共同学习......
   不讲废话了,实践才是硬道理.

   第一部分:了解一下java1.5起默认的三个annotation类型:
   一个是@Override:只能用在方法之上的,用来告诉别人这一个方法是改写父类的。
   一个是@Deprecated:建议别人不要使用旧的API的时候用的,编译的时候会用产生警告信息,可以设定在程序里的所有的元素上.
   一个是@SuppressWarnings:这一个类型可以来暂时把一些警告信息消息关闭.
   如果不清楚上面三个类型的具体用法,各位可以baidu或google一下的,很简单的。

   第二部分:讲一下annotation的概念先,再来讲一下怎样设计自己的annotation.
   首先在jdk自带的java.lang.annotation包里,打开如下几个源文件:
  
   1、源文件Target.java
   @Documented
   @Retention(RetentionPolicy.RUNTIME)
   @Target(ElementType.ANNOTATION_TYPE)
   public @interface Target {
      ElementType[] value();
   }

  
   其中的@interface是一个关键字,在设计annotations的时候必须把一个类型定义为@interface,而不能用class或interface关键字(会不会觉得sun有点吝啬,偏偏搞得与interface这么像).
  
   2、源文件Retention.java
   @Documented
   @Retention(RetentionPolicy.RUNTIME)
   @Target(ElementType.ANNOTATION_TYPE)
   public @interface Retention {
      RetentionPolicy value();
   }


   看到这里,大家可能都模糊了,都不知道在说什么,别急,往下看一下.
   在上面的文件都用到了RetentionPolicy,ElementType这两个字段,你可能就会猜到这是两个java文件.的确,这两个文件的源代码如下:
  
   3、源文件RetentionPolicy.java
    public enum RetentionPolicy {
     SOURCE,
     CLASS,
     RUNTIME
    }

   这是一个enum类型,共有三个值,分别是SOURCE,CLASS 和 RUNTIME.
   SOURCE代表的是这个Annotation类型的信息只会保留在程序源码里,源码如果经过了编译之后,Annotation的数据就会消失,并不会保留在编译好的.class文件里面。
   ClASS的意思是这个Annotation类型的信息保留在程序源码里,同时也会保留在编译好的.class文件里面,在执行的时候,并不会把这一些信息加载到虚拟机(JVM)中去.注意一下,当你没有设定一个Annotation类型的Retention值时,系统默认值是CLASS.
   第三个,是RUNTIME,表示在源码、编译好的.class文件中保留信息,在执行的时候会把这一些信息加载到JVM中去的.
  举一个例子,如@Override里面的Retention设为SOURCE,编译成功了就不要这一些检查的信息;相反,@Deprecated里面的Retention设为RUNTIME,表示除了在编译时会警告我们使用了哪个被Deprecated的方法,在执行的时候也可以查出该方法是否被Deprecated.


   4、源文件ElementType.java
   public enum ElementType {
    TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR,
    LOCAL_VARIABLE, ANNOTATION_TYPE,PACKAGE
   }

   @Target里面的ElementType是用来指定Annotation类型可以用在哪一些元素上的.说明一下:TYPE(类型), FIELD(属性), METHOD(方法), PARAMETER(参数), CONSTRUCTOR(构造函数),LOCAL_VARIABLE(局部变量), ANNOTATION_TYPE,PACKAGE(包),其中的TYPE(类型)是指可以用在Class,Interface,Enum和Annotation类型上.
   另外,从1的源代码可以看出,@Target自己也用了自己来声明自己,只能用在ANNOTATION_TYPE之上.
   如果一个Annotation类型没有指明@Target使用在哪些元素上,那么它可以使用在任何元素之上,这里的元素指的是上面的八种类型.
   举几个正确的例子:
   @Target(ElementType.METHOD)
   @Target(value=ElementType.METHOD)
   @Target(ElementType.METHOD,ElementType.CONSTRUCTOR)  
   具体参考一下javadoc文档
  
   上面一下1和2的源文件,它们都使用了@Documented,@Documented的目的就是让这一个Annotation类型的信息能够显示在javaAPI说明文档上;没有添加的话,使用javadoc生成API文档的时候就会找不到这一个类型生成的信息.
   另外一点,如果需要把Annotation的数据继承给子类,那么就会用到@Inherited这一个Annotation类型.
  
   第三部分:下面讲的设计一个最简单的Annotation例子,这一例子共用四个文件;
   1、Description.java
   package lighter.iteye.com;

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

   @Target(ElementType.TYPE)
   @Retention(RetentionPolicy.RUNTIME)
   @Documented
   public @interface Description {
       String value();
   }

  
   说明:所有的Annotation会自动继承java.lang.annotation这一个接口,所以不能再去继承别的类或是接口.
   最重要的一点,Annotation类型里面的参数该怎么设定:
   第一,只能用public或默认(default)这两个访问权修饰.例如,String value();这里把方法设为defaul默认类型.
   第二,参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和String,Enum,Class,annotations等数据类型,以及这一些类型的数组.例如,String value();这里的参数成员就为String.
   第三,如果只有一个参数成员,最好把参数名称设为"value",后加小括号.例:上面的例子就只有一个参数成员.

   2、Name.java
   package lighter.iteye.com;

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

    //注意这里的@Target与@Description里的不同,参数成员也不同
   @Target(ElementType.METHOD)
   @Retention(RetentionPolicy.RUNTIME)
   @Documented
   public @interface Name {
       String originate();
       String community();
   }


   3、JavaEyer.java
package lighter.iteye.com;

@Description("javaeye,做最棒的软件开发交流社区")
public class JavaEyer {
	@Name(originate="创始人:robbin",community="javaEye")
	public String getName()
	{
		return null;
	}
	
	@Name(originate="创始人:江南白衣",community="springside")
	public String getName2()
	{
		return "借用两位的id一用,写这一个例子,请见谅!";
	}
}

   4、最后,写一个可以运行提取JavaEyer信息的类TestAnnotation
 package lighter.iteye.com;

  import java.lang.reflect.Method;
  import java.util.HashSet;
  import java.util.Set;

  public class TestAnnotation {
	/**
	 * author lighter
	 * 说明:具体关天Annotation的API的用法请参见javaDoc文档
	 */
       public static void main(String[] args) throws Exception {
       String  CLASS_NAME = "lighter.iteye.com.JavaEyer";
       Class  test = Class.forName(CLASS_NAME);
       Method[] method = test.getMethods();
       boolean flag = test.isAnnotationPresent(Description.class);
        if(flag)
        {
        	Description des = (Description)test.getAnnotation(Description.class);
        	System.out.println("描述:"+des.value());
        	System.out.println("-----------------");
        }
        
        //把JavaEyer这一类有利用到@Name的全部方法保存到Set中去
        Set<Method> set = new HashSet<Method>();
        for(int i=0;i<method.length;i++)
        {
        	boolean otherFlag = method[i].isAnnotationPresent(Name.class);
        	if(otherFlag) set.add(method[i]);
        }
        for(Method m: set)
        {
        	Name name = m.getAnnotation(Name.class);
        	System.out.println(name.originate());
        	System.out.println("创建的社区:"+name.community());
        }
     }
}

    5、运行结果:
     描述:javaeye,做最棒的软件开发交流社区
     -----------------
    创始人:robbin
    创建的社区:javaEye
    创始人:江南白衣
     创建的社区:springside

这一篇文章写了两个小时有多啦,也算是一篇关于设计自定的Annotation的入门篇啦;另外,特在处声明,如果转载请注明出处:http://lighter.iteye.com。
参考资料:
1、http://java.sun.com/j2se/1.5.0/docs/guide/language/annotations.html
2、http://caterpillar.onlyfun.net/Gossip/
3、http://calvin.iteye.com/blog/33210
   发表时间:2006-11-30  
非常不错啊,照着做了一遍就成功了,非常易懂,但是有个疑问,如何控制一个annotation里的某些属性可以填可以不填?

难道都是必填?
0 请登录后投票
   发表时间:2006-11-30  

[quote="Norther"]非常不错啊,照着做了一遍就成功了,非常易懂,但是有个疑问,如何控制一个annotation里的某些属性可以填可以不填?
难道都是必填?
[/quote]

不是必填的,我举一个例子,可以更清楚一些,在把上面的Name.java改为

java 代码
  1. @Target(ElementType.METHOD)          
  2. @Retention(RetentionPolicy.RUNTIME)          
  3. @Documented         
  4. public @interface Name {          
  5.   String originate();          
  6.   String community();          
  7.   public enum CommunityNumber(LITTLE,ORDINARY,WELL)       
  8.   CommunityNumber  number()  default  CommunityNumber.WELL;       
  9. }   


这里的default是这一个关键字,指定默认值.(在上面文章上没有说明,谢谢Norther的提醒)
所以你可以在JavaEyer类上的方法指定用
@Name(originate="创始人:robbin",community="javaEye")或
@Name(originate="创始人:robbin",community="javaEye",number=Name.CommunityNumber.WELL)均可
这里的number的属性就不是必填的
另外,在enum CommunityNumber中,不一定非得要enum类型,用其他的合法类型也可,这里只是举一个例子而已

0 请登录后投票
   发表时间:2006-11-30  
刚刚做了一个测试,
.如果Annotation的RetentionPolicy设置为CLASS,test.isAnnotationPresent(Description.class)的返回值是false,何解?
0 请登录后投票
   发表时间:2006-11-30  
JavaVision 写道
lighter 写道
 
   第二,参数成员只能用基本类型byte,short,char,int,long,float,double,boolean和String,Enum等数据类型.例如,String value();这里的参数成员就为String.
  

The values of annotation members must be non-null compile-time constant expressions that are assignment-compatible with the declared type of the member. Allowed member types are the primitive types, String, Class, enumerated types, annotation types, and arrays of any of the above types (but not an array of arrays).

谢谢JavaVision的指点,去查了一下资料,在sun官方上有如下句子:
引用
Return types are restricted to primitives, String, Class, enums, annotations, and arrays of the preceding types.

的确,文章中少了Class,annotation数据类型,现在去改正.
我看了一些例子,大多也是用int,String,enum的,而Class,annotation的没有见过. 汗..
0 请登录后投票
   发表时间:2006-11-30  
写的很清楚。获取annotation的数据都要用反射么?
0 请登录后投票
   发表时间:2006-12-01  
写的好,清晰易懂
0 请登录后投票
   发表时间:2006-12-02  
Tin 写道
写的很清楚。获取annotation的数据都要用反射么?

到目前我只看过,获取annotation的数据要到反射才能提出数据,有没有其他方法不太清楚..
0 请登录后投票
   发表时间:2006-12-03  
ff
0 请登录后投票
   发表时间:2006-12-03  
老大你有没有好点的j2ee的项目啊,给我一点啊,有文档更好啊
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics