java自定义注解

来源:http://blog.csdn.net/beyond667/article/details/9228121

小序:一直想篇关于注解的文章,又怕写不好贻笑大方,几年前第一次接触注解还认为没xml好用,因为书本上写着注解还不能完全替代xml,自认为只能算是一种简化。以前还担心就配个@interface会不会有什么冲突的地方,会不会不能胜任近乎苛求的需求 ,慢慢在项目中用到,见的多了也就感觉,哇,原来注解太方便了。注意,这片文章不是写关于Hibernate或者spring的注解的,只是介绍下注解的原理以及自定义的注解怎么用,现在就开始。

Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。包含在 java.lang.annotation 包中注解的定义和接口差不多,只是在interface前面多了一个“@”。形象地说注解只是做了如下声明:大家好,代码这里用了这个注解,请大家多多关照。 然后呢,下面那里用到注解就关照下该怎么处理。

  1. public @interface MyAnnotation  
  2. {}  
public @interface MyAnnotation
{}
上面的代码是一个最简单的注解。这个注解没有属性。也可以理解为是一个标记注解。就象Serializable接口一样是一个标记接口,里面未定义任何方法。
当然,也可以定义而有属性的注解。
  1. public @interface MyAnnotation  
  2. {    String value();}  
public @interface MyAnnotation
{    String value();}
可以按如下格式使用MyAnnotation
  1. @MyAnnotation(“abc”)  
  2. public void myMethod()  
  3. {}  
@MyAnnotation(“abc”)
public void myMethod()
{}
看了上面的代码,大家可能有一个疑问。怎么没有使用value,而直接就写”abc”了。那么”abc”到底传给谁了。其实这里有一个约定。如果没有写属性名的值,而这个注解又有value属性,就将这个值赋给value属性,如果没有,就出现编译错误。
除了可以省略属性名,还可以省略属性值。这就是默认值。
  1. public @interface MyAnnotation  
  2. {    public String myMethod() default "xyz";}  
public @interface MyAnnotation
{    public String myMethod() default "xyz";}
可以直接使用MyAnnotation
  1. @MyAnnotation // 使用默认值xyz   
  2. public void myMethod()  
  3. {}  
@MyAnnotation // 使用默认值xyz
public void myMethod()
{}
也可以这样使用
  1. @MyAnnotation(myMethod=”abc”)  
  2. public void myMethod()  
  3. {}  
@MyAnnotation(myMethod=”abc”)
public void myMethod()
{}
如果要使用多个属性的话。可以参考如下代码。
  1. public @interface MyAnnotation  
  2. {  
  3.     public enum MyEnum{A, B, C}  
  4.     public MyEnum value1();  
  5.     public String value2();  
  6.   
  7. }  
public @interface MyAnnotation
{
    public enum MyEnum{A, B, C}
    public MyEnum value1();
    public String value2();

}
  1. @MyAnnotation(value1=MyAnnotation.MyEnum.A, value2 = “xyz”)  
  2. public void myMethod()  
  3. {}  
@MyAnnotation(value1=MyAnnotation.MyEnum.A, value2 = “xyz”)
public void myMethod()
{}
如果仅仅这样配自定义的注解还差一点,为这些注解再声明一下,这就要讲到元注解

元注解是指注解的注解。包括  @Retention @Target @Document @Inherited四种。

@Target:定义注解的作用目标
其定义的源码为:   

  1. @Documented    
  2.    @Retention(RetentionPolicy.RUNTIME)    
  3.    @Target(ElementType.ANNOTATION_TYPE)    
  4.    public @interface Target {    
  5.        ElementType[] value();    
  6.    }    
 @Documented  
    @Retention(RetentionPolicy.RUNTIME)  
    @Target(ElementType.ANNOTATION_TYPE)  
    public @interface Target {  
        ElementType[] value();  
    }  
@Target(ElementType.TYPE)   //接口、类、枚举、注解
@Target(ElementType.FIELD) //字段、枚举的常量
@Target(ElementType.METHOD) //方法
@Target(ElementType.PARAMETER) //方法参数
@Target(ElementType.CONSTRUCTOR)  //构造函数
@Target(ElementType.LOCAL_VARIABLE)//局部变量
@Target(ElementType.ANNOTATION_TYPE)//注解
@Target(ElementType.PACKAGE) ///包   
 由以上的源码可以知道,他的elementType 可以有多个,一个注解可以为类的,方法的,字段的等等

@Retention: 定义注解的保留策略
@Retention(RetentionPolicy.SOURCE)   //注解仅存在于源码中,在class字节码文件中不包含
@Retention(RetentionPolicy.CLASS)     // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得
@Retention(RetentionPolicy.RUNTIME)  // 注解会在class字节码文件中存在,在运行时可以通过反射获取到


Documented
这个注解和它的名子一样和文档有关。在默认的情况下在使用javadoc自动生成文档时,注解将被忽略掉。如果想在文档中也包含注解,必须使用Documented为文档注解。

  1. @interface MyAnnotation{ }  
  2. @MyAnnotation  
  3. class Class1{public void myMethod() { }}  
@interface MyAnnotation{ }
@MyAnnotation
class Class1{public void myMethod() { }}
使用javadoc为这段代码生成文档时并不将@MyAnnotation包含进去。生成的文档对Class1的描述如下:
 class Class1 extends java.lang.Object
而如果这样定义MyAnnotation将会出现另一个结果。
  1. @Documented  
  2. @interface MyAnnotation {}  
@Documented
@interface MyAnnotation {}
生成的文档:
@MyAnnotation // 这行是在加上@Documented后被加上的
class Class1 extends java.lang.Object

Inherited
继承是java主要的特性之一。在类中的protected和public成员都将会被子类继承,但是父类的注解会不会被子类继承呢?很遗憾的告诉大家,在默认的情况下,父类的注解并不会被子类继承。如果要继承,就必须加上Inherited注解。
  1. @Inherited  
  2. @interface MyAnnotation { }  
  3. @MyAnnotation  
  4. public class ParentClass {}  
  5. public class ChildClass extends ParentClass { }  
@Inherited
@interface MyAnnotation { }
@MyAnnotation
public class ParentClass {}
public class ChildClass extends ParentClass { }
在以上代码中ChildClass和ParentClass一样都已被MyAnnotation注解了。

下面来分析一段网上自定义的注解,来了解下怎么用的,因为总不能把项目上的代码搞过来让大家分析。这段代码我跑了一下没问题。

HelloWord.class

  1. @Documented  
  2. @Retention(RetentionPolicy.RUNTIME)  
  3. @Target(ElementType.METHOD)  
  4. @Inherited  
  5. public @interface HelloWorld {  
  6.    public String name()default "我";  
  7. }  
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
public @interface HelloWorld {
   public String name()default "我";
}
YTS.class

  1. @Documented  
  2. @Target({ElementType.TYPE,ElementType.METHOD})  
  3. @Retention(RetentionPolicy.RUNTIME)  
  4. public @interface YTS {  
  5.     public enum YtsType{util,entity,service,model};  
  6.    public YtsType classType() default YtsType.util;  
  7. }  
@Documented
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface YTS {
    public enum YtsType{util,entity,service,model};
   public YtsType classType() default YtsType.util;
}
处理定义这两个注解的类ParseAnnotation.class
  1. public class ParseAnnotation {  
  2.     
  3.     public void parseMethod(Class clazz) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException, InstantiationException{  
  4.   
  5.        Object obj = clazz.getConstructor(new Class[]{}).newInstance(new Object[]{});  
  6.     for(Method method : clazz.getDeclaredMethods()){  
  7.         HelloWorld say = method.getAnnotation(HelloWorld.class);  
  8.         String name = "";  
  9.         if(say != null){  
  10.            name = say.name();  
  11.            method.invoke(obj, name);  
  12.         }  
  13.        YTS yts = (YTS)method.getAnnotation(YTS.class);  
  14.        if(yts != null){  
  15.           if(YtsType.util.equals(yts.classType())){  
  16.           System.out.println("this is a util method");  
  17.         }else{  
  18.             System.out.println("this is a other method");  
  19.            }  
  20.        }  
  21.      }  
  22.    }  
  23.    @SuppressWarnings("unchecked")  
  24.    public void parseType(Class clazz) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException{  
  25.     YTS yts = (YTS) clazz.getAnnotation(YTS.class);  
  26.        if(yts != null){  
  27.            if(YtsType.util.equals(yts.classType())){  
  28.                System.out.println("this is a util class");  
  29.            }else{  
  30.                System.out.println("this is a other class");  
  31.            }  
  32.        }  
  33.    }  
  34.      
  35. }  
public class ParseAnnotation {
  
    public void parseMethod(Class clazz) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException, InstantiationException{

       Object obj = clazz.getConstructor(new Class[]{}).newInstance(new Object[]{});
    for(Method method : clazz.getDeclaredMethods()){
        HelloWorld say = method.getAnnotation(HelloWorld.class);
        String name = "";
        if(say != null){
           name = say.name();
           method.invoke(obj, name);
        }
       YTS yts = (YTS)method.getAnnotation(YTS.class);
       if(yts != null){
          if(YtsType.util.equals(yts.classType())){
          System.out.println("this is a util method");
        }else{
            System.out.println("this is a other method");
           }
       }
     }
   }
   @SuppressWarnings("unchecked")
   public void parseType(Class clazz) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException{
    YTS yts = (YTS) clazz.getAnnotation(YTS.class);
       if(yts != null){
           if(YtsType.util.equals(yts.classType())){
               System.out.println("this is a util class");
           }else{
               System.out.println("this is a other class");
           }
       }
   }
   
}
测试类SayHell.class

  1. @YTS(classType =YtsType.util)  
  2. public class SayHell {  
  3.   
  4.     @HelloWorld(name = " 小明 ")  
  5.     @YTS  
  6.     public void sayHello(String name){  
  7.         if(name == null || name.equals("")){  
  8.             System.out.println("hello world!");  
  9.         }else{  
  10.             System.out.println(name + "say hello world!");  
  11.         }  
  12.     }  
  13.       
  14.     public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException, InstantiationException {  
  15.         ParseAnnotation parse = new ParseAnnotation();  
  16.         parse.parseMethod(SayHell.class);  
  17.         parse.parseType(SayHell.class);  
  18.        
  19.        
  20.     }  
  21. }  
@YTS(classType =YtsType.util)
public class SayHell {

    @HelloWorld(name = " 小明 ")
    @YTS
    public void sayHello(String name){
        if(name == null || name.equals("")){
            System.out.println("hello world!");
        }else{
            System.out.println(name + "say hello world!");
        }
    }
    
    public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException, InstantiationException {
        ParseAnnotation parse = new ParseAnnotation();
        parse.parseMethod(SayHell.class);
        parse.parseType(SayHell.class);
     
     
    }
}
打印结果:

  1. 小明 say hello world!  
  2. this is a util method  
  3. this is a util class  
小明 say hello world!
this is a util method
this is a util class

下面分析这段代码:可以看到@yts这注解可以定义到类也可以定义到方法上。首先掉parse.parseMethod(SayHell.class)这个方法,此方法里先

  1. clazz.getDeclaredMethods()  
clazz.getDeclaredMethods()
通过反射来获取list<Method>,即这个类的全部方法,然后遍历这些方法,看哪个上面配置了@yts和@HelloWord的注解,如果配置helloWord的注解,将会把helloword的name属性作为参数来执行这个方法,在这里因为sayhello方法上配了@HelloWord,所以会执行这个方法,输出 小明 sayhello word。当然,如果这里不传name=小明那将会按默认的‘我’来作为参数,输出 我 sayhello word.后面的同理就不分析了.

通过上面的大家基本上能了解自定义注解是怎么用的。下面说下鄙人对自定义注解的用处的一些心得。(实际项目中用到的地方)

1,假如现在要批量保存100条数据,怎样保证里面有不重复的数据呢?当然我们可以用hibernate的@UniqueConstraint,参照语法如下:

  1. @Table(name = "contact", uniqueConstraints = {  
  2.   @UniqueConstraint(columnNames = { "name""email" }  
  3.       )})  
@Table(name = "contact", uniqueConstraints = {
  @UniqueConstraint(columnNames = { "name", "email" }
      )})
这里只能保证get出来的name和email来确定唯一数据,但是save时呢?我们知道,要保证两个对象不相同就要重写equals和hashcode方法,所以一般来说我们会重写这两个对象的这两个方法来保证对象的唯一性,这样在save的时候就可以解决这问题。但是问题又来了,你总不会每个对象都重写一次吧,多麻烦啊,所以我们就可以考虑自定义注解来解决这个问题。思路如下,代码不提供。首先我们可以先自定义个注解假如叫@AAA,然后写个抽象类来获取配@AAA注解的地方,通过equalsBuilder 这个工具把某个类所有配AAA的属性都get到,然后append到equalsBuilder里,通过它的isequal() 和toHashCode() 来就可以轻松判断。这样,以后你所配的vo的field上面,只要是要保证唯一的就可以加这个注解。不用再一个对象一个对象的重写equals和HashCode,是不是很方便呢?

2,配缓存的时候。我们知道,hibernate 提供了ehcache来支持二级缓存,ehcache二级缓存我会在以后仔细讲。因为这个缓存hibernate只提供了@cache这个注解来定义某个VO,一般情况下这满足了我们的需求,但是我们项目有个特殊的地方,比如要掉另外一个系统的东西,封装写参数传过去,再把返回的东西封装下,这个过程可能需要花很多时间,而这个如果要用ehcache做缓存就比较麻烦一点,如下:

  1. // 使用默认配置文件创建CacheManager   
  2. CacheManager manager =CacheManager.create();  
  3. // 通过manager可以生成指定名称的Cache对象   
  4. Cache cache = cache =manager.getCache("demoCache");  
  5.   
  6. //往cache中添加元素   
  7. Element element = new Element("key""value");  
  8. cache.put(element);  
// 使用默认配置文件创建CacheManager
CacheManager manager =CacheManager.create();
// 通过manager可以生成指定名称的Cache对象
Cache cache = cache =manager.getCache("demoCache");

//往cache中添加元素
Element element = new Element("key", "value");
cache.put(element);

看到了吧,每一个response就必须通过这种方式一个一个put进某个cache里,是不是很不人性化?

所以解决思路也就来了,我们就自定义某个注解,比如还是@aaa,通过spring 的aop面向切面 @around(“@annotation(com.aaa)”) 来获取到配置这个注解的类(这种写法是不是很另类呢,),然后request作为key response作为value放入Element,当然 放入之前以这个key 看缓存里有没有在put进去,要不就失去缓存的意义了。


综上,大家可以看到,自定义注解还是有很多很多地方能用到的,不仅仅是这点,根据实际情况实际需求来自定义注解,一定会让你的代码更简洁,好用。

希望这篇文章能让大家学到点。因为之前学自定义注解,看网上都是千篇一律的抄袭,你无法想象打开20个网页都是一样的时候的心情,特别记录于此,以飨读者。


原文地址:https://www.cnblogs.com/IntelligentBrain/p/5111316.html