注解和注解处理器APT

目录

背景:

常见注解

元注解

自定义注解

1.创建自定义注解

2. 创建注解处理器

3. 注册apt


背景:

项目中xml是一种松耦合的配置文件,但是随着项目的越来越大,xml文件也会越来越复杂,

注解也随之出现,注解是高耦合但是非常简洁的标记式的配置方式。

显然两者各有优劣。

我们写代码或者看代码都经常遇到注解Annotation,这些注解显然不是随便写出来就能用的,他们的生效需要注解处理器(APT)。

Java代码从编写到运行会经过三个大的时期:代码编写,编译,读取到JVM运行,针对三个时期(也可以说是生命周期吧,其实就是@Retention的三个参数)也分别有三类注解:

SOURCE:就是针对代码编写阶段,比如@Override注解
CLASS:就是针对编译阶段,这个阶段可以让编译器帮助我们去动态生成代码
RUNTIME:就是针对读取到JVM运行阶段,这个可以结合反射使用。

常见注解举例

@LayoutRes

protected abstract int getContentLayoutId();

表示对int类型的约束,返回符合我们具体要求的int类型值,这就实现了我们需要的是int类型的资源id,而不是一个普通的int类型值。

元注解

显然,想要大概了解注解,我们最好是可以自定义一个注解,(当然java是支持我们自定义的,毕竟如果不能自定义的话,那就只能用原生的一些,就没啥意义了)

那么怎么进行自定义注解呢?java提供了一些元注解,他们用来对注解做注解(就是作用于注解的),

    1.@Target,
    2.@Retention,
    3.@Documented,
    4.@Inherited

1.@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 新增
 
}

@Target(ElementType.TYPE)
public @interface Table {
    /**
     * 数据表名称注解,默认值为类名称
     * @return
     */
    public String tableName() default "className";
}

@Target(ElementType.FIELD)
public @interface NoDBColumn {

}

  2.@Retention


isAnnotationPresent只能检查运行时注解,也就是注解定义的时候@Retention(RetentionPolicy.RUNTIME)的注解,其他的注解在运行时就没得了

 1 package cn.gacl.annotation;
 2 @MyAnnotation
 3 //这里是将新创建好的注解类MyAnnotation标记到AnnotaionTest类上
 4 public class AnnotationUse {
 5     public static void main(String[] args) {
 6         // 这里是检查Annotation类是否有注解,这里需要使用反射才能完成对Annotation类的检查
 7         if (AnnotationUse.class.isAnnotationPresent(MyAnnotation.class)) {
 8             /*
 9              * MyAnnotation是一个类,这个类的实例对象annotation是通过反射得到的,这个实例对象是如何创建的呢?
10              * 一旦在某个类上使用了@MyAnnotation,那么这个MyAnnotation类的实例对象annotation就会被创建出来了
                对于这种运行时注解,可以通过 “被注解的类.getAnnotation(注解类)”获得该注解类的实例化对象
15              */
16             MyAnnotation annotation = (MyAnnotation) AnnotationUse.class
17                     .getAnnotation(MyAnnotation.class);
18             System.out.println(annotation);// 打印MyAnnotation对象,这里输出的结果为:@cn.itcast.day2.MyAnnotation()
19         }
20     }
21 }

4. @Inherited:是否允许子类继承该注解。使用了@Inherited定义的注解的自动继承特性只在当这个注解用到类声明的时候才有有效,其他都不生效。

自定义注解

使用@interface自定义注解,然后创建一个注解处理器。(注解处理器一般都要创建的,不创建这么一个类去处理而是直接在代码里面做反射的话那做个注解出来有锤子用)

1.创建自定义注解

我们使用@interface自定义注解,一般定义为 java-library

 注解的属性定义方式就和接口中定义方法的方式一样,而应用了注解的类可以认为是实现了这个特殊的接口

 1 package cn.gacl.annotation;
 2 
 3 import java.lang.annotation.ElementType;
 4 import java.lang.annotation.Retention;
 5 import java.lang.annotation.RetentionPolicy;
 6 import java.lang.annotation.Target;
 7 
 8 @Retention(RetentionPolicy.RUNTIME)
10 @Target( { ElementType.METHOD, ElementType.TYPE })
11 public @interface MyAnnotation {
12     /**
13      * 定义基本属性
14      * @return
15      */
16     String color() default "blue";//为属性指定缺省值
17 }

那添加这个有什么用呢?实际上我们前面说过,注解可以用来配置文件,那显然就是说,我们注解里面这些属性肯定是在代码里面拿出来用的

我们这边不做框架,所以就简单的直接给出拿出注解中的属性的方法:

 另外,特别的,value属性。

如果一个注解中有一个名称为value的属性,且你只想设置value属性(即其他属性都采用默认值或者你只有一个value属性),那么可以省略掉“value=”部分。

例如:@SuppressWarnings("deprecation")

2. 创建注解处理器

比较简单的注解处理器可以用反射来实现,但是有两点需要注意的是,反射实现的注解处理器肯定只能处理@Retention(RetentionPolicy.RUNTIME)的注解,另外就是,反射都是在JVM运行时实现的,这样在JVM运行时通过反射实现注解处理器的方式在一定程度上会对性能造成影响,所以,在日常开发中很少会使用这样的方式。(那些高端、优雅的注解是怎么实现的<2> -- 解析注解 - 简书反射的解析方式见上面链接)

那我们怎么处理注解呢?

那就是使用 APT 解析 编译时 注解。在编译时会通过注解标示来动态生成一些 Java 代码或者 xml,而在运行时,注解已经不存在了,它会依靠编译时生成的 Java 代码来实现我们需要的业务逻辑。就是普通的 java 代码,当然不会影响效率了

下面是一些创建注解处理器的方法要点:

我们一般会使用继承AbstractProcessor的方法

AbstractProcessor是一个抽象类,实现了接口Processor。位于包javax.annotation.processing中。该接口中定义的所有类、接口都是与实现注解处理器相关的

 init()方法可以初始化拿到一些使用的工具,比如文件相关的辅助类 Filer(可以用来生成新的java文件);元素相关的辅助类Elements;日志相关的辅助类Messager;
getSupportedSourceVersion()方法返回 Java 版本;
getSupportedAnnotationTypes()方法返回要处理的注解的结合;
上面几个方法写法基本都是固定的,重头戏是process()方法。

process:里面需要写如何具体处理注解的,需要拿到我们想处理的注解所标注的类

里面常用的api函数:

拿到所有RouteAnnotation注解标注的元素(element):

Element e = roundEnvironment.getElementsAnnotatedWith(RouteAnnotation.class)

判断被注解的元素的种类(注解标注的不一定是类,还有可能是成员变量啊成员方法之类的):

//Element e = roundEnvironment.getElementsAnnotatedWith(RouteAnnotation.class)
TypeElement clazz = (TypeElement) e.getEnclosingElement();

往往apt处理注解会生成一些java代码,那么这些java代码是如何生成的呢?

目前我看到的有两种方法:

1. filer.createSourceFile

2.javapoet

3. 注册apt

一切看起来都很顺理成章,但是我们有没有想过,这个注解处理器显然需要在编译的时候生成java文件的,JVM怎么知道要编译的时候通过我们的注解处理器生成java文件呢?

这时候就需要注册我们的注解处理器了,让JVM在编译的时候JVM找到我们自定义的注解处理器?这个时候就要用到Java SPI机制,或者可以使用谷歌官方出品的开源库Auto-Service,即通过注解@AutoService(Processor.class)可以省略上面配置的步骤,也就是在注解处理类最上面添加 @AutoService(Processor.class)

----这个地方还没太搞明白,下次再说

拓展:

一个Annotation类型可以说实际上是一个特殊的java接口,而我们通过反射api获取的一个注解类实例的时候,其实 JDK 是通过动态代理机制生成一个实现我们注解(接口)的代理类。

为什么这么说呢?可以看反编译出来的文件:

 JAVA 注解的基本原理 - Single_Yam - 博客园

反射注解与动态代理综合使用 - 简书

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值