山鬼

reflection, annotation, ButterKnife与Ophelia

字数统计: 1.6k阅读时长: 7 min
2018/11/10 Share

这篇文章,从反射和注解讲起,然后分析著名的android主街框架ButterKnife,最后写了个自己的轻量级注解项目Ophelia。


反射 reflection

定义

  • Reflection is commonly used by programs which require the ability to examine or modify the runtime behavior of applications running in the Java virtual machine.

Oracle在Reflection api的开篇就是这么一句话。反射是指程序在运行时检查和修改自身的能力。在运行时,JVM可以获得任意一个类或任意一个对象的属性和方法。

在android源码中,用处很多,比如Instrumentation的构造器方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public Activity newActivity(Class<?> clazz, Context context, 
IBinder token, Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
Object lastNonConfigurationInstance) throws InstantiationException,
IllegalAccessException {
Activity activity = (Activity)clazz.newInstance();
ActivityThread aThread = null;
// Activity.attach expects a non-null Application Object.
if (application == null) {
application = new Application();
}
activity.attach(context, aThread, this, token, 0 /* ident */, application, intent,
info, title, parent, id,
(Activity.NonConfigurationInstances)lastNonConfigurationInstance,
new Configuration(), null /* referrer */, null /* voiceInteractor */,
null /* window */, null /* activityConfigCallback */);
return activity;
}

上面的代码,用newInstance,调用默认的构造函数创建了Activity对象。

反射,就是在运行时,将class文件的各个成分映射成java对象,类、构造函数、成员函数、属性、包等等。

class

获取类,有三种方式。

1
2
3
4
5
6
7
8
// 获取类,三种方式
Class<?> class1 = response.getClass();//方法一
Class<?> class2 = response.class;//方法二
try{
class3 = Class.forName("com.jascal.Response");//方法三
}catch(ClassNotFoundException e){
e.printStackTrace();
}

object

获取创建类对象,关键在于newInstance和constructor

1
2
3
4
5
6
7
Class clazz = null;
clazz = Class.forName("com.jascal.Response");
Constructor<Response> constructor1 = clazz.getConstructor();
Constructor<Response> constructor2 = clazz.getConstructor(String.class);

Fruit fruit1 = constructor1.newInstance();
Fruit fruit2 = constructor2.newInstance("message");

method && field

获取类的方法及属性

1
2
3
4
5
6
method = clazz.getMethod("setMessage",null);//获取无参方法
method.invoke(fruit,null);//执行无参方法

method = clazz.getMethod("setMessage",String.class); //获取有参方法
method.invoke(fruit,"result OK");//执行有参方法


注解 annotation

定义

  • An annotation is a form of metadata, that can be added to Java source code. Classes, methods, variables, parameters and packages may be annotated. Annotations have no direct effect on the operation of the code they annotate.

注解,在butterknife,retrofit,dagger里都有应用。注解本身,是可以添加在java源码中的一种元数据(这个元数据是直译,本身并不是java数据的意思,而是代表源码中的一份子,只是一种标记)。类,方法,变量,参数和包都可以添加注解。注解对被注解的操作没有直接作用(本身是种标记,更深层的,是通过后续的相关处理,比如代码动态生成等,间接地影响程序本身)。

接下来说说一些关键词,还是以源码为主。

@interface

@interface是注解关键字,直观上相当于interface,看栗子

1
2
3
4
5
6
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}

元注解

在上面的代码中,应该有注意到@Retention,@Target,这两个就是元注解,元注解是修饰注解的注解。

元注解有四种,@Retention, @Target, @Inherited, @Documented。

  • @Retention 保留的范围,默认值为CLASS. 可选值有三种
    • SOURCE, 只在源码中可用
    • CLASS, 在源码和字节码中可用
    • RUNTIME, 在源码,字节码,运行时均可用
  • @Target 可以用来修饰哪些程序元素,如 TYPE, METHOD, CONSTRUCTOR, FIELD, PARAMETER等,未标注则表示可修饰所有
  • @Inherited 是否可以被继承,默认为false
  • @Documented 是否会保存到 Javadoc 文档中

@Retention是定义保留策略, 直接决定了我们用何种方式解析. SOUCE级别的注解是用来标记的, 比如Override, SuppressWarnings. 我们真正使用的类型是CLASS(编译时)和RUNTIME(运行时)

运行时注解

运行时注解,即@Retention(RetentionPolicy.RUNTIME),这类注解会保留到运行时,通过反射处理。举个栗子,就像上面定义的注解BindView,反射处理的方式,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
try {
Class cls = Class.forName("com.jascal.testAnnotation");// 获取类
Field[] declaredFields = cls.getDeclaredFields();// 获取类中的field,参考反射,包括类或接口中的可访问字段
for(Field field : declaredFields){
BindView bindView = field.getAnnotation(BindView.class);// 获取field的注解
if(bindView != null){
String value = bindView.value();// 这里就是注解的传参
System.out.println(value);
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}

基于反射,势必会影响响应速度。

编译时注解

编译时注解,即@Retention(RetentionPolicy.CLASS)。
解析编译时注解,需要继承AbstractProcessor,实现其中的抽象方法process

1
2
3
4
5
6
public abstract class AbstractProcessor implements Processor {
...
public abstract boolean process(Set<? extends TypeElement> var1, RoundEnvironment var2);
...
}

该方法返回ture表示该注解已经被处理, 后续不会再有其他处理器处理; 返回false表示仍可被其他处理器处理。处理方式因项目而异,这里只写下基本形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@SupportedAnnotationTypes("myAnnotation.TestAnnotation")// 指定目标注解
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class MyAnnotationProcesser extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (TypeElement te : annotations) {
for (Element element : roundEnv.getElementsAnnotatedWith(te)) {
TestAnnotation testAnnotation = element.getAnnotation(TestAnnotation.class);
// do something
}
}
return true;
}
}

在自定义Processor类中,要注意以下几点

  • @AutoService(Processor.class),谷歌提供的注解注册工具,需要在gradle中加依赖
    1
    2
    3
    dependencies {
    compile 'com.google.auto.service:auto-service:1.0-rc3'
    }
  • void init(ProcessingEnvironment processingEnvironment),初始化类
  • Set getSupportedAnnotationTypes(),获取该processor所支持解析的注解类。
  • SourceVersion getSupportedSourceVersion(),获取java版本
  • boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment), 解析的入口函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {

private Types typeUtils; //类型工具
private Elements elementUtils;//元素工具
private Filer filer; //文件管理器
private Messager messager;//处理异常

@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
typeUtils = processingEnv.getTypeUtils();//获取类的信息
elementUtils = processingEnv.getElementUtils();//获取程序的元素。如、包、类、函数
filer = processingEnv.getFiler();//生成java文件
messager = processingEnv.getMessager();//处理错误日志
}

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
return false;
}

@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotations = new LinkedHashSet<String>();
annotations.add(BindString.class.getCanonicalName());
return annotations;
}

@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}

编译时注解,解析的过程就是代码生成的过程,代码生成是编译时的过程,不影响运行时速度。代码生成这一部分写在下个章节,自定义注解。

CATALOG
  1. 1. 反射 reflection
    1. 1.1. 定义
    2. 1.2. class
    3. 1.3. object
    4. 1.4. method && field
  2. 2. 注解 annotation
    1. 2.1. 定义
    2. 2.2. @interface
    3. 2.3. 元注解
    4. 2.4. 运行时注解
    5. 2.5. 编译时注解