在后端工程中使用静态依赖注入框架Dagger2

想必前端的同学对于Dagger2并不陌生,在Android开发中,它是主力的依赖注入框架,但是,实际上,Dagger2不仅仅能用于前端开发中,在后端也有它的用武之地。

最近在做公司的一个产品,公司强制必须使用内部自研框架,由于这个框架不支持IOC,所以用起来很让人头疼,想着将Spring结合到框架了里,但是感觉短时间做不到,然而我还是十分的怀念有IOC的日子,这是,突然想起来,去年在搞Android时了解到了一款依赖注入框架Dagger2,想了想,为什么不用它呢。

说干就干,我找到了Dagger2官方文档,文档很简单,一页就说完了基本的IOC使用。大致看了一眼,基本上就知道怎么用了,那么我们就来一个简单的 demo 说明一下用法吧。

首先是配置依赖项和注解处理器,我们的 demo 用maven来演示(我自己做工程一般会选择Gradle,但是公司使用maven,这里就和公司一致了)

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>io.github.since1986</groupId>
<artifactId>learn-dagger</artifactId>
<version>1.0-SNAPSHOT</version>

<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
</dependency>
<dependency>
<groupId>com.google.dagger</groupId>
<artifactId>dagger</artifactId>
<version>2.7</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<encoding>${project.build.sourceEncoding}</encoding>
<annotationProcessorPaths>
<path>
<groupId>com.google.dagger</groupId>
<artifactId>dagger-compiler</artifactId>
<version>2.7</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>

注意上边配置的dependencies里有一个dagger,另外pluginsmaven-compiler-plugin配置了annotationProcessorPaths,这些都是用来处理代码生成的。正式这些代码生成机制,造就了Dagger2的”静态注入”的特性。(这里说明一下,Dagger2是”静态的依赖注入框架”,所谓”静态”,我的理解是指用代码生成的方式来处理注入,生成的代码也会是你的工程代码的一部分,你可以像自己写的代码一样来调用,编译时也会被一起编译,这些代码一旦生成,在运行时是不会变化的,所以称之为”静态”)

配置好了依赖以及注解处理器,我们就可以编码了。我们的 demo 尽量简单一些,一共有两个service,一个UserService,一个ProfileService,其中UserService中注入了ProfileService

1
2
3
4
5
6
7
8
package io.github.since1986.learn.dagger.service;

import io.github.since1986.learn.dagger.model.Profile;

public interface ProfileService {

Profile get(long id);
}
1
2
3
4
5
6
7
8
package io.github.since1986.learn.dagger.service;

import io.github.since1986.learn.dagger.model.User;

public interface UserService {

User get(long id);
}
1
2
3
4
5
6
7
8
9
10
11
package io.github.since1986.learn.dagger.service;

import io.github.since1986.learn.dagger.model.Profile;

public class ProfileServiceImpl implements ProfileService {

@Override
public Profile get(long id) {
return Profile.DEFAULT;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package io.github.since1986.learn.dagger.service;

import io.github.since1986.learn.dagger.model.User;

import javax.inject.Inject;

public class UserServiceImpl implements UserService {

private final ProfileService profileService;

@Inject // 使用JavaEE标准的javax.inject.Inject来注入
public UserServiceImpl(ProfileService profileService) {
this.profileService = profileService;
}

@Override
public User get(long id) {
User default_ = User.DEFAULT;
default_.setProfile(profileService.get(id));
return default_;
}
}

我们注意UserServiceImpl中这一段

1
2
3
4
@Inject // 使用JavaEE标准的javax.inject.Inject来注入
public UserServiceImpl(ProfileService profileService) {
this.profileService = profileService;
}

依赖注入在业务类中只需要写一个注解@Inject就好了,这里其实很类似于Spring的构造器注入。当然,只有这个@Inject是无法完成真正的注入的,我们接着看下边的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package io.github.since1986.learn.dagger.service;

import dagger.Module;
import dagger.Provides;

import javax.inject.Singleton;

@Module
public class ProfileServiceModule {

@Singleton
@Provides
static ProfileService provideProfileService() {
return new ProfileServiceImpl();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package io.github.since1986.learn.dagger.service;

import dagger.Module;
import dagger.Provides;

import javax.inject.Singleton;

@Module
public class UserServiceModule {

@Singleton
@Provides
static UserService provideUserService(ProfileService profileService) {
return new UserServiceImpl(profileService);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package io.github.since1986.learn.dagger;

import dagger.Component;
import io.github.since1986.learn.dagger.service.ProfileServiceModule;
import io.github.since1986.learn.dagger.service.UserService;
import io.github.since1986.learn.dagger.service.UserServiceModule;

import javax.inject.Singleton;

@Singleton
@Component(modules = {UserServiceModule.class, ProfileServiceModule.class})
public interface AppComponent {

UserService userService();
}

我们可以看出来实际上,真正用于处理依赖注入组件读取的配置信息(相当于Spring中的@Configuration)是写在@Module@Component标注的类里的。

@Module中有@Provides标示出来需要对外发布的Bean(这里不知道该怎么起名,就借用Spring的概念吧),你可以简单地把它理解为Spring中的@Bean

@Component(modules = {UserServiceModule.class, ProfileServiceModule.class})UserServiceModuleProfileServiceModule这两个Module结合了起来(这里可以勉强理解为Spring中的容器,当然Dagger2里应该并不存在容器的概念)。

到这里,依赖注入的配置过程就完成了(与Spring一样,也是通过Java文件进行配置完成DI),然后我们再来看如何使用。在使用时,和Spring就有所不同了,由于是”静态”注入,所以注入实际上是依靠注解处理器来生成代码从而完成的,因此需要让注解处理器运作起来,我们用mavenmaven-compiler-plugin中的compiler:compile来生成就好了。生成完毕后,会在target/generated-sources/annotations/下生成一些列源代码,这些源码就是Dagger2帮我们生成的依赖注入处理类。其中有一个比我们的AppComponent类名多一个Dagger前缀的类,这个类就是我们想要的(多一个Dagger前缀是Dagger2的规则)。

1
2
3
4
5
6
7
8
9
10
11
12
package io.github.since1986.learn.dagger;

import io.github.since1986.learn.dagger.service.UserService;

public class App {

private static UserService userService = DaggerAppComponent.create().userService();

public static void main(String[] args) {
System.out.println(userService.get(1).getProfile().getName());
}
}

private static UserService userService = DaggerAppComponent.create().userService();注意这一段,以我的理解,这个类似于Spring中的context.getBean()

到这里,我们的小 demo 就写好了,相当的简单,几个注解,外加注解处理器,就搞定了依赖注入。那么也许你会问,有spring这么强大的框架,我为什么还要用Dagger2呢?我个人理解,确实一般情况下是用不到的,但是Dagger2比较轻量,不存在很多复杂的概念,因此整合进一些小众框架用于提供IOC支持会相对容易一些,我也是基于实际的需要出发,所以选择了它,如果能快速整合Spring的话,我还是会选择用Spring。当然,小巧轻量也是一个优势,说不定就能玩出更多花样来,谁知道呢。