Annotation应用层的知识归纳

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

2018-02-16  本文已影响30人  青岚之峰

前言

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

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

铺垫

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

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里面常用的几个类:

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

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占位符如下:

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

javax.lang.model.element

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

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

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

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

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

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

TypeKind
表示类型的种类。

以下是常用的类型:

获取元素的父元素
通过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.创建自定义注解,咱们在这里创建两个注解:

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();
}
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();
    }
public JavaFile brewJava() {
        String packageName = MoreElements.getPackage(this.encloseingElement).getQualifiedName().toString();
        return JavaFile.builder(packageName, createTypeSpec()).build();
    }
...
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主要使用反射技术,动态的创建并调用上文中生成的类(下文中称为绑定类)。

    @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中使用

dependencies {
    ...
    annotationProcessor project(':MockButterknife-complier') 
    implementation project(path: ':MockButterknife-complier')
    implementation project(path: ':Mockbutterknife-source')
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();
    }
}
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,感谢你耐心的阅读。


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

上一篇下一篇

猜你喜欢

热点阅读