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;
- factories 接口类型和实例工厂的映射
- singletonFactories 单例bean的工厂, 所有生命周期是单例的都会加入这个集合
- staticInjections 需要静态注入的Class 类集合
- created 表示该容器是否已经创建,因为容器是是非线程安全,所以用这个字段标记一下
下面开启idea debug 模式走起。
builder.factory(Bar.class, BarImpl.class);
- 图1
图2
图3
从代码发现了方法再次重载, 同时给容器设置了一个默认的名字。 下面再进factory() 方法里面看
图4
201行的代码生成了一个工厂类, 这个工厂类通过构造器创建一个实例。同时实现类放到了implementation 字段里面了。 再继续debug
图5
第92 行把key和对应的生命周期工厂类放入了一个被封装后的工厂,这里为什么会用scope 来封装一下呢? 主要是因为不同的生命周期有不同的创建方式。 比如单例只需要创建一次即可。从下图的代码的47行中可以看到Singleton 的 scopeFactory 方法加了锁, 这是保证单例安全。
图6
默认工厂,直接返回工厂就行。
图7
2. 创建容器
builder.create(false)
图8
- ensureNotCreated 因为Builder不是线程安全的,所以先确保不会被创建。将factoryies 放入ContainerImpl 里面, 因为loadSingletons是false, 同时我们也没有静态字段(这两行代码先不关注,后面会说)。所以直接将容器返回。
3. 获取实例
Bar instance = container.getInstance(Bar.class);
图9
callable.call(reference[0]) 回调了487行的getInstance(type, context), 再继续深入
图10
447 行代码使用Bar的Class类型和名字创建了个Key类型实例, 通过key对象去factories 对象里面拿到对应的工厂类, 再创建Bar实现的实例。 毫无疑问下面又会调用创建实例的create方法。
图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. 首先创建对象
Foo foo = container.inject(Foo.class);
图12
图13
图14
我们发现, 最后其实在调用472行中inject 方法。 继续debug 看看inject 方法到底在干什么。
图15
getConstructor(implementation) , 拿到构造器调用construct 创建对象。 继续向下面看
图16(请忽略图里说的创建代理对象,这是创建构造器对象)
为Foo创建对象,并且将创建对象设置到代理中(这里代理用不上,后面在解决循环依赖的时候会说),并且拿到了该类的注入器对象injectors (在373行), 这个注入器对象从哪儿来的? 为什么我们debug 没有出来? 这里其实在
图15的433 行代码中的 ConstructorInjector constructor = getConstructor(implementation);
, 调用父类的异步方法去获取注入器。 框架里面有个类是 ReferenceCache, 用来做缓存的, 里面很多的异步操作。 但是今天我们不研究这个, 所以直接略过。
图17
2. 注入字段
通过构造器的去拿到injects 对象集合, 刚好是4个, 回想一下Foo 里面的字段和静态方法也是4个。我们继续 debug injector.inject(context, t)
图18
如图18 所示, 通过反射将字段注入进去。 这就是框架注入的步骤。
3. 小结
- 创建代理对象
- 通过@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是单例, 这种情况下, 框架是如何处理循环依赖的呢。
图19
图20
2. 当发现自己的构造器参数里面有其它参数时,先创建参数对象
先Inject 参数B,相当于创建B
图21
进入B的参数构造器, 这个时候B的构造器参数里面需要AImpl的实例对象,
图22
又去创建AImpl的单例对象。
图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的实例对象。
图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;
}
}
直接看
injectStatics
方法调用,首先将需要注入的静态类设置加入集合。injectStatics(StaticInjectionTest.Static.class)
在图5的时候, 我们已经知道在创建容器时会执行静态注入。如下图所示
我们继续看执行静态注入的具体流程, 继续debug container.injectStatics(staticInjections)
下面继续看addInjectorsForMembers 方法做了什么?
主要是这一段代码, injectors.add(injectorFactory.create(this, member, inject.value()));
过滤静态字段(inject.value() 是字段的名字,), injector 工厂生成了injector。这里的静态方法同理,所以不截图了。
拿到了注入器后,分别执行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 设计方法的东西。