美文网首页
java注解学习(2)-利用注解生成动态代码

java注解学习(2)-利用注解生成动态代码

作者: weiinter105 | 来源:发表于2019-02-24 21:59 被阅读0次

    前言

    在前面我们学习过java的注解的语法,说明了注解的作用;前面举的例子是运行时注解,这样会影响我们代码执行的效率;本章我们看看编译时注解的使用方式,可以利用注解在编译时生成代码,已提高我们的工作效率;其关键就在AbstractProcessor(注解处理器)

    示例

    废话不多说,我们直接看一个简单的示例:

    注解处理器

    java library

    首先创建一个java library,用来创建我们的注解处理器
    Gradle module java-library

    apply plugin: 'java-library'
    
    
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        api 'com.google.auto.service:auto-service:1.0-rc3'
    }
    
    tasks.withType(JavaCompile) {
        options.encoding = "UTF-8"
    }
    
    sourceCompatibility = "1.7"
    targetCompatibility = "1.7"
    

    定义一个我们的自定义注解

    package com.example.testabstractprocessor;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Retention(RetentionPolicy.SOURCE) //编译时起作用
    @Target(ElementType.FIELD) //注解作用在field
    public @interface TestBindView {
        int value() default -1;
    }
    

    针对上面的自定义注解,编写注解处理器

    package com.example.testabstractprocessor;
    
    import com.google.auto.service.AutoService;
    
    import java.io.IOException;
    import java.io.Writer;
    import java.util.LinkedHashSet;
    import java.util.Set;
    
    import javax.annotation.processing.AbstractProcessor;
    import javax.annotation.processing.Filer;
    import javax.annotation.processing.Messager;
    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.PackageElement;
    import javax.lang.model.element.TypeElement;
    import javax.lang.model.element.VariableElement;
    import javax.lang.model.util.Elements;
    import javax.tools.Diagnostic;
    import javax.tools.JavaFileObject;
    
    
    @AutoService(Processor.class)
    public class TestAbstractProcessor extends AbstractProcessor {
    
        private Filer mFiler;//用于生成java文件
        private Messager mMessager; //用于生成日志
        private Elements mElements; //用于解读annation注解对象的相关信息的辅助类
    
        public void note(String log){
            mMessager.printMessage(Diagnostic.Kind.WARNING,log);
        }
    
        public void note(String format,Object ...args){
            mMessager.printMessage(Diagnostic.Kind.WARNING,String.format(format,args));
        }
    
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            /*
            该方法返回一个Set<String>,代表ButterKnifeProcessor要处理的注解类的名称集合,即 ButterKnife 支持的注解:butterknife-annotations
            * */
            //表示,自定义的annotation的名称集合
            Set<String> annotations = new LinkedHashSet<>();
            annotations.add(TestBindView.class.getCanonicalName());
            return annotations;
        }
    
        @Override
        public SourceVersion getSupportedSourceVersion() {
            //返回当前系统支持的 java 版本
            return SourceVersion.latestSupported();
        }
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            /*
            首先 init() 方法完成sdk版本的判断以及相关帮助类的初始化,帮助类主要有以下几个:
            Elements elementUtils,注解处理器运行扫描源文件时,以获取元素(Element)相关的信息。Element 有以下几个子类:
            包(PackageElement)、类(TypeElement)、成员变量(VariableElement)、方法(ExecutableElement)
            //总之,是用来表示被标注的信息
            Types typeUtils,
            Filer filer,用来生成 java 类文件。
            Trees trees,
            **/
            super.init(processingEnvironment);
            mFiler = processingEnvironment.getFiler();
            mMessager = processingEnvironment.getMessager();
            mElements = processingEnvironment.getElementUtils();
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            //完成了目标类信息的收集并生成对应 java 类
            Set<? extends Element> TestBindViewElements = roundEnvironment.getElementsAnnotatedWith(TestBindView.class);
            for(Element element:TestBindViewElements){
                //获取包名
                PackageElement packageElement = mElements.getPackageOf(element);
                String pkgName = packageElement.getQualifiedName().toString();
                note("package name = %s",pkgName);
    
                //获得包装类类名
                TypeElement typeElement = (TypeElement) element.getEnclosingElement();
                String enclosingName = typeElement.getQualifiedName().toString();
                note("Enclosing class name = %s",enclosingName);
    
                //TestBindView是针对view进行注解的
                String fieldName = ((VariableElement)element).getSimpleName().toString(); //field的名字
                String fieldClassType = ((VariableElement)element).asType().toString(); //field的类型
                TestBindView testBindView = element.getAnnotation(TestBindView.class);
                int fieldValue = testBindView.value(); //注解的数据
                note("field %s %s = %d",fieldClassType,fieldName,fieldValue);
    
                //生成辅助文件
                createFile(typeElement,fieldClassType,fieldName,fieldValue);
                return true;
    
            }
            return false;
        }
    
        public void createFile(TypeElement enclosingClass,String fieldType,String fieldName,int fieldValue){
            String pkgName = mElements.getPackageOf(enclosingClass).getQualifiedName().toString();
            String enclosingName = enclosingClass.getQualifiedName().toString();
            try {
                JavaFileObject javaFileObject = mFiler.createSourceFile(enclosingName+"_TestBindView"
                                                                       ,new Element[]{}); //要生成的java文件
                Writer writer =  javaFileObject.openWriter();
                writer.write(brewCode(pkgName,fieldType,fieldName,fieldValue));
                writer.flush();
                writer.close(); //创建对应的java文件
    
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public String brewCode(String pkgName,String fieldType,String fieldName,int fieldValue){
            StringBuilder builder = new StringBuilder();
            builder.append("package " + pkgName + ";\n\n");
            builder.append("//Auto generated by AbstractProcessor,do not modify!!\n\n");
            builder.append("public class MainActivity_TestBindView { \n\n");
            builder.append("public static void main(String[] args){ \n");
            String info = String.format("%s %s = %d", fieldType, fieldName, fieldValue);
            builder.append("System.out.println(\"" + info + "\");\n");
            builder.append("}\n");
            builder.append("}");
            return builder.toString();
    
        }
    }
    

    注意点1:
    @AutoService(Processor.class) :向javac注册我们这个自定义的注解处理器,这样,在javac编译时,才会调用到我们这个自定义的注解处理器方法。
    AutoService这里主要是用来生成
    META-INF/services/javax.annotation.processing.Processor文件的。如果不加上这个注解,那么,你需要自己进行手动配置进行注册,具体手动注册方法如下:
    1.创建一个
    META-INF/services/javax.annotation.processing.Processor文件,
    其内容是一系列的自定义注解处理器完整有效类名集合,以换行切割
    2.将自定义注解处理器和
    META-INF/services/javax.annotation.processing.Processor打包成一个.jar文件。所以其目录结构大概如下所示:

    MyProcessor.jar
        - com
            - example
                - MyProcessor.class
    
        - META-INF
            - services
                - javax.annotation.processing.Processor
    

    注意点2:
    init(ProcessingEnvironment env):每个Annotation Processor必须***
    有一个空的构造函数 *。编译期间,init()会自动被注解处理工具调用,并传入ProcessingEnviroment参数,通过该参数可以获取到很多有用的工具类: Elements (用于存储被注解的信息), Types , Filer(用于生成java文件) **等等
    process(Set<? extends TypeElement> annoations, RoundEnvironment roundEnv):Annotation Processor扫描出的结果会存储进roundEnv中,可以在这里获取到注解内容,编写你的操作逻辑。注意,process()函数中不能直接进行异常抛出,否则的话,运行Annotation Processor的进程会异常崩溃,然后弹出一大堆让人捉摸不清的堆栈调用日志显示.
    getSupportedAnnotationTypes(): 该函数用于指定该自定义注解处理器(Annotation Processor)是注册给哪些注解的(Annotation),注解(Annotation)指定必须是完整的包名+类名(eg:com.example.MyAnnotation)
    getSupportedSourceVersion():用于指定你的java版本,一般返回:SourceVersion.latestSupported()。当然,你也可以指定具体java版本:
    return SourceVersion.RELEASE_7;

    经过前面3个步骤后,其实就已经算完成了自定义Annotation Processor。后面要做的就是在源码里面,在需要的地方写上我们自定义的注解就行了。
    以上注解处理器就使用完毕,讲生成的jar文件cp到app/libs文件夹下,在app端使用

    app

    Gradle module app

    apply plugin: 'com.android.application'
    
    android {
        compileSdkVersion 28
        defaultConfig {
            applicationId "com.example.myapplication"
            minSdkVersion 15
            targetSdkVersion 28
            versionCode 1
            versionName "1.0"
            testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        }
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            }
        }
    }
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation 'com.android.support:appcompat-v7:28.0.0'
        implementation 'com.android.support.constraint:constraint-layout:1.1.3'
        testImplementation 'junit:junit:4.12'
        androidTestImplementation 'com.android.support.test:runner:1.0.2'
        androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
        annotationProcessor files('libs/TestAbstractProcessor.jar')
    }
    
    package com.example.myapplication;
    
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.widget.TextView;
    
    import com.example.testabstractprocessor.TestBindView;
    
    public class MainActivity extends AppCompatActivity {
    
       @TestBindView(R.id.textView)
        TextView tv; //此处对TextView使用了注解
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    }
    

    参考注解处理器(Annotation Processor)简析
    注意点:

    1. 在 app/build/generated/source/apt/debug下面生成了想要的文件
    package com.example.myapplication;
    
    //Auto generated by AbstractProcessor,do not modify!!
    
    public class MainActivity_TestBindView { 
    
    public static void main(String[] args){ 
    System.out.println("android.widget.TextView tv = 2131165319");
    }
    

    2.执行gradlew build
    就可以在Android Studio下的terminal中看到
    警告: package name = com.example.myapplication
    警告: Enclosing class name = com.example.myapplication.MainActivity
    警告: field android.widget.TextView tv = 2131165319
    通过Messager在编译期间打出log

    踩坑

    总结下踩过得坑

    首先在Android Stuido里面创建一个java library module

    https://blog.csdn.net/u013087553/article/details/71747327

    使用创建java library

    https://www.cnblogs.com/jpfss/p/9875402.html

    java生成jar包
    META-INF https://www.cnblogs.com/mq0036/p/8566427.html

    错误: 编码GBK的不可映射字符
    https://www.cnblogs.com/fply/p/8327759.html

    module:app

    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation 'com.android.support:appcompat-v7:28.0.0'
        implementation 'com.android.support.constraint:constraint-layout:1.1.3'
        testImplementation 'junit:junit:4.12'
        androidTestImplementation 'com.android.support.test:runner:1.0.2'
        androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
        api project(path: ':TestAbstractProcessor')
    }
    

    这样配置不行
    Annotation processors must be explicitly declared now. The following dependencies on the compile classpath are found to contain annotation processor. Please add them to the annotationProcessor configuration.

    Gradle 3.0以后,先将生成的jar包复制过去app project下面的libs文件夹

    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation 'com.android.support:appcompat-v7:28.0.0'
        implementation 'com.android.support.constraint:constraint-layout:1.1.3'
        testImplementation 'junit:junit:4.12'
        androidTestImplementation 'com.android.support.test:runner:1.0.2'
        androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
        annotationProcessor files('libs/TestAbstractProcessor.jar')
    }
    

    拓展延伸

    以上其实就是ButterKnife的基本原理;也是利用注解处理器,对自定义的注解生成对应的java文件;具体可参考ButterKnife 原理解析
    总结来说就是注解处理器+JavaPoet(更方便的生成java文件)
    生成类似于

    // Generated code from Butter Knife. Do not modify!
    package com.example.test1;
    
    import android.support.annotation.CallSuper;
    import android.support.annotation.UiThread;
    import android.view.View;
    import android.widget.Button;
    import android.widget.TextView;
    import butterknife.Unbinder;
    import butterknife.internal.DebouncingOnClickListener;
    import butterknife.internal.Utils;
    import java.lang.IllegalStateException;
    import java.lang.Override;
    
    public class MainActivity_ViewBinding<T extends MainActivity> implements Unbinder {
      protected T target;
    
      private View view2131165218;
    
      @UiThread
      public MainActivity_ViewBinding(final T target, View source) {
        this.target = target;
    
        View view;
        target.test = Utils.findRequiredViewAsType(source, R.id.test, "field 'test'", TextView.class);
        view = Utils.findRequiredView(source, R.id.button, "field 'btn' and method 'btn_click'");
        target.btn = Utils.castView(view, R.id.button, "field 'btn'", Button.class);
        view2131165218 = view;
        view.setOnClickListener(new DebouncingOnClickListener() {
          @Override
          public void doClick(View p0) {
            target.btn_click();
          }
        });
      }
    
      @Override
      @CallSuper
      public void unbind() {
        T target = this.target;
        if (target == null) throw new IllegalStateException("Bindings already cleared.");
    
        target.test = null;
        target.btn = null;
    
        view2131165218.setOnClickListener(null);
        view2131165218 = null;
    
        this.target = null;
      }
    }
    

    总结

    注解一般有运行时处理和编译时处理两种使用方式,这里我们都学习了一遍,可以看出,注解对于一些大型框架,效率工具而言,是一种利器;希望有一天自己能根据工作中的需求用到注解的使用

    相关文章

      网友评论

          本文标题:java注解学习(2)-利用注解生成动态代码

          本文链接:https://www.haomeiwen.com/subject/wlfiyqtx.html