美文网首页Annotation应用层的知识归纳
如何构建编译时注解解析框架

如何构建编译时注解解析框架

作者: 青岚之峰 | 来源:发表于2018-02-16 22:29 被阅读30次

    前言

    在前面的文章中,咱们学习了Java类加载Java反射Java注解,那现在咱们就可以利用所学搞点事情了,所谓学以致用,方为正途。

    如果想直接阅读源码,请点这里Github

    铺垫

    在开始搞事情前,咱们还需要了解以下几个物件:

    • Annotation Processor: 注解处理器
    • JavaPoet:Java源码文件生成者
    • javax.lang.model.element:用于解析程序中的元素,例如:包、类、方法、变量

    Annotation Processor

    注解处理器是在编译时用来扫描和处理注解的工具。你可以注册自己感兴趣的注解,程式编译时会将添加注解的元素,交由注册它的注解处理器来处理。

    那咱们如何实现一个自己的注解处理器?

    1. 继承AbstractProcessor
    2. 覆盖getSupportedAnnotationTypes()
    3. 覆盖getSupportedSourceVersion()
    4. 覆盖process()

    AbstractProcessor:抽象注释处理器,为大多数自定义注释处理器的超类。

    getSupportedAnnotationTypes():这里注册你感兴趣的注解。它的返回一个字符串的Set,包含注解类型的合法全称。

    getSupportedSourceVersion():指定使用的Java版本。通常这里返回SourceVersion.latestSupported()。

    process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment):注解处理器的核心方法,在这里进行注解扫描、评估和处理,以及生成Java文件。

    生成Java文件,就交由JavaPoet来完成

    JavaPoet

    JavaPoet是一个用来生成 .java源文件的工具(由Square提供)。

    咱们来讲一下JavaPoet里面常用的几个类:

    • TypeSpec:表示一个类、接口或者枚举声明
    • MethodSpec:表示一个构造函数或方法声明
    • FieldSpec:表示一个成员变量、字段声明
    • JavaFile:生成java文件

    下面通过一个实例来说明具体使用方式:

    private void generateHelloWorld() throws IOException {
            MethodSpec mainMethod = MethodSpec.methodBuilder("main")
                    .addModifiers(new Modifier[]{Modifier.PUBLIC, Modifier.STATIC})
                    .addParameter(String[].class, "args")
                    .addStatement("System.out.println(\"Hello World\")")
                    .build();
    
            FieldSpec androidVersion = FieldSpec.builder(String.class, "androidVer")
                    .addModifiers(new Modifier[]{Modifier.PRIVATE})
                    .initializer("$S", "Lollipop")
                    .build();
    
            TypeSpec typeSpec = TypeSpec.classBuilder("HelloWorld")
                    .addModifiers(new Modifier[]{ Modifier.FINAL, Modifier.PUBLIC})
                    .addMethod(mainMethod)
                    .addField(androidVersion)
                    .build();
    
            JavaFile javaFile = JavaFile.builder("com.hys.test", typeSpec).build();
            javaFile.writeTo(System.out);
        }
    

    执行函数,结果如下:

    package com.hys.test;
    
    import java.lang.String;
    
    public class HelloWorld {
        private String androidVer = "Lollipop";
    
        public static void main(String[] args) {
            System.out.println("Hello World");
        }
    }
    

    这里$S占位符,JavaPoet占位符如下:

    • $S:字符串类型占位符
    • $T:类型占位符
    • $N:名称占位符(方法名或者变量名等)
    • $L:字面常量

    这里只是投石问路,关于JavaPoet更多API使用,请参见其文档

    javax.lang.model.element

    Element
    用于 Java 的模型元素的接口。

    • ExecutableElement:表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注释类型元素
    • PackageElement:表示一个包程序元素
    • TypeElement:表示一个类或接口程序元素
    • TypeParameterElement:表示类、接口、方法或构造方法元素的形式类型参数
    • VariableElement:表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数

    通过Element的getModifiers()获得元素的修饰符

    Modifier
    表示程序元素(如类、方法或字段)上的修饰符。
    以下是常用修饰符:

    • ABSTRACT:修饰符 abstract
    • FINAL:修饰符 final
    • NATIVE:修饰符 native
    • PRIVATE:修饰符 private
    • PROTECTED:修饰符 protected
    • PUBLIC:修饰符 public
    • STATIC:修饰符 static
    • SYNCHRONIZED:修饰符 synchronized

    通过Element的asType()获得元素的类型

    TypeMirror
    表示 Java 编程语言中的类型。这些类型包括基本类型、声明类型(类和接口类型)、数组类型、类型变量和 null 类型。

    通过TypeMirror的getKind()类型的种类

    TypeKind
    表示类型的种类。

    以下是常用的类型:

    • ARRAY:数组类型
    • BOOLEAN:基本类型 boolean
    • BYTE:基本类型 byte
    • CHAR:基本类型 char
    • DECLARED:类或接口类型
    • DOUBLE:基本类型 double
    • ERROR:无法解析的类或接口类型。
    • EXECUTABLE:方法、构造方法或初始化程序
    • FLOAT:基本类型 float
    • INT:基本类型 int
    • LONG:基本类型 long
    • NONE:在实际类型不适合的地方使用的伪类型
    • NULL:null 类型
    • PACKAGE:对应于包元素的伪类型
    • SHORT:基本类型 short
    • TYPEVAR:类型变量
    • VOID:对应于关键字 void 的伪类型

    获取元素的父元素
    通过Element的getEnclosingElement返回元素的父元素。

    获取元素上的注解
    通过Element的getAnnotation(Class<A> annotationType)获得元素上的注解。

    了解了上述内容,下面咱们开始搞事情

    创建注解处理器

    1.Android Studio的File->New->New module,如下图:


    2.在弹出的Create New Module对话框中选择Java Library,命名为MockButterknife-complier,如下图:

    3.创建注解处理器类,继承AbstractProcessor,覆盖getSupportedAnnotationTypes()、getSupportedSourceVersion()、process()三个方法,如下图:

    4.注册注解处理器,在项目下创建resources->META-INF->Services目录,在Services目录下创建javax.annotation.processing.Processor文件,如下图:

    5.编辑javax.annotation.processing.Processor文件,添加注解处理器类,如下图:

    6.配置注解处理器,添加JavaPoet,如下:
    apply plugin: 'java-library'
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        compile 'com.squareup:javapoet:1.8.0'
    }
    

    7.创建自定义注解,咱们在这里创建两个注解:

    • BindView注解
    package com.hys.mockbutterknife.annotations;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.CLASS)
    public @interface BindView {
        int value();
    }
    
    • OnClick注解
    package com.hys.mockbutterknife.annotations;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.CLASS)
    public @interface OnClick {
        int[] value();
    
    

    8.注册自定义注解到注解处理器,在AnnotationProcessor添加如下代码:

    private Set<Class<? extends Annotation>> getSupportedAnnotations(){
            Set<Class<? extends Annotation>> supportedAnnotations = new LinkedHashSet<>();
            supportedAnnotations.add(BindView.class);
            supportedAnnotations.add(OnClick.class);
            return supportedAnnotations;
        }
    

    在getSupportedAnnotationTypes()方法中调用getSupportedAnnotations(),即将自定义注解注册到注解处理器,代码如下:

     @Override
        public Set<String> getSupportedAnnotationTypes() {
            Set<String> supportedAnnotationTypes = new LinkedHashSet<>();
    
            Iterator ite = getSupportedAnnotations().iterator();
            while (ite.hasNext()){
                Class annotation = (Class<? extends Annotation>)ite.next();
                supportedAnnotationTypes.add(annotation.getCanonicalName());
            }
    
            return supportedAnnotationTypes;
        }
    

    9.上面咱们已经注册了自定义注解,接下来应该处理这些注解(啰嗦,不处理,注册它们做啥?!)

    后面以BindView为例

    查找添加注解的元素

    Iterator ite = env.getElementsAnnotatedWith(BindView.class).iterator();
    

    验证元素合法性

    • 验证元素是否可以访问
    private boolean isInaccessible(Element element, String targetThing, Class<? extends Annotation> annotationClass) {
    
            TypeElement enclosingElement = (TypeElement)element.getEnclosingElement();
            //检查元素的访问修饰符
            Set<Modifier> modifiers = element.getModifiers();
            if (modifiers.contains(Modifier.PRIVATE) || modifiers.contains(Modifier.STATIC)) {
                this.error(element, "@%s %s must not be private or static. (%s.%s)", annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(), element.getSimpleName());
                return true;
            }
    
            //检查元素的父元素
            if (enclosingElement.getKind() != ElementKind.CLASS) {
                this.error(enclosingElement, "@%s %s may only be contained in classes. (%s.%s)", annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(), element.getSimpleName());
                return true;
            }
    
            //检查父元素的访问修饰符
            if (enclosingElement.getModifiers().contains(Modifier.PRIVATE)) {
                this.error(enclosingElement, "@%s %s may not be contained in private classes. (%s.%s)", annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(), element.getSimpleName());
                return true;
            }
    
            return false;
        }
    
    • 验证元素所在包的合法性
    private boolean isInWrongPackage(Element element, Class<? extends Annotation> annotationClass) {
    
            TypeElement enclosingElement = (TypeElement)element.getEnclosingElement();
            String qualifiedName = enclosingElement.getQualifiedName().toString();
            //元素的父元素(即元素所在的类)不能在android的系统包中
            if (qualifiedName.startsWith("android.")) {
                this.error(element, "@%s-annotated class incorrectly in Android framework package. (%s)", annotationClass.getSimpleName(), qualifiedName);
                return true;
            } 
            ////元素的父元素不能在java的资源包中
            else if (qualifiedName.startsWith("java.")) {
                this.error(element, "@%s-annotated class incorrectly in Java framework package. (%s)", annotationClass.getSimpleName(), qualifiedName);
                return true;
            }
    
            return false;
        }
    
    • 验证元素类型的合法性
    /*
    * 递归验证
    * 以TextView为例:isSubtypeOfType(typeMirror, "android.view.View")
    */
    public static boolean isSubtypeOfType(TypeMirror typeMirror, String otherType) {
            // 类型相同
            if (isTypeEqual(typeMirror, otherType))
                return true;
    
            if (typeMirror.getKind() != TypeKind.DECLARED)
                return false;
    
            DeclaredType declaredType = (DeclaredType)typeMirror;
            List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
            if (typeArguments.size() > 0) {
                StringBuilder typeString = new StringBuilder(declaredType.asElement().toString());
                typeString.append('<');
    
                for(int i = 0; i < typeArguments.size(); ++i) {
                    if (i > 0) {
                        typeString.append(',');
                    }
    
                    typeString.append('?');
                }
    
                typeString.append('>');
                if (typeString.toString().equals(otherType)) {
                    return true;
                }
            }
    
            Element element = declaredType.asElement();
            if (!(element instanceof TypeElement)) {
                return false;
            } else {
                TypeElement typeElement = (TypeElement)element;
                // 获取元素的父类
                TypeMirror superType = typeElement.getSuperclass();
                // 检查父类的类型
                if (isSubtypeOfType(superType, otherType)) {
                    return true;
                } else {                
                    Iterator var7 = typeElement.getInterfaces().iterator();
    
                    TypeMirror interfaceType;
                    do {
                        if (!var7.hasNext()) {
                            return false;
                        }
    
                        interfaceType = (TypeMirror)var7.next();
                    } while(!isSubtypeOfType(interfaceType, otherType));
    
                    return true;
                }
            }
    
        }
    

    生成Java源文件

    • 生成类
    private TypeSpec createTypeSpec(){
            // 生成新类名,原类名+ _ViewBinding
            String className = this.encloseingElement.getSimpleName().toString() + "_ViewBinding";
            // 获取父元素的类型全称
            TypeName targetTypeName = TypeName.get(this.encloseingElement.asType());
    
            // 创建类构建器
            TypeSpec.Builder classBuilder = TypeSpec.classBuilder(className)
                    .addModifiers(new Modifier[]{Modifier.PUBLIC}) // 添加public修饰符
                    .addField(targetTypeName, "target", new Modifier[]{Modifier.PRIVATE}); // 添加成员变量target
    
            classBuilder.addFields(createFieldForListener());
          
            if(isActivity()){
                classBuilder.addMethod(createConstructorForActivity());
            } else if(isView()){
                classBuilder.addMethod(createConstructorForView());
            } else if(isDialog()){
                classBuilder.addMethod(createConstructorForDialog());
            }
    
            // 默认类构造器
            classBuilder.addMethod(createBindConstructor());
            // 生成类
            return classBuilder.build();
        }
    
    • 生成JavaFile对象
    public JavaFile brewJava() {
            String packageName = MoreElements.getPackage(this.encloseingElement).getQualifiedName().toString();
            return JavaFile.builder(packageName, createTypeSpec()).build();
        }
    
    • 生成Java源文件
    ...
    JavaFile javaFile = bindSet.brewJava();
    
    try{
          javaFile.writeTo(this.processingEnv.getFiler());
    }catch (IOException ex){
         this.error(typeElement, "Unable to write binding for type %s: %s", typeElement, ex.getMessage());
    }
    ...
    

    创建API

    注解处理器搞好了,还需要给用户提供API,用户才能使用。

    咱们创建一个新的Module,Android Studio的File->New->New module,选择Android Library,命名为Mockbutterknife-source

    这个Module主要使用反射技术,动态的创建并调用上文中生成的类(下文中称为绑定类)。

    • 编写API接口(其中之一)
        @UiThread
        public static void bind(Activity target) {
            View sourceView = target.getWindow().getDecorView();
            createBinding(target, sourceView);
        }
    
    • 动态创建绑定类,调用其构造器方法
    private static void createBinding(Object target, View source) {
            Class<?> targetClass = target.getClass();
            // 查找targetClass名称+_ViewBinding的class文件,加载并返回构造器
            Constructor constructor = findBindConstructorForClass(targetClass);
    
            if (constructor == null) {
                return ;
            }
    
            try {
                constructor.newInstance(target, source);
            } catch (IllegalAccessException e) {
                throw new RuntimeException("Unable to invoke " + constructor, e);
            } catch (InstantiationException e) {
                throw new RuntimeException("Unable to invoke " + constructor, e);
            } catch (InvocationTargetException e) {
                Throwable cause = e.getCause();
                if (cause instanceof RuntimeException) {
                    throw (RuntimeException) cause;
                }
                if (cause instanceof Error) {
                    throw (Error) cause;
                }
                throw new RuntimeException("Unable to create binding instance.", cause);
            }
        }
    

    在APP中使用

    • 配置APP,在build.gradle中添加如下内容:
    dependencies {
        ...
        annotationProcessor project(':MockButterknife-complier') 
        implementation project(path: ':MockButterknife-complier')
        implementation project(path: ':Mockbutterknife-source')
    
    • 为Activity添加自定义注解
    public class MainActivity extends AppCompatActivity {
    
        @BindView(R.id.tv_click)
        TextView tvClick;
        @BindView(R.id.tv_dont_click)
        TextView tvDontClcik;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            MockButterKnife.bind(this);
    
            initData();
        }
    
       ...
    
        @OnClick(value = {R.id.tv_click, R.id.tv_dont_click})
        public void onClick(View view){
    
            if(view.getId() == R.id.tv_click)
                new AboutDialog().show(this.getSupportFragmentManager());
            else if(view.getId() == R.id.tv_dont_click)
                Toast.makeText(this, getString(R.string.main_toast), Toast.LENGTH_SHORT).show();
        }
    }
    
    • 生成的class文件
    package com.hys.annotationprocessortest;
    
    import android.support.annotation.UiThread;
    import android.view.View;
    import android.widget.TextView;
    
    public class MainActivity_ViewBinding {
      private MainActivity target;
    
      private View view2131165309;
    
      private View view2131165310;
    
      @UiThread
      public MainActivity_ViewBinding(MainActivity target) {
        this(target, target.getWindow().getDecorView());
      }
    
      public MainActivity_ViewBinding(final MainActivity target, View source) {
        this.target = target;
        this.target.tvClick = (TextView)source.findViewById(2131165309);
        this.target.tvDontClcik = (TextView)source.findViewById(2131165310);
        this.view2131165309 = source.findViewById(2131165309);
        this.view2131165309.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View v) {
               target.onClick(v);
           }
        });
        this.view2131165310 = source.findViewById(2131165310);
        this.view2131165310.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View v) {
               target.onClick(v);
           }
        });
      }
    }
    

    好了,关于如何构建编译时注解解析框架,就先讲到这,上述项目的具体代码在Github,感谢你耐心的阅读。


    我是青岚之峰,如果读完后觉的有所收获,欢迎点赞加关注

    相关文章

      网友评论

        本文标题:如何构建编译时注解解析框架

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