注解与APT详解

本文详细介绍了Java中的元注解(@Target、@Retention、@Documented、@Inherited)及其应用,并通过实例展示了如何使用APT(Android Annotation Processing Tool)自动生成代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是元注解:

简单来说,就是注解的注解,元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明:
 

1. @Target, 
2. @Retention, 
3. @Documented, 
4. @Inherited
元注解的作用和相应分参数的使用说明:

@Target:

该标识用于描述注解的使用范围(即:被描述的注解可以用在什么地方): packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。
取值(ElementType)有:

1. CONSTRUCTOR:用于描述构造器 
2. FIELD:用于描述域 
3. LOCAL_VARIABLE:用于描述局部变量 
4. METHOD:用于描述方法 
5. PACKAGE:用于描述包 
6. PARAMETER:用于描述参数 
7. TYPE:用于描述类、接口(包括注解类型) 或enum声明

@Retention:

该标示定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。

取值(ElementType)有: 
1. SOURCE:在源文件中有效(即源文件保留) 
2. CLASS:在class文件中有效(即class保留) 
3. RUNTIME:在运行时有效(即运行时保留)

@Documented:

Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员

@Inherited:

@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

==注意==

@Inheritedannotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承
annotation,方法并不从它所重载的方法继承annotation。

当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,
则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited
annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指
定的annotation类型被发现,或者到达类继承结构的顶层。

自定义注解:

定义注解格式:

public @interface 注解名 {定义体}

使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。
  

注解参数的可支持数据类型:

1. 所有基本数据类型(int,float,boolean,byte,double,char,long,short)
2. String类型
3. Class类型
4. enum类型
5. Annotation类型
6. 以上所有类型的数组

Annotation类型里面的参数设定:

1. 只能用public或默认(default)这两个访问权修饰.例如,String value();这里把方法设为defaul默认类型;    
2. 参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和
String,Enum,Class,annotations等数据类型,以及这一些
类型的数组.例如,String value();这里的参数成员就为String;   
3. 如果只有一个参数成员,最好把参数名称设为”value”,
后加小括号.例:下面的例子FruitName注解就只有一个参数成员。 
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AnimalName {
    String value() default "";
}
APT的介绍:

APT英文全称:Android annotation process tool是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,使用Annotation进行额外的处理。

Annotation处理器在处理Annotation时可以根据源文件中的Annotation生成额外的源文件和其它的文件(文件具体内容由Annotation处理器的编写者决定),APT还会编译生成源文件和原来的源文件,将它们一起生成class文件。简言之:APT可以把注解,在编译时生成代码。


APT的处理要素:

注解处理器(AbstractProcess)+代码处理(javaPoet)+处理器注册(AutoService)+apt

  

使用APT来处理annotation的流程:
1. 定义注解(如@automain)
2. 定义注解处理器
3. 在处理器里面完成处理方式,通常是生成java代码。
4. 注册处理器
5. 利用APT完成如下图的工作内容。
  1. 定义注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface DIActivity {
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DIView {
    int value() default 0;
}
  1. 定义注解处理器

在定义注解处理器的时候还需要对注解器进行配置,其位置为:

 resources
    - META-INF
          - services
                - javax.annotation.processing.Processor

Processor内容:

com.soubu.di.DIProcessor   # 指定处理器全类名

==但了更方便的增加这个配置,我们只需要在,AnLogProcessor类中,加一个注解 @AutoService(Processor.class) 编译器会自动的为我们生成这个配置文件了。==

package com.soubu.di;


import com.google.auto.service.AutoService;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;




@AutoService(Processor.class)
public class DIProcessor extends AbstractProcessor {

    private Elements elementUtils;

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        // 规定需要处理的注解
        return Collections.singleton(DIActivity.class.getCanonicalName());
    }

    /**
     * public final class DIMainActivity extends MainActivity {
     *  public static void bindView(MainActivity activity) {
     *      activity.tv = (android.widget.TextView) activity.findViewById(R.id.tv);
     * }
     * }
     *
     * @param set
     * @param roundEnvironment
     * @return
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(DIActivity.class);
        for (Element element : elements) {
            // 判断是否Class
            TypeElement typeElement = (TypeElement) element;
            List<? extends Element> members = elementUtils.getAllMembers(typeElement);
            MethodSpec.Builder bindViewMethodSpecBuilder = MethodSpec.methodBuilder("bindView")
                    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                    .returns(TypeName.VOID)
                    .addParameter(ClassName.get(typeElement.asType()), "activity");
            for (Element item : members) {
                DIView diView = item.getAnnotation(DIView.class);
                if (diView == null) {
                    continue;
                }
                bindViewMethodSpecBuilder.addStatement(String.format("activity.%s = (%s) activity.findViewById(%s)", item.getSimpleName(), ClassName.get(item.asType()).toString(), diView.value()));
            }
            TypeSpec typeSpec = TypeSpec.classBuilder("DI" + element.getSimpleName())
                    .superclass(TypeName.get(typeElement.asType()))
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                    .addMethod(bindViewMethodSpecBuilder.build())
                    .build();
            JavaFile javaFile = JavaFile.builder(getPackageName(typeElement), typeSpec).build();
            try {
                javaFile.writeTo(processingEnv.getFiler());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

    private String getPackageName(TypeElement typeElement) {
        return elementUtils.getPackageOf(typeElement).getQualifiedName().toString();
    }

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        elementUtils = processingEnv.getElementUtils();
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.RELEASE_7;
    }
}
继承了AbstractProcessor抽象类,并重写以下五个方法:

1. init()

首先 init() 方法完成sdk版本的判断以及相关帮助类的初始化,帮助类主要有以下几个:

Elements elementUtils,注解处理器运行扫描源文件时,以获取元素(Element)相关的信息。
Element 有以下几个子类:

  • 包(PackageElement)、类(TypeElement)、成员变量(VariableElement)、方法(ExecutableElement)
  • Types typeUtils,
  • Filer filer,用来生成 java 类文件。
  • Trees trees,

2. getSupportedAnnotationTypes()

该方法返回一个Set,代表ButterKnifeProcessor要处理的注解类的名称集合,即 ButterKnife 支持的注解:butterknife-annotations

3.getSupportedSourceVersion()

返回当前系统支持的 java 版本。

4. getSupportedOptions()

返回注解处理器可处理的注解操作。

5. process()

在这里完成了目标类信息的收集并生成对应 java 类。

到此,这里的DIAptActivity是我们自动生成的,这里就需要使用到JavaPoet,要使用的话,必须先编译一下项目,生成java源代码。

JavaPoet 中一些重要的类(这些类还有许多实用的方法哦):

  • TypeSpec 表示类、接口、或者枚举声明
  • ParameterSpec 表示参数声明
  • MethodSpec 表示构造函数、方法声明
  • FieldSpec 表示成员变量,一个字段声明
  • CodeBlock 表示代码块,用来拼接代码
  • JavaFile 表示Java类的代码

还有几个占位符也了解下:

  • LforLiteralsJavaPoetbeginControlFlow(for(inti= L , f o r L i t e r a l s 替 换 字 符 串 、 原 语 、 J a v a P o e t 中 的 类 型 例 如 b e g i n C o n t r o l F l o w ( “ f o r ( i n t i = L; i < L;i++),1,10)addStatement(result=result L ; i + + ) ” , 1 , 10 ) a d d S t a t e m e n t ( “ r e s u l t = r e s u l t L i”, “+”)
  • SforStringsaddStatement("return S , f o r S t r i n g s 替 换 字 符 串 , 例 如 a d d S t a t e m e n t ( " r e t u r n S”, “hello”)
  • TforTypesaddStatement("returnnew T , f o r T y p e s 替 换 类 型 , 例 如 a d d S t a t e m e n t ( " r e t u r n n e w T()”, Date.class)
  • $N,for Names 替换JavaPoet中的声明

在app的Activity中使用:

@DIActivity
public class AptActivity extends AppCompatActivity {

    @DIView(R.id.tv)
    TextView tv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DIAptActivity.bindView(this);
        tv.setText("ReflectActivity");
    }
}

image

引用相关文章:
https://blog.csdn.net/fengxingzhe001/article/details/78520298
https://www.jianshu.com/p/1910762593be
https://www.jianshu.com/p/94979c056b20
https://www.jianshu.com/p/dce26aa75060
https://www.jianshu.com/p/39fc66aa3297

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值