Thinking in Annotation
Thinking in Java这本书很久前就购买了,打算有时间看一下,因为自己的时间被自己安排的紧张,也没时间看书。黄师傅上次课程讲到了注解的使用和反射的使用,今天打算学习一下注解。该文章参考Thinking in Java的第20章Annotation。
简单介绍
注解为我们代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便的使用这些数据。
内置注解:
JavaSE5内置了3个注解
- @Override 覆盖超类中的方法,如果不小心拼写错误,或者方法签名对不上覆盖的方法,编译器会发出错误提示
- @Deprecated 如果程序员使用了注解为它的元素,编译器会发出警告信息
- @Suppress Warnings 关闭不当的警告信息,在JavaSE5之前版本,也可以使用该注解
除了这三个注解,Java还提供了四种胡姐,负责新注解的创建,我们将稍后学习。
当我们创建描述符合性质的类和接口时,一旦其中包含了重复性的工作,那就可以考虑使用注解来简化与自动化该过程,例如
在EJB中存在很多的额外工作,EJB3.0就是使用注解消除了它们。
基本语法
编译器要确保其构造路径上必须有@Test注解的定义,我们可以创建一个通过反射机制来运行testExecute()方法的工具。
package littlepage.annotation.test1; public class Testable { public void execute(){ System.out.println("Executing...."); } @Test void testExecute(){execute();} }
备注接的方法与其他方法没有区别,在这个例子中,注解@Test可以与任何修饰符作用于方法,例如public、static或者void。从语法角度看,注解的使用方式几乎与修饰符使用一模一样。
package littlepage.annotation.test1; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Test { }
除了@符号以外,@Test的定义很像一个空的接口。定义注解,你需要一些元注解(meta-annotation),如@Target和@Retention。@Target用来定义你的注解将用于什么地方(例如一个方法或者一个域),@Retention用来定义注解在哪一个级别可用,在源码中(Source),类文件中(Class)或者运行时(Runtime)
在注解中,一般会包含一些元素以某些值。当分析处理注解时,程序或者工具可利用这些值。注解的元素看起来很像接口方法,唯一区别是你可以用为其指定默认值。
没有元素的注解称为标记注解,例如上面的@Test。
下面死一个简单的注解,我们可以使用它来跟踪项目用例。
package littlepage.annotation.test1; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface UseCase { public int id(); public String description() default "no description"; }
注意,id和description类似方法的定义,由于便要一起会对id进行类型检查,因此将用例文档追踪数据库与源代码的关联是可靠的。description元素有一个默认值,如果注解某方法没有给出description的值,那么处理器会使用该元素默认值。
下面一个类中有三个方法被注解的用例
package littlepage.annotation.test1; import java.util.List; public class PasswordUtils { @UseCase(id=47,description = "Password must contain at least one numeric") public boolean validatePassword(String password){ return (password.matches("\\w*\\d\\w*")); } @UseCase(id=48) public String encryptPassword(String password){ return new StringBuilder(password).reverse().toString(); } @UseCase(id=49,description = "New passwords can't equal previously used ones") public boolean checkForNewPassword(List<String> prevPasswords,String password){ return !prevPasswords.contains(password); } }
元注解
Java目前只内置了3种标准注解,以及四种元注解。元注解负责注解其他注解。
图:详见Thinking in Java p622
编写注解处理器
如果没有用来读取注解的工具,那注解也不刽比注释更加有用。使用注解的过程中,很重要的一个部分是创建和使用注解处理器。Java SE5扩展了反射机制的API,帮助程序员构造这类工具。同时,它还提供了一个外部工具apt帮助程序员解析带有注解的Java代码。
下面是一个非常简单的注解处理器,我们将用它来读取PasswordUtils类,并使用反射机制查找@UseCase标记。我们为其提供了一组id值,然后它会列出在PasswordUtils中找到的用例,以及缺失的 用例。
package littlepage.annotation.test1; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class UseCaseTracker { public static void trackUseCases(List<Integer> useCases, Class<?> clazz){ for (Method m:clazz.getDeclaredMethods()) { UseCase uc = m.getAnnotation(UseCase.class); if (uc != null) { System.out.println("Found Use Case:" + uc.id() + " " + uc.description()); useCases.remove(new Integer(uc.id())); } } } public static void main(String[] args) { List<Integer> useCases=new ArrayList<>(); Collections.addAll(useCases,47,48,49,50); trackUseCases(useCases,PasswordUtils.class); } }
这个程序用到了两个反射的方法:getDeclaredMethods()和getAnnotation(),它们都属于AnnotatedElement接口(Class,Method和Field等类都实现了该接口)。getAnnoation()方法返回指定类型的注解对象。在这里就是UseCase。如果被注解方法上没没有该类型的猪价,则返回null值。然后我们通过调用id和description的方法从返回的对象中提取元素的值。
注解元素
标签@UeCase由UseCase.java定义,其中包含int元素id,以及一个String元素description,注解元素可用类型如下所示:
- 所有基本类型(int,float,boolean等)
- String
- Class
- enum
- Annotation
- 以上类型的数组
如果你使用其他类型,那么编译器就会报错。也不允许使用任何包装类型,不过由于自动打包机制,这个算不上限制。注解也可以作为元素类型,稍后,我们可以看到,这是一个很有用的技巧。
(浅识注解,博主学习速度太慢,需要慢慢消化 )