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