Skip to main content

Google guice 框架源码学习

1. Google Guice 框架简介

​ 本文有大量的截图和解释, 不妨用idea边调试边看文章。

​ 在学习 Spring 的时候, 我们常常习惯用 @Bean 将Bean的创建交给spirng 来管理, 使用 @Autowire 实现依赖自动注入Bean。

@Configuration
class MyBean{
@Bean
public Person person {
return new Person();
}
}

class ABCService {
@Autowore
private Person Person;
}

​ 在Spring 的项目中可以随便使用, 在没有Spring 的项目使用 DI 和IOC ,要引入 spring-core , 显得不是那么友好,一些不想用spring 框架的公司就会造出一些轮子,比如Google 公司出了一款轻量的IOC 框架, 下面我们来研究研究。

本文源码将会使用 google juice 的第一个版本版本号为:66b415a2066cac9f36ed58070777de388f63a3a4 ,这个版本大约在20个类左右,基于jdk 1.5的,这个版本代码相对来说非常少。 下面我们带着目的去研究代码。

2. 如何Bean创建和管理

下面是guice框架将Bar接口和实现类BarImpl绑定的代码,并且获取Bar类型的实例对象。

  public void testManageBean(){
ContainerBuilder builder = new ContainerBuilder();
builder.factory(Bar.class, BarImpl.class);
Container container = builder.create(false);
Bar instance = container.getInstance(Bar.class);
System.out.println(instance);
}

​ 首先创建了一个容器Builder, 再将Bar 和BarImpl 放入factory 方法中, 再调用create() 创建出容器, 最后通过getInstance() 方法来获取实例。下面看框架代码是如何实现的。

1. 创建ContainerBuilder

ContainerBuilder builder = new ContainerBuilder();

首先看看注释

Builds a dependency injection {@link Container}. The combination of
dependency type and name uniquely identifies a dependency mapping; you can
use the same name for two different types. Not safe for concurrent use.

构建一个依赖注入, 依赖的类型和唯一的名字作为映射,两个不同的类型可以使用同一个名字(好像spring 不可以这样), 这个类并发不安全。

这里说一下ContainerBuilder 里面的三个变量, 后面会用到。

  final Map<Key<?>, InternalFactory<?>> factories =   new HashMap<Key<?>, InternalFactory<?>>();
final List<InternalFactory<?>> singletonFactories = new ArrayList<InternalFactory<?>>();
final List<Class<?>> staticInjections = new ArrayList<Class<?>>();
boolean created;
  1. factories 接口类型和实例工厂的映射
  2. singletonFactories 单例bean的工厂, 所有生命周期是单例的都会加入这个集合
  3. staticInjections 需要静态注入的Class 类集合
  4. created 表示该容器是否已经创建,因为容器是是非线程安全,所以用这个字段标记一下

下面开启idea debug 模式走起。

  1. builder.factory(Bar.class, BarImpl.class);

    image-20220416135734557

    1. ​ 图1

image-20220416135800301

​ 图2

image-20220416135815906

​ 图3

从代码发现了方法再次重载, 同时给容器设置了一个默认的名字。 下面再进factory() 方法里面看

image-20220416140009260

​ 图4

201行的代码生成了一个工厂类, 这个工厂类通过构造器创建一个实例。同时实现类放到了implementation 字段里面了。 再继续debug

image-20220416140517464

​ 图5

第92 行把key和对应的生命周期工厂类放入了一个被封装后的工厂,这里为什么会用scope 来封装一下呢? 主要是因为不同的生命周期有不同的创建方式。 比如单例只需要创建一次即可。从下图的代码的47行中可以看到Singleton 的 scopeFactory 方法加了锁, 这是保证单例安全。

image-20220416140847252

​ 图6

默认工厂,直接返回工厂就行。

image-20220416140923405

​ 图7

2. 创建容器

builder.create(false)

image-20220416142457081

​ 图8

  1. ensureNotCreated 因为Builder不是线程安全的,所以先确保不会被创建。将factoryies 放入ContainerImpl 里面, 因为loadSingletons是false, 同时我们也没有静态字段(这两行代码先不关注,后面会说)。所以直接将容器返回。

3. 获取实例

Bar instance = container.getInstance(Bar.class);

image-20220416143220942

​ 图9

​ callable.call(reference[0]) 回调了487行的getInstance(type, context), 再继续深入

image-20220416143523366

​ 图10

447 行代码使用Bar的Class类型和名字创建了个Key类型实例, 通过key对象去factories 对象里面拿到对应的工厂类, 再创建Bar实现的实例。 毫无疑问下面又会调用创建实例的create方法。

image-20220416143746681

​ 图11

4. 小结

​ guice将接口的类型,名字等生成一个key, 然后生成该接口实现类的工厂类, 同时将Key和工厂类实例放入map里面, 再将map放入容器类里面。当Containner.getInsance() 获取实例的时候, 首先通过接口类型和名字生成key, 去Map 里面去找到对应的实现类的工厂类, 再调用create 方法生成对应的实现类。

3. 依赖注入如何实现

   public void testInjection() {
Container container = createFooContainer();
Foo foo = container.inject(Foo.class);
}
static class Foo {
@Inject Bar bar;
@Inject Bar copy;
@Inject("s") String s;
int i;
@Inject("i")
void setI(int i) {
this.i = i;
}
}
private Container createFooContainer() {
ContainerBuilder builder = new ContainerBuilder();
builder
.factory(Bar.class, BarImpl.class)
.factory(Tee.class, TeeImpl.class)
// 指定字段s,i 注入
.constant("s", "test")
.constant("i", 5);

return builder.create(false);
}

名字叫Foo的类里面的bar 和copy 和s, 还有字段i, 都是使用了@Inject 注解的, 说明这些字段都是需要注入的。

1. 首先创建对象

  1. Foo foo = container.inject(Foo.class);

    image-20220416145409582

    ​ 图12

image-20220416145540216

​ 图13

image-20220416150031356

​ 图14

我们发现, 最后其实在调用472行中inject 方法。 继续debug 看看inject 方法到底在干什么。

image-20220416150225452

​ 图15

getConstructor(implementation) , 拿到构造器调用construct 创建对象。 继续向下面看

image-20220416150619122

​ 图16(请忽略图里说的创建代理对象,这是创建构造器对象)

为Foo创建对象,并且将创建对象设置到代理中(这里代理用不上,后面在解决循环依赖的时候会说),并且拿到了该类的注入器对象injectors (在373行), 这个注入器对象从哪儿来的? 为什么我们debug 没有出来? 这里其实在

图15的433 行代码中的 ConstructorInjector constructor = getConstructor(implementation); , 调用父类的异步方法去获取注入器。 框架里面有个类是 ReferenceCache, 用来做缓存的, 里面很多的异步操作。 但是今天我们不研究这个, 所以直接略过。

image-20220416151157660

​ 图17

2. 注入字段

通过构造器的去拿到injects 对象集合, 刚好是4个, 回想一下Foo 里面的字段和静态方法也是4个。我们继续 debug injector.inject(context, t)

image-20220416152821637

​ 图18

如图18 所示, 通过反射将字段注入进去。 这就是框架注入的步骤。

3. 小结

  1. 创建代理对象
  2. 通过@Inject 获取注入器,使用反射注入对应的字段

4. 处理依赖循环

  @Scoped(SINGLETON)
static class AImpl implements A {
final B b;
@Inject public AImpl(B b) {
this.b = b;
}
public B getB() {
return b;
}
}

interface B {
A getA();
}

static class BImpl implements B {
final A a;
@Inject public BImpl(A a) {
this.a = a;
}
public A getA() {
return a;
}
}

1. 先创建AImpl的构造器

AImpl 和 BImpl 相互依赖,并且AImpl是单例, 这种情况下, 框架是如何处理循环依赖的呢。

image-20220416155136101

​ 图19

image-20220416155237541

​ 图20

2. 当发现自己的构造器参数里面有其它参数时,先创建参数对象

先Inject 参数B,相当于创建B

image-20220416155430941

image-20220416155548515

image-20220416155616081

​ 图21

进入B的参数构造器, 这个时候B的构造器参数里面需要AImpl的实例对象,

image-20220416155723093

​ 图22

​ 又去创建AImpl的单例对象。

image-20220416160838526

​ 图23

3. 参数对象创建完成后, 再创建AImpl实例对象,同时将持有AImpl的代理对象重新设置为新的Aimpl的实例对象

判断A的构造器已经构造好了, 就会为B对象的AImpl创建代理对象。 就是下面的方法。 这个时候B持有的其实是AImpl的代理对象。

if (constructionContext.isConstructing()) {
// TODO (crazybob): if we can't proxy this object, can we proxy the
// other object?
return constructionContext.createProxy(expectedType);
}

因为在第一次创建Aimpl的时候,已经 赋值了 this.constructing = true。 下次在创建的时候如果是在一个上下文中。下面的代码图21的357 调用的。

  void startConstruction() {
this.constructing = true;
}

现在B的Aimpl的代理对象创建好了,B的对象也创建好了。那么就要继续创建AImpl的实例对象。

image-20220416160217777

​ 图24

创建完AImpl的对象之后, 再将AImpl的真实对象, B持有的InvocationHanlder.setDelegate(真实对象) 即可。 是不是很妙 ? 哎, 不得不佩服大神在2006 年就能写出这种代码, 太厉害啦。 guice框架里面解决依赖循环的是 ConstructionContext 这个类。大家有时间可以看看怎么解决的。 无论AImpl里面有多少个持有A对象实例的字段, 只要AImpl完成实例对象初始化后, 再设置给持有AImpl对象的代理就可以了。

下面再看看ConstructionContext 的部分代码

Object createProxy(Class<? super T> expectedType) {
// TODO: if I create a proxy which implements all the interfaces of
// the implementation type, I'll be able to get away with one proxy
// instance (as opposed to one per caller).

if (!expectedType.isInterface()) {
throw new DependencyException(
expectedType.getName() + " is not an interface.");
}

if (invocationHandlers == null) {
invocationHandlers = new ArrayList<DelegatingInvocationHandler<T>>();
}

DelegatingInvocationHandler<T> invocationHandler =
new DelegatingInvocationHandler<T>();
invocationHandlers.add(invocationHandler);

return Proxy.newProxyInstance(
expectedType.getClassLoader(),
new Class[] { expectedType },
invocationHandler
);
}

void setProxyDelegates(T delegate) {
if (invocationHandlers != null) {
for (DelegatingInvocationHandler<T> invocationHandler
: invocationHandlers) {
invocationHandler.setDelegate(delegate);
}
}
}

  • createProxy

    创建代理对象, 当创建B对象的时候,在创建构造器的时候会先创建AImpl对象 , 但是AImpl实例构造器已经被标记, 那就创建一个代理对象。

  • setProxyDelegates

    当B的对象创建完成后,又返回到AImpl创建构造器对象代码的地方,继续创建AImpl对象的实例, 再调用setProxyDelegates 方法,给B持有的Aimpl的代理对象中的delegate设置为AImpl的实例。

4. 小结

在两个A,B 两个对象相互依赖的时候, 首先创建A的构造器,标记一下A的构造器已经创建,当发现自己的构造器有参数B的时候先去常见构造器参数B,如果构造器中参数B, 创建实例对象B的时候发现需要创建A对象,这个时候A的构造器已经标记创建了, 那么就给B设置一个代理对象。当B创建完成后, 再创建A的实例对象, 同时再将A的实例对象设置给B的持有的A类型的代理对象。 这个主要是通过proxy 去实现的。

5. 静态注入

现在我们来看看静态注入是如何实现的。

public class StaticInjectionTest extends TestCase {

public void testInjectStatics() {
Container c = new ContainerBuilder()
.constant("s", "test")
.constant("i", 5)
.injectStatics(StaticInjectionTest.Static.class)
.create(false);
assertEquals("test", StaticInjectionTest.Static.s);
assertEquals(5, StaticInjectionTest.Static.i);
}

static class Static {
@Inject("i") static int i;
static String s;
@Inject("s") static void setS(String s) {
StaticInjectionTest.Static.s = s;
}
}
  1. 直接看 injectStatics 方法调用,首先将需要注入的静态类设置加入集合。

    injectStatics(StaticInjectionTest.Static.class)

    image-20220416163728265

  2. 在图5的时候, 我们已经知道在创建容器时会执行静态注入。如下图所示

image-20220416163912290

​ 我们继续看执行静态注入的具体流程, 继续debug container.injectStatics(staticInjections)

image-20220416164023068

​ 下面继续看addInjectorsForMembers 方法做了什么?

image-20220416164306372

主要是这一段代码, injectors.add(injectorFactory.create(this, member, inject.value()));

过滤静态字段(inject.value() 是字段的名字,), injector 工厂生成了injector。这里的静态方法同理,所以不截图了。

image-20220416165057283

​ 拿到了注入器后,分别执行MethodInject和FieldInject的inject 方法,下面就不用细说了,背后的原理就是使用反射的方式给字段设置值, 调用静态方法设置值。

6. guice 和spring 使用DI 和IOC 速度对比

在java 的生态中有一道永远迈不过的天花板,那就是spring。 所以guice 想和spring 一较高下。 guice 处理bean的速度大约是spring的40倍。这个测试类的名字是 com.google.inject.SpringTest 所有小伙伴们在使用storm 或者flink 之类的框架的时候,该选择guice 就要选择juice 。

7. 总结

通过本篇文章我们能够学习到

  • bean 的创建和管理(IOC)
  • 依赖注入(DI)是如何实现的
  • 如何处理依赖循环
  • 静态注入如何实现

​ 本篇文章只是分析了部分功能的原理,这只是我们学习的一小步,后面如果可能的话,我将分析guice 设计方法的东西。