Java Annotation及注解原理简析

时间:2021-07-18 20:38:25


参考资料:http://www.trinea.cn/android/java-annotation-android-open-source-analysis/

                     http://www.cnblogs.com/mandroid/archive/2011/07/18/2109829.html

                     https://github.com/android-cn/android-open-project-analysis/tree/master/dagger

一、Annotation 示例:

Override Annotation

@Override
publicvoidonCreate(BundlesavedInstanceState);

Retrofit Annotation

@GET("/users/{username}")
User getUser(@Path("username")Stringusername);

Butter Knife Annotation

@InjectView(R.id.user)EditTextusername;

ActiveAndroid Annotation

@Column(name=Name")publicStringname;

Retrofit 为符合 RESTful 规范的网络请求框架
Butter Knife 为 View 及事件等依赖注入框架
Active Android 为 ORM 框架

二、Annotation 概念及作用:

1 、概念

          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.

Annotation其实是一种接口。通过Java的反射机制相关的API来访问annotation信息。相关类(框架或工具中的类)根据这些信息来决定如何使用该程序元素或改变它们的行为。

annotation是不会影响程序代码的执行,无论annotation怎么变化,代码都始终如一地执行。

Java语言解释器在工作时会忽略这些annotation,因此在JVM中这些annotation是“不起作用”的,只能通过配套的工具才能对这些annontaion类型的信息进行访问和处理。

Annotation与interface的异同

1)、Annotation类型使用关键字@interface而不是interface。

这个关键字声明隐含了一个信息:它是继承了java.lang.annotation.Annotation接口,并非声明了一个interface

2)、Annotation类型、方法定义是独特的、受限制的。

Annotation类型的方法必须声明为无参数、无异常抛出的。这些方法定义了annotation的成员:方法名成为了成员名,而方法返回值成为了成员的类型。

方法返回值类型必须为primitive类型、Class类型、枚举类型、annotation类型或者由前面类型之一作为元素的一维数组。

方法的后面可以使用default和一个默认数值来声明成员的默认值,null不能作为成员默认值,这与我们在非annotation类型中定义方法有很大不同。

Annotation类型和它的方法不能使用annotation类型的参数、成员不能是generic。只有返回值类型是Class的方法可以在annotation类型中使用generic,

因为此方法能够用类转换将各种类型转换为Class。

3)、Annotation类型又与接口有着近似之处

它们可以定义常量、静态成员类型(比如枚举类型定义)。Annotation类型也可以如接口一般被实现或者继承。

2 、作用

a. 标记,用于告诉编译器一些信息

b. 编译时动态处理,如动态生成代码

c. 运行时动态处理,如得到注解信息

三、Annotation 分类

1 、标准 Annotation

包括Override, Deprecated, SuppressWarnings,标准 Annotation 是指 Java 自带的几个 Annotation,上面三个分别表示重写函数,函数已经被禁止使用,忽略某项 Warning

(1)、Override

  java.lang.Override是一个markerannotation类型,它被用作标注方法。它说明了被标注的方法重载了父类的方法,起到了断言的作用。如果我们使用了这种annotation在一个没有覆盖父类方法的方法时,java编译器将以一个编译错误来警示。这个annotaton常常在我们试图覆盖父类方法而确又写错了方法名时加一个保障性的校验过程。

(2)、Deprecated

  Deprecated也是一种markerannotation。当一个类型或者类型成员使用@Deprecated修饰的话,编译器将不鼓励使用这个被标注的程序元素。所以使用这种修饰具有一定的“延续性”:如果我们在代码中通过继承或者覆盖的方式使用了这个过时的类型或者成员,虽然继承或者覆盖后的类型或者成员并不是被声明为@Deprecated,但编译器仍然要报警。

注意:@Deprecated这个annotation类型和javadoc中的@deprecated这个tag是有区别的:前者是java编译器识别的,而后者是被javadoc工具所识别用来生成文档(包含程序成员为什么已经过时、它应当如何被禁止或者替代的描述)。

(3)、SuppressWarnings

  此注解能告诉Java编译器关闭对类、方法及成员变量的警告。

  有时编译时会提出一些警告,对于这些警告有的隐藏着Bug,有的是无法避免的,对于某些不想看到的警告信息,可以通过这个注解来屏蔽。

        SuppressWarning不是一个markerannotation。它有一个类型为String[]的成员,这个成员的值为被禁止的警告名。对于javac编译器来讲,被-Xlint选项有效的警告名也同样对@SuppressWarings有效,同时编译器忽略掉无法识别的警告名。annotation语法允许在annotation名后跟括号,括号中是使用逗号分割的name=value对用于为annotation的成员赋值:

@SuppressWarnings(value={"unchecked","fallthrough"})

public void lintTrap() { /* sloppy method body omitted */ }

2、 元 Annotation

@Retention, @Target, @Inherited, @Documented,元 Annotation 是指用来定义 Annotation 的 Annotation,在后面 Annotation 自定义部分会详细介绍含义

3 自定义 Annotation

自定义 Annotation 表示自己根据需要定义的 Annotation,定义时需要用到上面的元 Annotation

四、Annotation 自定义

1 、调用

publicclassApp{
 
    @MethodInfo(
        author=trinea.cn+android@gmail.com,
        date="2014/02/14",
        version=2)
    publicStringgetAppName(){
        return"trinea";
    }
}

这里是调用自定义 Annotation——MethodInfo 的示例,MethodInfo Annotation 作用为给方法添加相关信息,包括 author、date、version

2 、定义

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
public@interfaceMethodInfo{
 
    Stringauthor()default"trinea@gmail.com";
 
    Stringdate();
 
    intversion()default1;
}

(1). 通过 @interface 定义,注解名即为自定义注解名

(2). 注解配置参数名为注解类的方法名,且:

a. 所有方法没有方法体,没有参数没有修饰符,实际只允许 public & abstract 修饰符,默认为 public ,不允许抛异常

b. 方法返回值只能是基本类型,String, Class, annotation, enumeration 或者是他们的一维数组

c. 若只有一个默认属性,可直接用 value() 函数。一个属性都没有表示该 Annotation 为 Mark Annotation

(3). 可以加 default 表示默认值

3 、元 Annotation

@Documented 是否会保存到 Javadoc 文档中
@Retention 保留时间,可选值 SOURCE(源码时),CLASS(编译时),RUNTIME(运行时),默认为 CLASS,值为 SOURCE 大都为 Mark Annotation,这类 Annotation 大都用来校验,比如 Override, Deprecated, SuppressWarnings
@Target 可以用来修饰哪些程序元素,如 TYPE, METHOD, CONSTRUCTOR, FIELD, PARAMETER 等,未标注则表示可修饰所有
@Inherited 是否可以被继承,默认为 false

五、Annotation 解析

1、 运行时 Annotation 解析

(1) 运行时 Annotation 指 @Retention 为 RUNTIME 的 Annotation,可手动调用下面常用 API 解析

method.getAnnotation(AnnotationName.class);
method.getAnnotations();
method.isAnnotationPresent(AnnotationName.class);

其他 @Target 如 Field,Class 方法类似
getAnnotation(AnnotationName.class) 表示得到该 Target 某个 Annotation 的信息,因为一个 Target 可以被多个 Annotation 修饰
getAnnotations() 则表示得到该 Target 所有 Annotation
isAnnotationPresent(AnnotationName.class) 表示该 Target 是否被某个 Annotation 修饰

(2) 解析示例如下:

publicstaticvoidmain(String[]args){
    try{
        Classcls=Class.forName("cn.trinea.java.test.annotation.App");
        for(Methodmethod:cls.getMethods()){
            MethodInfomethodInfo=method.getAnnotation(
MethodInfo.class);
            if(methodInfo!=null){
                System.out.println("method name:" +method.getName());
                System.out.println("method author:" +methodInfo.author());
                System.out.println("method version:" +methodInfo.version());
                System.out.println("method date:" +methodInfo.date());
            }
        }
    }catch(ClassNotFoundExceptione){
        e.printStackTrace();
    }
}

以之前自定义的 MethodInfo 为例,利用 Target(这里是 Method)getAnnotation 函数得到 Annotation 信息,然后就可以调用 Annotation 的方法得到响应属性值

2 、编译时 Annotation 解析

(1) 编译时 Annotation 指 @Retention 为 CLASS 的 Annotation,甴apt(Annotation Processing Tool) 解析自动解析。需要做的
a. 自定义类集成自 AbstractProcessor
b. 重写其中的 process 函数
这块很多同学不理解,实际是
apt(Annotation Processing Tool) 在编译时自动查找所有继承自 AbstractProcessor 的类,然后调用他们的 process 方法去处理
(2) 假设之前自定义的 MethodInfo 的 @Retention 为 CLASS,解析示例如下:

@SupportedAnnotationTypes({"cn.trinea.java.test.annotation.MethodInfo"})
publicclassMethodInfoProcessorextends AbstractProcessor{
 
    @Override
    publicbooleanprocess(Set<?extendsTypeElement>annotations,RoundEnvironmentenv){
        HashMap<String,String>map=newHashMap<String,String>();
        for(TypeElementte :annotations){
            for(Elementelement:env.getElementsAnnotatedWith(te)){
                MethodInfomethodInfo=element.getAnnotation(MethodInfo.class);
                map.put(element.getEnclosingElement().toString(),methodInfo.author());
            }
        }
        returnfalse;
    }
}

SupportedAnnotationTypes 表示这个 Processor 要处理的 Annotation 名字。
process 函数中参数 annotations 表示待处理的 Annotations,参数 env 表示当前或是之前的运行环境
process 函数返回值表示这组 annotations 是否被这个 Processor 接受,如果接受后续子的 rocessor 不会再对这个 Annotations 进行处理

六、 Android 开源库 Annotation 原理简析

1 、Annotation — Retrofit

(1)、 调用


@GET("/users/{username}")
User getUser(@Path("username")Stringusername);

(2) 、定义
@Documented
@Target(METHOD)
@Retention(RUNTIME)
@RestMethod("GET")
public@interfaceGET{
  Stringvalue();
}
从定义可看出 Retrofit 的 Get Annotation 是运行时 Annotation,并且只能用于修饰 Method

(3) 、原理

privatevoidparseMethodAnnotations(){
    for(AnnotationmethodAnnotation:method.getAnnotations()){
    Class<?extendsAnnotation>annotationType= methodAnnotation.annotationType();
    RestMethodmethodInfo=null;
 
    for(AnnotationinnerAnnotation:annotationType.getAnnotations()){
        if(RestMethod.class==innerAnnotation.annotationType()){
            methodInfo=(RestMethod)innerAnnotation;
            break;
        }
    }   ……
    }
} 
RestMethodInfo.java 的 parseMethodAnnotations 方法如上,会检查每个方法的每个 Annotation, 看是否被 RestMethod 这个 Annotation 修饰的 Annotation 修饰,这个有点绕,就是是否被 GET、DELETE、POST、PUT、HEAD、PATCH 这些 Annotation 修饰,然后得到 Annotation 信息,在对接口进行动态代理时会掉用到这些 Annotation 信息从而完成调用。

2 、Annotation — Dagger

Dagger 相关概念:

Module:也叫 ModuleClass,指被 @Module 注解修饰的类,为 Dagger 提供需要依赖注入的 Host 信息及一些 Dependency 的生成方式。

ModuleAdapter:指由 APT 根据 @Module 注解自动生成的类,父类是 Dagger 的 ModuleAdapter.java,与 ModuleClass 对应,以 ModuleClass 的 ClassName 加上 $$ModuleAdapter 命名,在 ModuleClass 的同一个 package 下。

Binding:指由 APT 根据 @Inject 注解和 @Provides 注解自动生成,最终继承自 Binding.java 的类。为下面介绍的 DAG 图中的一个节点,每个 Host 及依赖都是一个 Binding

InjectAdapter:每个属性或构造函数被 @Inject 修饰的类都会生成一个 继承自 Binding.java 的子类,生成类以修饰类的 ClassName 加上 $$InjectAdapter 命名,在该类的同一个 package 下。

ProvidesAdapter:每个被 @Provides 修饰的生成函数都会生成一个继承自 ProvidesBinding.java 的子类,ProvidesBinding.java 继承自 Binding.java,生成类以 Provide 函数名首字母大写加上 ProvidesAdapter 命名,是 Provide 函数所在 Module 对应生成的ModuleAdapter中的静态内部类。Binding 更具体信息在下面会介绍。

Binding 安装:指将 Binding 添加到 Binding 库中。对 Dagger Linker.java 代码来说是将 Binding 添加到 Linker.bindings 属性中,Linker.bindings 属性表示某个 ObjectGraph 已安装的所有 Binding。对于下面的 DAG 图来说是将节点放到图中,但尚未跟其他任何节点连接起来。

Binding 连接:把当前 Binding 和它内部依赖的 Binding 进行连接,即初始化这个 Binding 内部的所有 Binding,使它们可用。对 DAG 的角度说,就是把某个节点与其所依赖的各个节点连接起来。

总体设计:

Dagger 是一个基于DAG(有向无环图结构)的依赖注入库,DAG 是数据结构的一种。在一组节点中,每一个节点指向一个或多个节点,但不存在一条正向的链最终重新指向自己(即不存在环),这样的结构称为有向无环图;Dagger 的运作机制,是运用APT(Annotation Process Tool) 在编译时生成一些用于设定规则的代码,然后在运行时将这些规则进行动态组合,生成一个(或多个)DAG,然后由 DAG 来完成所有依赖的获取,实现依赖注入。下面是Dagger 中依赖注入与 DAG 的关系

Java Annotation及注解原理简析

上图代表了某个应用程序内的一整套依赖关系,其中每个箭头都表示两个类之间依赖关系,Host 和 Dependency 都是其中的一个节点。

可以看出,一个程序中的整套依赖关系其实就是一个 DAG。而实际上,Dagger 也是这么做的:预先建立一个 DAG,然后在需要获取对象的时候通过这个依赖关系图来获取到对象并返回,若获取失败则进行查找,查找到后再补充到 DAG 中。

Dagger 是支持传递依赖的。例如在上图中,当需要获取一个 CustomView,会首先获取一个 DataHelper 作为获取 CustomView 的必要参数;此时如果 DataHelper 还未初始化,则还要分别拿到 HttpHelper 和 Database 用来初始化 DataHelper;以此类推。

工作流程

1). 编译时,通过 APT 查看所有 java 文件,检查并根据注解生成一些新的 java 文件

编译时检查:

        Dagger 会在编译时对代码进行检查,并在检查不通过的时候报编译错误,具体原因请看下面的详细原理介绍。检查内容主要有三点:

1). 所有需要依赖注入的类,需要被显式声明在相应的Module中。

2). 一个Module中所有 @Provides 函数的参数都必须在这个 Module 中提供相应的被 @Provides 修饰的函数,或者在 @Module 注解后添加 "complete = false" 注明这是一个不完整 Module,表示它依赖不属于这个 Module 的其他 Denpendency。

3). 一个Module中所有的 @Provides 函数都要被它声明的注入对象所使用,或者在 @Module 注解后添加 "library = ture" 注明它含有对外的 Denpendency,可能被其他Module依赖。

InjectAdapterProvidesAdapterModuleAdapter,这些文件用于运行时辅助 DAG 的创建和完善。然后,将这些新生成的 java 文件和项目原有的 java 文件一并编译成 class 文件。


2). 运行时,在 Application 或某个具体模块的初始化处,使用ObjectGraph类来加载部分依赖(实质上是利用编译时生成的ModuleAdapters加载了所有的ProvidesBinding),形成一个不完整的依赖关系图。

3). 这个不完整的依赖关系图生成之后,就可以调用ObjectGraph的相应函数来获取实例和注入依赖了。实现依赖注入的函数有两个:

ObjectGraph.get(Class<T> type)函数,用于直接获取对象;

ObjectGraph.inject(T instance)函数,用于对指定对象进行属性的注入。

在这些获取实例和注入依赖的过程中,如果用到了还未加载的依赖,程序会自动对它们进行加载(实质上是加载的编译时生成的InjectAdapter)。在此过程中,内存中的 DAG 也被补充地越来越完整。

详细设计

Java Annotation及注解原理简析

                    Dagger 整体框架最简类关系图。大致原理可以描述为:Linker通过Loader加载需要的Binding并把它们拼装成合理的依赖关系图 ,由ObjectGraph(其子类DaggerObjectGraph)最终实现依赖注入的管理。ObjectGraph 是个抽象类,DaggerObjectGraph 是它目前唯一的子类,对 Dagger 的调用实际都是对 DaggerObjectGraph 的调用。

Class功能详细介绍:

Binding 是一个泛型抽象类,相当于依赖关系 DAG 图中的节点,依赖关系 DAG 图中得每一个节点都有一个由 APT 生成的继承自 Binding 的类与之对应,而依赖关系 DAG 图中的每一个节点与HostDependency一一对应,所以每个HostDependency必然有一个由 APT 生成的继承自 Binding 的子类与之对应,我们先简单的将这些生成类分为HostBindingDependencyBinding

1). Binding.java 实现的接口

Binding.java 实现了两个接口,第一个是 javax 的Provider接口,此接口提供了 get() 函数用于返回一个Dependency实例,当然也可以是Host实例;

第二个接口是 Dagger 中的MembersInjector接口,此接口提供了 injectMembers() 用来向Host对象中注入(即设置)Dependency

单纯的DependencyBinding只要实现Provider接口,在 get() 函数中返回自己的实例即可。单纯的HostBinding只要实现MembersInjector

在 injectMembers() 函数中调用DependencyBinding的 get() 函数得到依赖,然后对自己的依赖进行注入即可。如果一个类既是Host又是Dependency

则与它对应的Binding这两个接口都需要实现。

如下的 Host 和 Dependency 类

public class Host {
@Inject Dependency dependency;
}

public class Dependency {
@Inject
public Dependency() {
……
}
}

由 APT 生成的 Binding 应该类似

public final class Host$$InjectAdapter extends Binding<Host> implements MembersInjector<Host> {
private Binding<Dependency> dependencyBinding;……

public void attach(Linker linker) {
dependencyBinding = (Dependency$$InjectAdapter)linker.requestBinding(……);
}

public void injectMembers(Host host) {
host.dependency = (Dependency)dependencyBinding.get();
}
}


public final class Dependency$$InjectAdapter extends Binding<Dependency> implements Provider<Dependency> {

public Dependency get() {
return new Dependency();
}
}

HostBinding指的是生成类 Host$$InjectAdapter,DependencyBinding指的是生成类 Dependency$$InjectAdapter,我们可以看到HostBinding的 attach 方法用于得到DependencyBinding的实例,然后在 injectMembers() 函数中通过调用这个实例的 get() 函数注入 Dependency,DependencyBinding 的 get() 函数就是调用Dependency的生成方法

Binding 分类

上面我们将生成的 Binding 子类简单分为了HostBindingDependencyBinding,实际根据前面的注入方式我们知道依赖的生成方式有 @Inject 和 @Provides 两种,对这两种方式,Dagger 生成 Binding 子类的规则不同。

对于 @Inject 方式的注入,APT 会在Dependency同一个 package 下以Dependency的 ClassName 加上 $$InjectAdapter 为类名生成一个 Binding 子类。对于 @Provides 方式的注入,@Provides 的生成函数必须写在某个Module内部,与此 Module 对应的ModuleAdapter(Module$$ModuleAdapter)内部会有一个此 @Provides 方式对应的 Binding 子类,继承自 Binding 的子类 ProvidesBinding,以 @Provides 函数名首字母大写加上 ProvidesAdapter 命名。

第一种是Host对应的 Binding,本文中我们统一称为HostBinding。这些HostBinding和被 @Module 修饰的Module injects 值中每个元素一一对应,他们提供 get()、injectMembers()、attach() 函数。第一种是Host对应的 Binding,本文中我们统一称为HostBinding。这些HostBinding和被 @Module 修饰的Module injects 值中每个元素一一对应,他们提供 get()、injectMembers()、attach() 函数。

第二种是 Inject Dependecy 对应的 Binding 子类,本文中我们统一称为InjectBinding。这些InjectBinding和所有含有 @Inject 修饰的构造函数的类一一对应,他们提供 get() 函数,不提供 injectMembers() 函数。如果它同时是个Host,也会提供 injectMembers() 函数。

第三种是 Provide Dependecy 对应的 Binding 子类,本文中我们统一称为ProvidesBindingProvidesBinding 和 @Module 类中的被 @Provides 修饰的函数一一对应,他们只提供 get() 函数,不提供 injectMembers() 函数。
上面三种 Binding 中,第一、二种会在 ObjectGraph.create 时加载进来,第三种在用的时候才会被动态加载。InjectBindingProvidesBinding统称为DependencyBinding

ObjectGraph 主要函数有:

1). create(Object... modules)  这是个静态的构造函数,用于返回一个 ObjectGraph 的实例,是使用 Dagger 调用的第一个函数。参数为 ModuleClass 对象,函数作用是根据 ModuleClass 构建一个依赖关系图。此函数实现会直接调用
DaggerObjectGraph.makeGraph(null, new FailoverLoader(), modules)  返回一个DaggerObjectGraph对象
2). inject(T instance)  抽象函数,表示向某个 Host 对象中注入依赖。
3). injectStatics()   抽象函数,表示向 ObjectGraph 中相关的 Host 注入静态属性。
4). get(Class type)  抽象函数,表示得到某个对象的实例,多用于得到依赖的实例
5). plus(Object... modules)  抽象函数,表示返回一个新的包含当前 ObjectGraph 中所有 Binding 的 ObjectGraph
6). validate()      抽象函数,表示对当前 ObjectGraph 做检查
DaggerObjectGraph:

      DaggerObjectGraph 是 ObjectGraph 的静态内部类,也是 ObjectGraph 目前唯一的子类。因为 ObjectGraph 的 create() 函数直接返回了 DaggerObjectGraph 对象,所以对 Dagger 的调用实际都是对 DaggerObjectGraph 的调用。

主要属性有:
1). Map injectableTypes    记录了所有需要被依赖注入的 Host 类型,以 Host 的 ClassName 加上一定规则前缀(// TODO)做为 key,以其所对应的 Module 为 value。
2). Map staticInjections    记录了所有需要被静态依赖注入的 Host 类型,以 Host 的 ClassName 加上一定规则前缀(// TODO)做为 key,以其所对应的 Module 为 value
3). Linker linker         Linker 是 负责调用 Loader 加载 Binding,存储并管理所有 Binding、调用 attach 方法初始化依赖的 DependencyBinding
4). Loader plugin      Loader 负责通过 ClassLoader 加载 APT 生成的ModuleAdapter类和InjectAdapter类

主要函数有:

1). makeGraph 函数

makeGraph 函数首先会通过 Modules.loadModules 函数得到所有的 ModuleAdapter;然后遍历所有 ModuleAdapter,将其中需要依赖注入的 Host 类型(injectableTypes)、需要静态静态注入的 Host 类型(staticInjections)、所有的 Binding(这里是ProvidesBinding)都保存下来,做为新的 DaggerObjectGraph 对象构造入参。另一种 Binding —— InjectBinding 会在需要用到的时候进行动态载入;第三步新建 Linker 保存上面的 Binding;最后用这些变量一起构建新的 DaggerObjectGraph 对象。

7). getInjectableTypeBinding(ClassLoader classLoader, String injectableKey, String key)

表示根据 key 得到某个 Binding。首先会从 ObjectGraph.injectableTypes 中得到其对应的 Module,然后通过 linker.requestBinding 查找其对应的 Binding,若未查找到的 Binding 或是尚未连接,则调用 linker.linkRequested() 得到 InjectBindng 并将其添加到 ObjectGraph 中,此时再次通过 linker.requestBinding 即可查找到其对应的 Binding,返回即可。 

8). linkInjectableTypes()

查找 injectableTypes 记录的所有需要被依赖注入的 Host 类型对应的HostBinding

9). linkStaticInjections()

查找 staticInjections 记录的所有需要被静态依赖注入的 Host 类型对应的HostBinding

10) linkEverything()

首先检查是否连接过,没有的话,则先调用 linkInjectableTypes() 和 linkStaticInjections() 将所有 HostBinding 添加到 Linker 中,然后调用 linker.linkAll() 进行全部 Binding 的依赖关联


1). makeGraph 函数   

1、Dagger基本使用

public class Boss {
...
@Inject
public Boss() {
...
}
...
}

需要注意的是,如果构造函数含有参数,Dagger 会在调用构造对象的时候先去获取这些参数(不然谁来传参?),所以你要保证它的参数也提供可被 Dagger 调用到的生成函数。Dagger 可调用的对象生成方式有两种:一种是用 @Inject 修饰的构造函数,上面就是这种方式。另外一种是用 @Provides 修饰的函数

2). Boss 对象怎样被设置到 Activity 中

通过 @Inject 注解了构造函数之后,在 Activity 中的 Boss 属性声明之前也添加 @Inject 注解。像这种在属性前添加的 @Inject 注解的目的是告诉 Dagger 哪些属性需要被注入。

public class MainActivity extends Activity {
@Inject Boss boss;
...
}

最后,我们在合适的位置(例如 onCreate() 函数中)调用 ObjectGraph.inject() 函数,Dagger 就会自动调用上面 (1) 中的生成方法生成依赖的实例,并注入到当前对象(MainActivity)。

public class MainActivity extendsActivity {
    @Inject Boss boss;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ObjectGraph.create(AppModule.class).inject(this);
    }
    ...
}

2、具体注入原理

APT 会在 MainActivity 所在 package 下生成一个辅助类 MainActivity$$InjectAdapter,这个类有个 injectMembers() 函数,代码类似:

public void injectMembers(MainActivity paramMainActivity) {
paramMainActivity.boss = ((Boss)boss.get());
……
}

上面我们已经通过 ObjectGraph.inject() 函数传入了 paramMainActivity,并且 boss 属性是 package 权限,所以 Dagger 只需要调用这个辅助类的 injectMembers() 函数即可完成依赖注入,这里的 boss.get() 会调用 Boss 的生成函数。到此为止,使用 Dagger 的 @Inject 方式将一个 Boss 对象注入到 MainActivity 的流程就完成了。

3). ObjectGraph.create(AppModule.class) 函数简介

下面介绍两个类,ObjectGraph 和 AppModule。其中 ObjectGraph 是由 Dagger 提供的类,可以简单理解为一个依赖管理类,它的 create() 函数的参数是一个数组,为所有需要用到的 Module(例如本例中的 AppModule)。AppModule 是一个自定义类,在 Dagger 中称为Module,通过 @Module 注解进行标记,代码如下:

@Module(injects = MainActivity.class)
public class AppModule {
}

可以看到,AppModule 是一个空类,除了一行注解外没有任何代码。

@Module 注解表示这个类是一个Module,Module 的作用是提供信息,让 ObjectGraph 知道哪些类对象需要被依赖注入,以及该怎么生成某些依赖例如,上面这段代码中声明了需要依赖注入的类为 MainActivity。需要在 Module 类中显式声明这些信息看起来很麻烦,多此一举的方式和 Dagger 的原理有关

3、自定义依赖生成方式

对构造函数进行注解是很好用的依赖对象生成方式,然而它并不适用于所有情况。例如:

1、接口(Interface)是没有构造函数的,当然就不能对构造函数进行注解

2、第三方库提供的类,我们无法修改源码,因此也不能注解它们的构造函数

3、有些类需要提供统一的生成函数(一般会同时私有化构造函数)或需要动态选择初始化的配置,而不是使用一个单一的构造函数

对于以上三种情况,可以使用 @Provides 注解来标记自定义的生成函数,从而被 Dagger 调用。形式如下:

@Provides
Coder provideCoder(Boss boss) {
return new Coder(boss);
}

和构造函数一样,@Provides 注解修饰的函数如果含有参数,它的所有参数也需要提供可被 Dagger 调用到的生成函数。
需要注意的是,所有 @Provides 注解的生成函数都需要在Module中定义实现,这就是上面提到的 Module 的作用之一——让 ObjectGraph 知道怎么生成某些依赖。

@Module
public class AppModule {
@Provides
Coder provideCoder(Boss boss) {
return new Coder(boss);
}
}
用法:
@Inject Coder lowLevelCoder;
使用@Inject告诉APT这个属性的实例化会以注解的方式完成,怎样完成呢?会调用module中的由@Providers注解的对应返回类型的函数,但是有可能会有不同类型的Coder对象 ,例如:

如果有两类程序员,他们的能力值 power 分别是 5 和 1000,应该怎样让 Dagger 对他们做出区分呢?使用 @Qualifier 注解即可

Qualifier(限定符)

(1). 创建一个 @Qualifier 注解,用于区分两类程序员:

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Level {
String value() default "";
}
(2). 为这两类程序员分别设置 @Provides 函数,并使用 @Qualifier 注解对他们做出不同的标记:
@Provides @Level("low") Coder provideLowLevelCoder() {
Coder coder = new Coder();
coder.setName("战五渣");
coder.setPower(5);
return coder;
}

@Provides @Level("high") Coder provideHighLevelCoder() {
Coder coder = new Coder();
coder.setName("大神");
coder.setPower(1000);
return coder;
}
(3). 在声明 @Inject 对象的时候,加上对应的 @Qualifier 注解。
@Inject @Level("low") Coder lowLevelCoder;
@Inject @Level("high") Coder highLevelCoder;
2). @Inject 和 @Provide 两种依赖生成方式区别

a. @Inject 用于注入可实例化的类,@Provides 可用于注入所有类

b. @Inject 可用于修饰属性和构造函数,可用于任何非 Module 类,@Provides 只可用于修饰非构造函数,并且该函数必须在某个Module内部

c. @Inject 修饰的函数只能是构造函数,@Provides 修饰的函数必须以 provide 开头