目录
  1. 1. 一、控制反转与依赖注入的概念
  2. 2. 二、三种注入方式
    1. 2.1. 2.1 构造函数注入(推荐方式)
    2. 2.2. 2.2 字段注入
    3. 2.3. 2.3 方法注入
  3. 3. 三、编译期 DI:Dagger/Hilt 原理
    1. 3.1. 3.1 核心概念
    2. 3.2. 3.2 Component 的实现原理:生成代码解析
    3. 3.3. 3.3 Subcomponent(子组件)
    4. 3.4. 3.4 Binds 与 Provides 的区别
  4. 4. 四、运行时 DI:Koin
  5. 5. 五、Dagger vs Koin vs Hilt
  6. 6. 六、面试常问题目
解读开源框架系列-IOC架构设计

一、控制反转与依赖注入的概念

控制反转(IoC, Inversion of Control)是一种设计原则,最通俗的理解是:不要让类自己创建它所依赖的对象,而是由外部(容器/框架)注入进来。依赖注入(DI, Dependency Injection)是 IoC 的一种具体实现方式。

在传统写法中,类自己控制依赖的创建:

public class NewsRepository {
private NewsApi newsApi;

public NewsRepository() {
// 类自己创建依赖——硬编码、不可测试
this.newsApi = new Retrofit.Builder()
.baseUrl("https://api.example.com")
.build()
.create(NewsApi.class);
}
}

使用依赖注入后:

public class NewsRepository {
private final NewsApi newsApi;

// 依赖由外部传入——可测试、可替换
@Inject
public NewsRepository(NewsApi newsApi) {
this.newsApi = newsApi;
}
}

依赖注入的三个核心好处:

  1. 可测试性:可以在测试中传入 mock 对象。
  2. 松耦合:NewsRepository 不需要知道 NewsApi 是如何创建的。
  3. 可替换性:更换实现只需修改注入配置,无需改动依赖方代码。

二、三种注入方式

2.1 构造函数注入(推荐方式)

public class UserViewModel {
private final UserRepository repository;

@Inject // Dagger 通过此注解标记构造函数
public UserViewModel(UserRepository repository) {
this.repository = repository;
}
}

构造函数注入的优势是:(1) 依赖在对象创建时就确定,不会出现 NPE;(2) 可以将依赖声明为 final;(3) 便于发现循环依赖——如果 A 依赖 B 且 B 依赖 A,编译时会检测到。

2.2 字段注入

public class MainActivity extends AppCompatActivity {
@Inject
UserViewModel viewModel; // 不可声明为 final

@Override
protected void onCreate(Bundle savedInstanceState) {
// 需要手动触发注入
((MyApplication) getApplication()).getAppComponent().inject(this);
super.onCreate(savedInstanceState);
}
}

字段注入的缺点:(1) 依赖不可 final;(2) 容易出现忘记触发注入的情况;(3) 在单元测试中难以替换依赖。Android 中主要用于 Activity/Fragment 等不能控制构造函数创建的对象。

2.3 方法注入

public class MainActivity extends AppCompatActivity {
private UserViewModel viewModel;

@Inject
public void setViewModel(UserViewModel viewModel) {
this.viewModel = viewModel;
}
}

方法注入使用较少,主要用于向后兼容(已经有 setter)或需要对注入的依赖做额外处理。

三、编译期 DI:Dagger/Hilt 原理

Dagger(https://github.com/google/dagger)是 Android 生态中最主流的编译期 DI 框架,由 Google 维护。其核心原理是通过 APT(Annotation Processing Tool)在编译期生成工厂类,完全避免运行时的反射开销。

3.1 核心概念

Component(组件):依赖注入的容器和桥梁。定义哪些模块提供什么依赖,注入给哪些目标。

@Component(modules = {NetworkModule.class, DatabaseModule.class})
@Singleton // Component 的作用域
public interface AppComponent {
// 暴露的依赖(供应点)
NewsApi getNewsApi();
UserDao getUserDao();

// 注入目标
void inject(MainActivity activity);
}

Module(模块):提供对象创建逻辑的地方,尤其是对第三方库(Retrofit、Room)的实例化。

@Module
public class NetworkModule {
@Provides
@Singleton // 与方法作用域对应
public NewsApi provideNewsApi() {
return new Retrofit.Builder()
.baseUrl("https://api.example.com")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(NewsApi.class);
}
}

Scope(作用域):控制依赖的生命周期。@Singleton 是内置的作用域,此外可自定义:

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope {}

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface FragmentScope {}

作用域确保:同一个 Component 实例中,被同一作用域标记的依赖只会创建一次。

Qualifier(限定符):区分同类型的多个依赖实例。

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface LoggingInterceptor {}

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthInterceptor {}

@Module
public class NetworkModule {
@Provides @LoggingInterceptor
public Interceptor provideLoggingInterceptor() {
return new HttpLoggingInterceptor();
}

@Provides @AuthInterceptor
public Interceptor provideAuthInterceptor() {
return chain -> chain.proceed(
chain.request().newBuilder()
.addHeader("Authorization", "Bearer " + token)
.build()
);
}
}

3.2 Component 的实现原理:生成代码解析

Dagger 为每个 @Component 注解的接口生成一个 DaggerXXXComponent 实现类。以下是实际的生成代码解析:

// 生成的 DaggerAppComponent.java(简化)
public final class DaggerAppComponent implements AppComponent {
private final NetworkModule networkModule;

private DaggerAppComponent(NetworkModule networkModuleParam) {
this.networkModule = networkModuleParam;
}

// 工厂方法
public static Builder builder() {
return new Builder();
}

@Override
public NewsApi getNewsApi() {
return NetworkModule_ProvideNewsApiFactory.provideNewsApi(networkModule);
}

@Override
public void inject(MainActivity activity) {
injectMainActivity(activity);
}

private MainActivity injectMainActivity(MainActivity instance) {
// 注入 ViewModel(如果 ViewModel 依赖也由 Dagger 管理)
MainActivity_MembersInjector.injectViewModel(instance,
new UserViewModel(DataModule_ProvideNewsApiFactory.provideNewsApi(networkModule)));
return instance;
}

public static final class Builder {
private NetworkModule networkModule;

public Builder networkModule(NetworkModule module) {
this.networkModule = Objects.requireNonNull(module);
return this;
}

public AppComponent build() {
if (networkModule == null) {
this.networkModule = new NetworkModule(); // 如果没有显式提供,使用默认构造
}
return new DaggerAppComponent(networkModule);
}
}
}

Dagger 生成的每个 @Provides 方法对应一个 Factory 类:

// 生成的 NetworkModule_ProvideNewsApiFactory.java
public final class NetworkModule_ProvideNewsApiFactory implements Factory<NewsApi> {
private final NetworkModule module;

public NetworkModule_ProvideNewsApiFactory(NetworkModule module) {
this.module = module;
}

@Override
public NewsApi get() {
return provideNewsApi(module);
}

public static NewsApi provideNewsApi(NetworkModule instance) {
return Preconditions.checkNotNull(
instance.provideNewsApi(),
"Cannot return null from a non-nullable @Provides method"
);
}

public static NetworkModule_ProvideNewsApiFactory create(NetworkModule module) {
return new NetworkModule_ProvideNewsApiFactory(module);
}
}

3.3 Subcomponent(子组件)

Subcomponent 用于将 Component 的生命周期与 Activity/Fragment 绑定:

@ActivityScope
@Subcomponent(modules = {ActivityModule.class})
public interface ActivityComponent {
void inject(MainActivity activity);

@Subcomponent.Factory
interface Factory {
ActivityComponent create(@BindsInstance Context context);
}
}

Subcomponent 继承了父 Component 的所有依赖,同时有自己的作用域和生命周期。当 Activity 销毁时,Subcomponent 也被释放,其管理的依赖随之被 GC。

3.4 Binds 与 Provides 的区别

@Module
public abstract class DatabaseModule {
// @Binds:用于接口绑定实现(必须是 abstract 方法)
@Binds
abstract UserDataSource bindUserDataSource(LocalUserDataSource impl);

// @Provides:用于需要 new 或涉及复杂创建逻辑的对象
@Provides
@Singleton
public AppDatabase provideAppDatabase(Context context) {
return Room.databaseBuilder(context, AppDatabase.class, "app.db").build();
}
}

四、运行时 DI:Koin

Koin(https://github.com/InsertKoinIO/koin)是 Kotlin 编写的轻量级 DI 框架,基于 Kotlin DSL 和函数式编程,无需 APT。

// Koin 模块定义
val appModule = module {
single { Retrofit.Builder().baseUrl("https://api.example.com").build().create(NewsApi::class.java) }
single { UserRepository(get()) } // get() 自动解析依赖
}

val viewModelModule = module {
viewModel { UserViewModel(get()) }
}

// Application 中初始化
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this@MyApp)
modules(appModule, viewModelModule)
}
}
}

// Activity 中使用
class MainActivity : AppCompatActivity() {
private val viewModel: UserViewModel by viewModel()
}

Koin 通过 Kotlin 的 inline + reified 实现类型解析,在运行时通过映射表查找依赖。其优点是配置简洁,无需 kapt(编译速度更快);缺点是依赖在运行时解析,编译期无法发现依赖未绑定的错误。

五、Dagger vs Koin vs Hilt

特性 Dagger Koin Hilt
原理 编译期 APT 运行时反射 编译期 APT(封装 Dagger)
编译速度 慢(kapt) 快(无 kapt) 慢但封装简单
配置复杂度 低(自动化)
错误发现 编译期 运行时 编译期
Kotlin 支持 一般 原生 优良
性能 无反射开销 有轻微运行时开销 无反射开销
生产推荐 中大型项目 中小型/快速原型 官方推荐

六、面试常问题目

Q1: Dagger 的 @Component 和 @Subcomponent 有什么区别?

@Component 是独立的依赖容器,有自己的作用域和生命周期,通常对应 Application 级别。@Subcomponent 是 Component 的子容器,继承父 Component 的所有依赖,但有自己的作用域(如 @ActivityScope),生命周期与其宿主(如 Activity)绑定。当 Activity 销毁时,Subcomponent 被释放,其管理的 @ActivityScope 依赖也随之释放,防止内存泄漏。

Q2: Dagger 如何解决循环依赖问题?

当 A 的构造函数依赖 B,B 的构造函数依赖 A 时,形成循环依赖。Dagger 会在编译期检测到这种循环并报错 “Found a dependency cycle”。解决方法是:将其中一个依赖改为 Provider<T> 或 Lazy<T> 延迟注入,或者引入第三个类 C 打破循环,或者将其中一个注入方式从构造函数注入改为字段注入。

Q3: @Binds 和 @Provides 有什么不同?为什么 @Binds 方法必须是 abstract 的?

@Binds 用于接口到实现的绑定,方法必须是 abstract 的且只有一个参数(实现类型),Dagger 直接知道”当请求接口时,返回这个实现”即可,无需方法体。@Provides 用于需要 new 对象或进行复杂创建逻辑的场景,方法必须有方法体。@Binds 比 @Provides 更高效,因为 Dagger 不需要调用方法体,直接使用实现类的构造函数。

Q4: Hilt 相对 Dagger 做了哪些简化?

Hilt 自动生成了 Component 层级(SingletonComponent → ViewModelComponent → ActivityComponent → FragmentComponent 等),无需手动定义 @Subcomponent;自动注入 Android 类(@AndroidEntryPoint 标记 Activity/Fragment 等),无需手动调用 inject();预定义 @ApplicationContext 和 @ActivityContext 限定符;集成 Jetpack ViewModel,一行 @HiltViewModel 即可。


参考源码路径:

  • Dagger:https://github.com/google/dagger
  • Dagger APT 核心:dagger-compiler/src/main/java/dagger/internal/codegen/ComponentProcessor.java
  • Hilt:https://dagger.dev/hilt/
  • Koin:https://github.com/InsertKoinIO/koin
  • Guice(Google 的运行时 DI):https://github.com/google/guice
打赏
  • 微信
  • 支付宝

评论