01.看一个案例
- 创建自定义注解,与创建接口有几分相似,但注解需要以@开头。
@Documented
@Target(ElementType.METHOD)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotataion{
String name();
String website() default "hello";
int revision() default 1;
}
- 自定义注解中定义成员变量的规则:
- 其定义是以无形参的方法形式来声明的。即:
- 注解方法不带参数,比如name(),website();
- 注解方法返回值类型:基本类型、String、Enums、Annotation以及前面这些类型的数组类型
- 注解方法可有默认值,比如default "hello",默认website=”hello”
- 当然注解中也可以不存在成员变量,在使用解析注解进行操作时,仅以是否包含该注解来进行操作。当注解中有成员变量时,若没有默认值,需要在使用注解时,指定成员变量的值。
public class AnnotationDemo {
@MyAnnotataion(name="lvr", website="hello", revision=1)
public static void main(String[] args) {
System.out.println("I am main method");
}
@SuppressWarnings({ "unchecked", "deprecation" })
@MyAnnotataion(name="lvr", website="hello", revision=2)
public void demo(){
System.out.println("I am demo method");
}
}
- 由于该注解的保留策略为
RetentionPolicy.RUNTIME
,故可在运行期通过反射机制来使用,否则无法通过反射机制来获取。这时候注解实现的就是元数据的第二个作用:代码分析。
02.注解解析
接下来,通过反射技术来解析自定义注解。
- 关于反射类位于包java.lang.reflect,其中有一个接口AnnotatedElement,该接口主要有如下几个实现类:Class,Constructor,Field,Method,Package。除此之外,该接口定义了注释相关的几个核心方法,如下:
- 因此,当获取了某个类的Class对象,然后获取其Field,Method等对象,通过上述4个方法提取其中的注解,然后获得注解的详细信息。
public class AnnotationParser {
public static void main(String[] args) throws SecurityException, ClassNotFoundException {
String clazz = "com.lvr.annotation.AnnotationDemo";
Method[] demoMethod = AnnotationParser.class
.getClassLoader().loadClass(clazz).getMethods();
for (Method method : demoMethod) {
if (method.isAnnotationPresent(MyAnnotataion.class)) {
MyAnnotataion annotationInfo = method.getAnnotation(MyAnnotataion.class);
System.out.println("method: "+ method);
System.out.println("name= "+ annotationInfo.name() +
" , website= "+ annotationInfo.website()
+ " , revision= "+annotationInfo.revision());
}
}
}
}
- 以上仅是一个示例,其实可以根据拿到的注解信息做更多有意义的事。
03.看一个完整的案例
- 下面是一个简单的注解,它可以跟踪一个项目中的用例。程序员可以在该方法上添加注解,我们就可以计算有多少已经实现了该用例。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {
public int id();
public String description() default "没有描述";
}
- 注意:id 和 description 类似方法的定义。
- description 元素有一个 default 值,如果在注解某个方法时没有给出 description 的值,则就会使用这个默认值。
- 下面的三个方法被注解,注解的元素在使用时是名值对的形式放入注解的括号内:
public class PasswordUtils {
@UseCase(id =47,description = "password 哈哈哈防止看到")
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 = "是否包含在这个密码库中")
public boolean checkForNewPassword(List<String> prevPassword,String password) {
return !prevPassword.contains(password);
}
}
- 编写注解处理器
- 如果没有用来读取注解的工具,那么注解就不会这有用。使用注解很重要的就是创建和使用注解处理器。Java SE5 扩展了反射机制的 API,方便我们构造这种工具。同时还提供了一个外部工具 apt 帮助我们解析带有注解的 Java 源代码。
public class UseCaseTracker {
public static void trackUseCase(List<Integer> useCase,Class<?> cl) {
for (Method method : cl.getDeclaredMethods()) {
UseCase uCase = method.getAnnotation(UseCase.class);
if (uCase != null) {
System.out.println("方法上的注解信息:"+uCase.id()+" "+uCase.description());
}
}
for (Integer integer : useCase) {
System.out.println("参数:"+integer);
}
}
public static void main(String[] args) {
List<Integer> uList = new ArrayList<>();
Collections.addAll(uList, 47,48,49,50);
trackUseCase(uList, PasswordUtils.class);
}
}
- 测试结果:
方法上的注解信息:49 是否包含在这个密码库中
方法上的注解信息:48 没有描述
方法上的注解信息:47 password 哈哈哈防止看到
参数:47
参数:48
参数:49
参数:50
- 上面用到了两个反射的方法:getDeclaredMethods() 和 getAnnotation(),getAnnotation() 方法返回指定类型的注解对象,在这里使用 UseCse。如果被注解的方法上没有改类型的注解,则返回 null 值。然后我们从返回的 UseCase 对象中提取元素的值。