美文网首页
Java编译时注解处理器的学习总结

Java编译时注解处理器的学习总结

作者: 132xin | 来源:发表于2020-08-03 22:33 被阅读0次

    APT的简介

    定义

    APT即是Annotation Processing Tool ,它是一个javac的一个工具,中文的意思是编译时注解处理器,可以用来在编译时扫描和处理注解。通过APT可以取到注解和被注解对象的相关信息,在拿到这些信息后我们可以根据需求来自动生成一些代码,省去了手动编写。获取注解及生成代码都是在代码编译的时候完成的,相比反射在运行时处理注解大大提高了程序性能。APT的核心就是AbstractProcessor类。

    如何在Android中使用APT呢

    在Android工程中使用APT,至少需要两个Java Library模块组成,在Android中创建Java Library的的步骤是,首先建一个Android项目,然后点击File-> New ->New Module,打开如图所示,然后选择Java Library模块。


    image.png

    这两个模块的作用是:
    一个Annotation模块,这个用来存在自定义的注解。
    一个Compiler模块,这个模块依赖Annotation模块。
    在项目的App模块和其它的业务模块都需要依赖Annotation模块,同时需要通过annotationProcessor依赖Compiler模块。
    app模块的gradle中依赖关系如下:

    implementation project(':annotation')
    annotationProcessor project(':factory-compiler')
    

    实现ButterKnife例子学习APT

    步骤:

    新建一个Android的app的工程。
    创建一个Java Library ,定义要被处理的注解(apt-annotation)。
    创建一个Java Library,定义注解处理器生成具体的类。(apt-processor)
    创建一个Android library module,通过反射调用apt-processor模块生成的方法,实现View的绑定。

    工程目录如下:


    image.png
    在apt-annotation中定义一个注解
    @Retention(RetentionPolicy.CLASS)
    @Target(ElementType.FIELD)
    public @interface BindView {
        int value() default -1;
    }
    
    

    因为这个注解是在成员变量中使用的,保留的时间是在.class字节码文件,不需要值JVM期间保留,这也体现了APT只是在编译器的编译时期完成工作。

    在apt-processor中的gradle文件中加入两个依赖。

    为了解决android studio升级到3.4.2,gradle升级到5.1.1后,apt不会执行,没办法自动生成注解文件的问题,还需要添加 annotationProcessor。

      implementation project(':apt-annotation')
      implementation 'com.google.auto.service:auto-service:1.0-rc4'
     annotationProcessor  'com.google.auto.service:auto-service:1.0-rc4'
    

    第二个依赖是AutoService,它是google开发的一个库,在使用@AutoService注解时需要用到,它的作用是用来生成META.INF/services/javax.annotation.processing.Processor文件的,在使用注解器的时候就不需要手动添加该文件。

    在apt-processor中创建注解处理器

    该Module是Java Library,不能是Android Library,因为Android平台的是基于OpenJDK的,而OpenJDK中是不包含APT的相关代码。

    BindViewProcessor要引用AutoService的注解,以及继承AbstractProcessor.并重写相应的方法:

    @AutoService(Processor.class)
    public class BindViewProcessor extends AbstractProcessor {
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            super.init(processingEnvironment);
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            return false;
        }
    
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            return super.getSupportedAnnotationTypes();
        }
    
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return super.getSupportedSourceVersion();
        }
        
    }
    
    

    接下来对重写的方法进行解析

    • public synchronized void init()
      这个 方法是用来初始化处理器的,方法中有个ProcessingEnvironment processingEnvironment类型的参数,它是一个注解处理工具的集合。包含了众多的工具类,例如Filer可以用来编写新文件。Meessage可以用来处理错误信息。Elements是一个可以处理Element的工具类。
    • 什么是Elments?
      在Java语言中,Elenent是一个接口,表示一个程序的元素,例如包,类,方法,变量。Element已知的子接口有如下几种。
      PackageElement 表示一个包程序元素。
      ExecutableElement表示某个类或者接口的方法,构造方法和初始化程序,包括注释类型元素。
      TypeElement 表示一个类或者接口的元素。注意对有关类型及其成员的信息的访问。注意,枚举类型是一种类,而注解类型是一种接口。
      VariableElement表示一个字段,enum常量,方法或者构成方法参数,局部变量或者异常参数。
    • public boolean process()
      这个是AbstractProcessor中最重要的一个方法,该方法的返回值是一个boolean类型,返回值表示注解是否由当前Processor处理,如果返回true,则这些注解由这个处理器进行处理,后续的其他Processor无需处理它们。如果返回false,则这些注解未在次Processor中处理,需要后续的其他Processor处理它们。在这个方法中,我们需要检验被注解的对象是否合法,可以编写处理注解的代码,以及自动生成需要的java文件等。处理的大部分逻辑都在这个类中。
    • public Set<String> getSupportedAnnotationTypes()
      这方法是返回一个set集合,集合中指定那些注解是需要这个处理器处理的。
    • public SourceVersion getSupportedSourceVersion()
      这个返回是返回当前的正在使用的java版本。通常返回SourceVersion.latestSupported()就可以了。

    接下来是编写BingViewProcessor的代码,注意改文件中不可以含有中文,否则编译不通过。代码中的中文是为了方便理解加的解析,在运行的时候要删除。如果需要中文解析的可以在build.gradle中添加

    //中文乱码问题(错误:编码GBK不可映射字符)
    tasks.withType(JavaCompile) {
        options.encoding = "UTF-8"
    }
    
    @AutoService(Processor.class)
    public class BindViewProcessor extends AbstractProcessor {
        private Elements mElementUtil;
        private Map<String,ClassCreatorFactory> mClassCreatorFactoryMap=new HashMap<>();
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            super.init(processingEnvironment);
            //初始化Element
            mElementUtil= processingEnvironment.getElementUtils();
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            mClassCreatorFactoryMap.clear();
            //获取所有包含了@BingView注解的集合
            Set<? extends Element> elements=roundEnvironment.getElementsAnnotatedWith(BindView.class);
            for (Element element:elements){
                VariableElement variableElement= (VariableElement) element;
                TypeElement typeElement= (TypeElement) variableElement.getEnclosingElement();
                String fullClassName=typeElement.getQualifiedName().toString();
                ClassCreatorFactory classCreatorFactory=mClassCreatorFactoryMap.get(fullClassName);
                if (classCreatorFactory==null){
                    classCreatorFactory=new ClassCreatorFactory( mElementUtil,typeElement);
                    mClassCreatorFactoryMap.put(fullClassName,classCreatorFactory);
                }
                BindView bindViewAnnotation=variableElement.getAnnotation(BindView.class);
                int id=bindViewAnnotation.value();
                classCreatorFactory.putElement(id,variableElement);
    }
       //开始创建Java类
                for (String key:mClassCreatorFactoryMap.keySet()){
                    ClassCreatorFactory factory=mClassCreatorFactoryMap.get(key);
                    try {
                        JavaFileObject fileObject=processingEnv.getFiler().createSourceFile(factory.getClassFullName(),factory.getTypeElement());
                        Writer writer=fileObject.openWriter();
                        writer.write(factory.generateJavaCode());
                        writer.flush();
                        writer.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            //返回true说明这个processor要处理这个注解,后续的Processor不需
            return true;
        }
    
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            //设置这个注解器是给哪个注解类用的,这里是给BingView的注解类使用
            HashSet<String> supportType=new LinkedHashSet<>();
            supportType.add(BindView.class.getCanonicalName());
            return supportType;
        }
    
        @Override
        public SourceVersion getSupportedSourceVersion() {
            //返回java的版本
            return SourceVersion.latestSupported();
        }
    
    }
    

    ClassCreatorFactory的代码如下,注意该类的代码不能出现中文的注释,该类的代码就是相当于在代码中通过字符串的方式编写代码,需要注意空格等细节问题,这个类很容易出错,可以利用JavaPoet根据实体类生成。

    public class ClassCreatorFactory {
        private String mClassName;
        private String mPackageName;
        private TypeElement typeElement;
        private Map<Integer, VariableElement> mVariableElementMap = new HashMap<>();
    
        public ClassCreatorFactory(Elements elements, TypeElement typeElement) {
            //获取该类
            this.typeElement = typeElement;
            //获取包元素
             PackageElement packageElement = elements.getPackageOf(typeElement);
            mPackageName = packageElement.getQualifiedName().toString();
            String temClassName = typeElement.getSimpleName().toString();
            //生成类的名称
            mClassName = temClassName + "_ViewBinding";
        }
    
        /**
         * 添加VariableElement,例如字段,局部变量,参数等
         * @param id
         * @param element
         */
        public void putElement(int id,VariableElement element){
            mVariableElementMap.put(id,element);
        }
    
        /**
         * 创建java代码,可以看出是文本的形式,对字符进行拼接即可。
         * @return
         */
        public String generateJavaCode(){
            StringBuilder stringBuilder=new StringBuilder();
            //注释
            stringBuilder.append("/**\n"+"* 通过APT自动创建的类"+"\n*/\n");
            //包名
            stringBuilder.append("package ").append(mPackageName).append(";\n\n");
            stringBuilder.append("public class ").append(mClassName).append("{\n");
            //创建方法
            generateMethod(stringBuilder);
            stringBuilder.append("\n}\n");
            return stringBuilder.toString();
        }
    
        /**
         * 创建方法
         * @param stringBuilder
         */
        private void generateMethod(StringBuilder stringBuilder) {
            stringBuilder.append("\tpublic void bindView(");
            stringBuilder.append(typeElement.getQualifiedName());
            stringBuilder.append(" value) { \n");
            for (int id:mVariableElementMap.keySet()){
                VariableElement variableElement=mVariableElementMap.get(id);
                String viewName=variableElement.getSimpleName().toString();
                String viewType=variableElement.asType().toString();
                stringBuilder.append("\t\tvalue.");
                stringBuilder.append(viewName);
                stringBuilder.append(" = ");
                stringBuilder.append("(");
                stringBuilder.append(viewType);
                //findViewById(id);
                stringBuilder.append(")(((android.app.Activity)value).findViewById( ");
                stringBuilder.append(id+" ));");
                stringBuilder.append("\n}\n");
            }
    
        }
        public String getClassFullName(){
            return mPackageName+"."+mClassName;
        }
        public TypeElement getTypeElement(){
            return typeElement;
        }
    
    
    }
    
    
    创建apt-sdk

    通过反射调用生成类的方法。

    public class BindViewUtil {
        public static void bindView(Activity activity) throws ClassNotFoundException, NoSuchMethodException {
            try {
                Class cla=activity.getClass();
                Class bindViewClass=Class.forName(cla.getName()+"_ViewBinding");
                Method bindViewMethod=bindViewClass.getMethod("bindView",cla);
                bindViewMethod.setAccessible(true);
                bindViewMethod.invoke(bindViewClass.newInstance(),activity);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
    
        }
    }
    
    在App中调用

    需要加入以下的权限:

       implementation project(":apt-annotation")
       //要用 annotationProcessor ,否则编译不通过
       annotationProcessor project(":apt-procrssor")
       implementation project(':apt-sdk')
    

    使用该Apt

    public class MainActivity extends AppCompatActivity {
        @BindView(R.id.textView)
        TextView textView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            try {
                BindViewUtil.bindView(this);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
            textView.setText("你好,注解处理器");
        }
    }
    

    接下来我们看到生成的类在下面的目录中


    image.png
    public class MainActivity_ViewBinding{
        public void bindView(com.example.hx.apttest.MainActivity value) { 
            value.textView = (android.widget.TextView)(((android.app.Activity)value).findViewById( 2131165353 ));
    }
    
    }
    

    从上面的类中可以看到调用用findViewById的方法。

    参考链接:
    https://blog.csdn.net/qq_20521573/article/details/82321755
    http://77blogs.com/?p=199

    相关文章

      网友评论

          本文标题:Java编译时注解处理器的学习总结

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