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。除此之外,该接口定义了注释相关的几个核心方法,如下:

Java注解解析_System

  • 因此,当获取了某个类的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 对象中提取元素的值。