利用编译时注解生成Java源代码

我们在编写注解的时候,需要指定@Retention,有三个可选值,表示注解会被保留到那个阶段。

RetentionPolicy.SOURCE 
     这种类型的Annotations只在源代码级别保留,编译时就会被忽略,因此一般用来为编译器提供额外信息,以便于检测错误,抑制警告等. 比如@Override @SuppressWarnings

RetentionPolicy.CLASS 
    这种类型的Annotations编译时被保留,在class文件中存在,但JVM将会忽略,一般用来生成源代码,xml文件等
RetentionPolicy.RUNTIME 
    这种类型的Annotations将被JVM保留,所以它们能在运行时被JVM或其他使用反射机制的代码所读取和使用.

编译时注解就是针对Retention=RetentionPolicy.CLASS的情况,目前android上有很多框架都使用了编译时注解,比如Dagger、ButterKnife。利用编译时注解,实现动态的生成代码,大大提升了运行效率。

下面就做个简单的案例:

    通过定义一个BindView注解,用来帮助我们获取view,类似ButterKnife中的BindView注解功能一样。

开发工具使用主流的AndroidStudio。

开发前我们先设计一下module依赖关系:

——InjecterAnnotation

java工程,用来存放注解

——InjecterProcessor

java工程,用来处理我们定义的编译时注解,实现动态生成代码。依赖于InjecterAnnotation

——Injecter

Android library工程,对外提供的注解api,依赖于InjecterAnnotation

——app

Android测试工程,用来测试我们的编译时注解,依赖于Injecter。

一、创建InjecterAnnotation 工程

创建一个java module,定义我们的注解,并指定Retention=RetentionPolicy.CLASS

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
    int value();
}
build.gradle

apply plugin: 'java'

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    sourceCompatibility = "1.7"
    targetCompatibility = "1.7"
}

二、创建InjecterProcessor工程

创建一个java工程,编写我们的注解处理器

我们编写的注解处理器,需要继承javax.annotation.processing.AbstractProcessor

@AutoService(Processor.class)
public final class InjecterProcessor extends AbstractProcessor{

    private Elements elementUtils;
    private Types typeUtils;
    private Filer filer; //生成源代码
    private Messager messager; //打印信息
    private static final ClassName VIEW_BINDER = ClassName.get("injecter.api", "ViewBinder");//实现的接口

    private static final String BINDING_CLASS_SUFFIX = "$$ViewBinder";//生成类的后缀 以后会用反射去取

    @Override public synchronized void init(ProcessingEnvironment env) {
        super.init(env);

        elementUtils = env.getElementUtils();
        typeUtils = env.getTypeUtils();
        filer = env.getFiler();
        messager = env.getMessager();
    }

    /**
     * 需要处理的注解,有多少个就添加多少个
     * @return
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<>();
        types.add(BindView.class.getCanonicalName());
        return types;
    }

    /**
     * 支持的源码版本
     * @return
     */
    @Override public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    /**
     * 获取注解信息,动态生成代码
     * @param annotations
     * @param roundEnv
     * @return
     */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
       
        //代码放到后面来讲
        return true;
    }
}	
如上所示,一般需要重写getSupportedAnnotationTypes,getSupportedSourceVersion,process

getSupportedAnnotationTypes

  添加我们需要处理的注解

getSupportedSourceVersion

 支持的源代码版本,一般返回SourceVersion.latestSupported()即可。

process

处理注解,动态生成代码,这是重点。


该java module的build.gradle文件定义如下

apply plugin: 'java'

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile project(':InjecterAnnotation')
    compile 'com.google.auto:auto-common:0.6'
    compile 'com.google.auto.service:auto-service:1.0-rc2'
    compile 'com.squareup:javapoet:1.7.0'

    sourceCompatibility = "1.7"
    targetCompatibility = "1.7"
}
1.com.google.auto.service:auto-service:用来帮助我们自动生成META-INF,该目录结构如下

META-INF

     --services

         --javax.annotation.processing.Processor

文件中添加我们的injecter.processor.InjecterProcessor

2.com.squareup:javapoet:java源代码生成工具

三、创建Injecter工程

提供api

public class Injecter {
    public static void bind(Activity activity){
        String clsName = activity.getClass().getName();
        try {
            Class<?> viewBindingClass = Class.forName(clsName + "$$ViewBinder");
            ViewBinder viewBinder = (ViewBinder)viewBindingClass.newInstance();
            viewBinder.bind(activity);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

通过Injecter.bind(activity)实例化动态创建的注解类,完成View的查找

四、Demo

首先在工程根目录中的build.gradle中添加apt支持

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.0'

        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'

    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

在demo module下的build.gradle中添加apt 依赖工程

apply plugin: 'com.android.application'
apply plugin: 'android-apt'

android {
    compileSdkVersion 24
    buildToolsVersion "24.0.3"
    defaultConfig {
        applicationId "com.annotation"
        minSdkVersion 15
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile project(':Injecter')
    //apt
    apt project(':InjecterProcessor')
}


编写测试类

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Injecter.bind(this);
    }
    @BindView(R.id.textview)
    TextView textView;
}

终于完成了所有步骤,下面我们看看编译器是否为我们生存了想要的代码,clean...rebuild project


可以看到build...generated....source目录下多了一个apt目录,且帮我们生存了一个java类 MainActivity$$ViewBinder.

接下来看看MainActivity$$ViewBinder类中都有什么

import android.widget.TextView;

import injecter.api.ViewBinder;

public class MainActivity$$ViewBinder<T extends MainActivity> implements ViewBinder<MainActivity> {
  @Override
  public void bind(final MainActivity target) {
    target.textView=(TextView)target.findViewById(2131165185);
  }
}
可以看到我们调用Injecter.bind(activity)时,会通过反射创建MainActivity$$ViewBinder 实例,然后调用findViewById生成我们需要的View对象。


到此,就完成了编译时注解的解析,代码生成,注解使用。重点还是在于注解处理器,由于篇幅已经很长,大家直接看代码吧

代码下载






  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Java 中,注解是在编译处理的,因此要在编译修改属性上的注解内容,需要使用 Java 的 annotation processing 工具(APT)。 APT 是一种在编译扫描和处理 Java 注解的工具,它可以在编译期间生成新的 Java 代码,并将其编译成可执行的程序。APT 工具提供了许多 API,可以用来读取和修改源代码上的注解信息。 下面是一个简单的例子,演示如何在编译修改属性上的注解内容: ```java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface MyAnnotation { String value(); } public class MyClass { @MyAnnotation("old value") private String myField; } public class MyProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { for (Element element : roundEnv.getElementsAnnotatedWith(MyAnnotation.class)) { if (element.getKind() == ElementKind.FIELD) { VariableElement variableElement = (VariableElement) element; MyAnnotation annotation = variableElement.getAnnotation(MyAnnotation.class); String newValue = "new value"; MyAnnotation newAnnotation = new MyAnnotation() { @Override public String value() { return newValue; } @Override public Class<? extends Annotation> annotationType() { return MyAnnotation.class; } }; AnnotatedTypeMirror.AnnotatedDeclaredType fieldType = (AnnotatedTypeMirror.AnnotatedDeclaredType) variableElement.asType(); AnnotatedTypeMirror.AnnotatedDeclaredType annotatedFieldType = fieldType.replaceAnnotation(newAnnotation); variableElement.getEnclosingElement().getEnclosedElements().remove(variableElement); FieldSpec.Builder fieldBuilder = FieldSpec.builder(TypeName.get(annotatedFieldType.getUnderlyingType()), variableElement.getSimpleName().toString()); variableElement.getModifiers().forEach(modifier -> fieldBuilder.addModifiers(modifier)); fieldBuilder.addAnnotation(annotatedFieldType.getAnnotations().stream().map(AnnotationSpec::get).collect(Collectors.toList())); fieldBuilder.initializer("null"); FieldSpec fieldSpec = fieldBuilder.build(); JavaFile.builder(annotatedFieldType.getUnderlyingType().toString().replace('.', '/').replace('$', '/') + "Generated", fieldSpec) .build() .writeTo(processingEnv.getFiler()); } } return true; } } ``` 在这个例子中,`MyAnnotation` 是一个自定义的注解,`MyClass` 是一个包含注解属性的类。`MyProcessor` 是一个 APT 处理器,它会在编译扫描和处理带有 `@MyAnnotation` 注解的元素。在 `process` 方法中,我们使用 `roundEnv.getElementsAnnotatedWith` 方法获取所有带有 `@MyAnnotation` 注解的元素,然后判断元素类型是否为字段。如果是字段,我们就可以使用 `variableElement.getAnnotation` 方法获取字段上的注解对象,然后构造一个新的注解对象并使用 `replaceAnnotation` 方法替换原有的注解对象。最后,我们可以使用 `JavaFile.builder` 方法创建一个新的 Java 文件,并将修改后的字段写入该文件中。 需要注意的是,APT 处理器需要在编译自动运行,可以通过在 `build.gradle` 文件中配置以下内容来实现: ```groovy dependencies { compileOnly 'com.google.auto.service:auto-service:1.0-rc7' annotationProcessor 'your.package.MyProcessor' } sourceSets { main { java { srcDirs 'src/main/java', 'build/generated/source/apt/main' } } } compileJava.options.annotationProcessorPath = configurations.compileClasspath ``` 这个例子只是一个简单的示例,实际使用中可能需要更复杂的处理逻辑。但是,这个例子可以帮助你了解在编译修改注解内容的基本原理。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值