序言
开发过程中注解我们使用的比较多的,使用注解简化开发。
JDK8引入的新特性里就有函数式接口的注解@FunctionalInterface,在接口上加上@FunctionalInterface注解,它的作用是“a functional interface has exactly one abstract method”,即仅仅只包含一个抽象方法的接口。
注解处理器(Annotation Processor)
《深入JAVA虚拟机》一书中提到,在从JDK 1.5之后, Java语言提供了对注解(Annotation)的支持,这些注解与普通的Java代码一样,是在运行期间发挥作用的。在JDK1.6中实现JSR-269规范(Pluggable Annotations Processing API),提供了一组插入式注解处理器的标准API在编译期间对注解进行处理,我们可以把它看作是一组编译器的插件,在这些插件里面,可以读取、修改、添加抽象语法树中的任意元素。如果这些插件在处理注解期间对语法树进行了修改,编译器将回到解析及填充符号表的过程重新处理,直到所有插入式注解处理器都没有再对语法树进行修改为止,每一次循环称为一个Round,也就是下图Javac编译过程中的回环部分
有了编译器注解处理的标准API后,我们的代码才有可能干涉编译器的行为,由于语法树中的任意元素,甚至包括代码注释都可以在插件之中访问到,所以通过插入式注解处理器实现的插件在功能上有很大的发挥空间。只要有足够的创意,程序猿可以使用插入式注解处理器来实现许多原本只能在编码中完成的事情。
Javac源码中, 插入式注解处理器的初始化过程是在initProcessAnnotations()方法中完成的,而它的执行过程则是在processAnnotations()方法中完成的,这个方法判断是否还有新的注解处理器需要执行,如果有的话,通过com.sun.tools.javac.processing.JavacProcessingEnvironment类的doProcessing()方法生成一个新的JavaCompiler对象对编译的后续步骤进行处理。
注解其实就是代码里的特殊标记,它用于替代配置文件:传统方式通过配置文件告诉类如何运行,有了注解技术后,开发人员可以通过注解告诉类如何运行。在Java技术里注解的典型应用是:可以通过反射技术去得到类里面的注解,以决定怎么去运行类。
注解可以标记在包、类、属性、方法,方法参数以及局部变量上,且同一个地方可以同时标记多个注解。
元注解(meta-annotation)
@Target:描述注解的使用范围(即:被修饰的注解可以用在什么地方) 。
Target注解用来说明那些被它所注解的注解类可修饰的对象范围:注解可以用于修饰 packages、types(类、接口、枚举、注解类)、类成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数),在定义注解类时使用了@Target 能够更加清晰的知道它能够被用来修饰哪些对象,它的取值范围定义在ElementType 枚举中。
public enum ElementType {
TYPE, // 类、接口、枚举类
FIELD, // 成员变量(包括:枚举常量)
METHOD, // 成员方法
PARAMETER, // 方法参数
CONSTRUCTOR, // 构造方法
LOCAL_VARIABLE, // 局部变量
ANNOTATION_TYPE, // 注解类
PACKAGE, // 可用于修饰:包
TYPE_PARAMETER, // 类型参数,JDK 1.8 新增
TYPE_USE // 使用类型的任何地方,JDK 1.8 新增
}
@Reteniton:描述注解保留的时间范围(即:被描述的注解在它所修饰的类中可以被保留到何时)。
Reteniton注解用来限定那些被它所注解的注解类在注解到其他类上以后,可被保留到何时,一共有三种策略,定义在RetentionPolicy枚举中。
public enum RetentionPolicy {
SOURCE, // 源文件保留
CLASS, // 编译期保留,默认值
RUNTIME // 运行期保留,可通过反射去获取注解信息
}
@Documented:描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息。
@Inherited:使被它修饰的注解具有继承性(如果某个类使用了被@Inherited修饰的注解,则其子类将自动具有该注解)。
注解使用
注解通常都是配合反射机制来使用的,如下,
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
//注解传入的属性
public String Field() default "my test annotation";
}
通常用@Aspect+@Around实现AOP对注解上的目标方法进行环绕增强,并做处理。
@Aspect
public class MyAnnotationTest {
private static String filed = "test";
private static final String ANNOTATION_PATH = "@annotation(com.hust.zhang.annotation.MyAnnotation)";
//可以通过@Value("${config:true}")从配置文件中拿到标志
private boolean isOpenFlag = true;
//ProceedingJoinPoint继承了JoinPoint。是在JoinPoint的基础上暴露出 proceed 这个方法。proceed是aop代理链执行的方法
@Around(ANNOTATION_PATH)
public Object around(ProceedingJoinPoint pjp) throws Throwable{
//未开启则直接执行目标方法
if (!isOpenFlag) {
return pjp.proceed();
}
//通过反射拿到目标方法的类名、方法名、参数、方法
String clazz = pjp.getTarget().getClass().getSimpleName();
String methodName = pjp.getSignature().getName();
Object[] args = pjp.getArgs();
Method method = ((MethodSignature) pjp.getSignature()).getMethod();
MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class);
return pjp.proceed();
}
public static void main(String[] args) throws ClassNotFoundException {
Class clazz = Class.forName("com.hust.zhang.annotation.MyAnnotationTest");
//获得当前类的所有属性
Field[] fields = clazz.getDeclaredFields();
Arrays.stream(fields).forEach(System.out::println);
}
}