Java中的Annotation (二、自定义Annotation)

今天学习如何开发一个自定义的Annotation。要想使Annotation有意义,还需要借用前几天学习的反射机制。

下面就开始今天的学习吧。

Annotation的定义格式。它类似于新创建一个接口类文件,但为了区分,我们需要将它声明为 @interface

public @interface Annotation名称{

  数据类型 变量名称();

}

下面声明了一个Annotation

public @interface MyAnnotation {
    
}

使用这个Annotation

@MyAnnotation
public class AnnotationDemo{

    public static void main(String []args){
        
    }
}

注意上述Annotation没有参数,这样是没有任何意义的。要让它有意义,需要借用java的反射机制。

有内容的Annotation

public @interface MyAnnotation {
    public String value();
}

在定义Annotation的时候使用了参数,就必须在使用其的时候清楚的指明变量的内容。

@MyAnnotation("Annotation")
public class AnnotationDemo{
    public static void main(String []args){       
    }
}

或者明确赋给value

@MyAnnotation(value="Annotation")
public class AnnotationDemo{
    public static void main(String []args){        
    }
}

可以设置一个参数,也可以设置多个参数。

public @interface MyAnnotation {
    public String key();
    public String value();
}

在使用的时候,传入要接收的参数。

@MyAnnotation(key="key",value="Annotation")
public class AnnotationDemo{
    public static void main(String []args){        
    }
}

也可为一个参数传递多个值。

public String[] value(); 接收的时候以数组形式接收。 value={"Annotation","java"}

相信大家现在都跟我一样,很好奇这个自定义的Annotation里面并没有定义私有域value, public String[] value() 是一个函数,那么它是怎么接收值的呢。带着这个疑问继续学习。

上述Annotation都必须在使用的时候,给定参数。那么能不能设置默认参数呢,使用的时候使用默认值。答案是肯定的。

public @interface MyAnnotation {
    public String key() default "java";               //指定好了默认值
    public String[] value() default "Annotation";    //指定好了默认值
}

在操作中,有时会对Annotation中的参数固定其取值范围,只能取固定的几个值,这个时候,就需要java中的枚举。

public @interface MyAnnotation {
    public Color key() default Color.BLACK;               //指定好了默认值
    public Color[] value() default Color.BLUE;            //指定好了默认值
    
    //在外部使用Annotation的时候,也需要固定枚举中的值
    public enum Color{
        RED , BLUE , BLACK
    }
}

外部

//在外部使用Annotation的时候,也需要使用枚举中的值
@MyAnnotation(key=Color.RED)
public class AnnotationDemo{
    public static void main(String []args){        
    }
}


上述是对自定义注释的简单定义与使用。

在Annotation中,可以使用Retention定义一个Annotation的保存范围。

@Documented
@Retention(value=RetentionPolicy.RUNTIME)
@Target(value=ElementType.ANNOTATION_TYPE)
public @interface MyAnnotation {
    public Color key() default Color.BLACK;               //指定好了默认值
    public Color[] value() default Color.BLUE;            //指定好了默认值
    
    //在外部使用Annotation的时候,也需要固定枚举中的值
    public enum Color{
        RED , BLUE , BLACK
    }
}

1、@Documented     类和方法的Annotation缺省情况下是不出现在javadoc中的,为了加入这个性质使用@Documented

2、@Retention

Annotation的作用范围通过RetentionPolicy来指定,RetentionPolicy有三个范围:

RetentionPolicy.SOURCE     // 此Annotation类型的信息只会保存在程序原文件之中(*.java),编译之后不会保存在编译好的文件中(*.class)

RetentionPolicy.CLASS       //  此Annotation类型的信息会保存在程序原文件之中(*.java),编译之后会保存在编译好的文件中(*.class),但在执行的时候这些Annotation信息将不会加载到JVM虚拟机中,如果                一个Annotation没有指定范围,则默认是此范围

RetentionPolicy.RUNTIME    //顾名思义了,这些Annotation类型信息将会一直保存到执行时,加载到JVM虚拟中。

在这三个范围中,我们最需要关心的就是 RetentionPolicy.RUNTIME了,因为它会在执行的时候起作用。

那么前面文章中提到的系统内建的三种Annotation都是什么作用范围呢。

@Override                   定义的时候采用的是@Retention(value=RetentionPolicy.SOURCE)

@Deprecated               定义的时候采用的是@Retention(value=RetentionPolicy.RUNTIME)

@SuppressWarnings      定义的时候采用的是@Retention(value=RetentionPolicy.SOURCE)

3、@Target

@Target注解表明某个注解应用在哪些目标上,可选择如下范围,根据因英文名,相信大家都能看懂。

ElementType.TYPE (class, interface, enum)

ElementType.FIELD (instance variable)

ElementType.METHOD

ElementType.PARAMETER

ElementType.CONSTRUCTOR

ElementType.LOCAL_VARIABLE

ElementType.ANNOTATION_TYPE (应用于另一个注解上)

ElementType.PACKAGE

4、@Inherited

@Inherited 表明是否一个使用某个annotation的父类可以让此annotation应用于子类。

@Inheriated注解仅在存在继承关系的上产生效果,在接口和实现类上并不工作。这条同样也适用在方法,变量,包等等。只有类才和这个注解连用。

一条关于@Inheriated注解的很好的解释在Javadoc中

http://docs.oracle.com/javase/8/docs/api/java/lang/annotation/Inherited.html

示例代码如下,定义@Inherited

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;

@Documented
@Retention(value=RetentionPolicy.RUNTIME)            //必须指定为RUNTIME,否则不起作用
@Target({ElementType.TYPE,ElementType.METHOD,
    ElementType.CONSTRUCTOR,ElementType.ANNOTATION_TYPE,
    ElementType.PACKAGE})
@Inherited
public @interface MyAnnotation {
    public String key();               
    public String value();                
}

在Stu子类中依然可取得父类中定义的注释信息。

interface A{    
    public String sayHello();
}

@MyAnnotation(key="key",value="value")
class Per implements A{
        
    public String sayHello(){
        return "hello world";
    }
}

class Stu extends Per{
    public String sayHello(){
        return "hello world";
    }
}

public class AnnotationDemo{
    
    public static void main(String []args){        
        Class <?> c = null ;        
        c = new Stu().getClass() ;
        
        if(c.isAnnotationPresent(MyAnnotation.class)){  // 判断是否是指定的Annotation
            MyAnnotation mda = null ;
            mda = c.getAnnotation(MyAnnotation.class) ;    // 得到指定的Annotation
            String key = mda.key() ;                        // 取出设置的key
            String value = mda.value() ;                    // 取出设置的value
            System.out.println("key = " + key) ;
            System.out.println("value = " + value) ;
        }            
    }
}

下面就是我最关心的了,如何利用反射在运行时候获得注释信息。

反射与Annotation

一个Annotation要想变得有意义,就需要结合反射机制。在Class类中,有如下与Annotation相关的方法。

public <A extends Annotation> A getAnnotation(Class<A> annotationClass)       //如果一个元素存在注释,则取得其全部注释

public Annotation[] getAnnotations()                                                               //返回此元素上的所有注释

public <A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass)  //返回直接存放在此元素上的所有注释

public boolean isAnnotation()                              //判断元素是否表示一个注释

public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)     //判断一个元素上是否存在注释

等等……

下面通过代码演示如何通过反射取得Annotation

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;


interface A{
    public String sayHello();
}
class Per implements A{
    @SuppressWarnings("unchecked") 
    @Override
    @Deprecated                        //只有它在运行时有效
    public String sayHello(){
        return "hello world";
    }
}

public class AnnotationDemo{
    public static void main(String []args){        
        Class <?> c = null ;        
        c = new Per().getClass() ;
        Method toM = null;
        try {
            toM = c.getMethod("sayHello") ; // 找到sayHello()方法
        } catch (NoSuchMethodException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }    
        Annotation an[] = toM.getAnnotations() ;    // 取得全部的Annotation
        for(Annotation a:an){                        // 使用 foreach输出
            System.out.println(a) ;
        }
        
    }
}

输出结果为@java.lang.Deprecated(),这显而易见,前面已经提到了,只有@Deprecated 在运行时有效

所以要想使我们自定义的Annotation在运行时有效,必须在定义的时候指定RetentionPolicy.RUNTIME。

如何取得定义的注释信息中的值呢。

在定义的时候

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;

@Documented
@Retention(value=RetentionPolicy.RUNTIME)            //必须指定为RUNTIME,否则不起作用
@Target(value=ElementType.METHOD)
public @interface MyAnnotation {
    public String key();               
    public String value();                
}

取出注释信息中的值

import java.lang.reflect.Method;

interface A{
    public String sayHello();
}
class Per implements A{
    @MyAnnotation(key="key",value="value")
    public String sayHello(){
        return "hello world";
    }
}

public class AnnotationDemo{
    public static void main(String []args){        
        Class <?> c = null ;        
        c = new Per().getClass() ;
        Method toM = null;
        try {
            toM = c.getMethod("sayHello");
        } catch (NoSuchMethodException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }    
        if(toM.isAnnotationPresent(MyAnnotation.class)){    // 判断是否是指定的Annotation
            MyAnnotation mda = null ;
            mda = toM.getAnnotation(MyAnnotation.class) ;   // 得到指定的Annotation
            String key = mda.key() ;                        // 取出设置的key
            String value = mda.value() ;                    // 取出设置的value
            System.out.println("key = " + key) ;
            System.out.println("value = " + value) ;
        }
    
        
    }
}

通过以上代码就可以取出在注释信息中的值。

下面简单

展示一些知名类库是如何利用注解的。一些类库如:JAXB, Spring Framework, Findbugs, Log4j, Hibernate, Junit。它们使用注解来完成代码质量分析,单元测试,XML解析,依赖注入和许多其它的工作。

1、Hibernate ORM

Hibernate可能是一个用得最广泛的对象关系映射类库。它提供了对象模型和关系型数据库的映射框架。使用注解作为设计的一部分。

下面的代码段使用了@Entity和@Table。这两个是用来向消费器(Hibernate处理程序)说明被注解的类是一个实体类,以及它映射的SQL表名。实际上,这个注解仅仅是指明了主表,还可以有说明字表的注解。

@Entity
@Table( name = "hibernate_annotated" )
public class HibernateAnnotated

接下来的代码段展示了如何向Hibernate处理程序说明被标记的元素是表的主键,并映射名为“id”的列,并且主键是自动生成的。

@Id
@GeneratedValue
@Column( name = "id" )
private int id;

为了指定标准的SQL表列名,我们可以写如下注解:

@Column( name = "description" )
private String description;

这说明被标记的元素映射的是这个类所对应的表中名为“description”的一列。

2、Spring MVC

Spring是个被广泛使用的Java企业级应用框架。其一项重要的特性就是在Java程序使用依赖注入。

Spring使用注解作为XML(Spring的早期版本使用的基于XML配置)的一种替代方式。现在两者都是可行的。你可以使用XML文件或者注解配置你的项目。在我看来两者都各有优势。

我们将在下例中展示两个可用注解:

@Component
public class DependencyInjectionAnnotation
{
 
 private String description;
 
 public String getDescription()
 {
 return description;
 }
 
 @Autowired
 public void setDescription( String description )
 {
 this.description = description;
 }
 
}

代码片段中我们可以找到两个使用在了类和方法上的注解。

@Component      //说明被标记的元素,在本例中是一个类,是一个自动检测的目标。这意味着被注解的类,将会被Spring容器实例化并管理。

@AutowiredSpring    //容器将会尝试通过类型(这是一种元素匹配机制)使用这个set方法来自动装配。此注解也可以使用在构造器和属性上,Spring也会根据注解的地方不同采取不同的操作。

更多关于依赖注入和Spring框架的细节请参考:http://projects.spring.io/spring-framework/ 

总结:

Annotation在使用中,肯定是结合反射,设定一些内容到方法中去,以完成特定的功能。

资料:

官方Java注解地址http://docs.oracle.com/javase/tutorial/java/annotations/

注解API:http://docs.oracle.com/javase/8/docs/api/java/lang/annotation/package-summary.html

原文地址:https://www.cnblogs.com/maydayit/p/4237277.html