前言:作为一个大型的开源项目,其设计模式,代码架构和设计理念中必然有值得我们学习和了解的内容。同时作为一个成千上万开源contributor参与的开源项目,其代码风格和编程习惯上肯定有会有所差异。分析这样的项目,必然要从宏观上了解其代码逻辑的基本流程,从细微处学习其常用的模式和编程方法。今天就看看Druid是如何将Guice依赖注入运用在自己的代码中,又为什么要这样去实现?
我们以CliMiddleManager
为入口,这个类比较简单只有一个从写的方法——getModules
,返回Guice的配置信息。这个在之前的Guice文章中有说过它的作用,这里不再说明。那么这个getModules
又是如何被使用的呢?其实这个方法是在它的父类GuiceRunnable
中声明的
从上图我们可以看到,其实Druid的每个服务继承了这个抽象类。
这里实际上是用到了一个模板方法模式——父类定义算法流程,算法的实现细节由子类决定。在父类
GuiceRunnable
的makeInjector
方法中会使用到这个getModules
方法返回的modules
来创建一个injector
。而具体的绑定配置则可以通过子类实现的getModules
来进行配置。在这个方法中,每个服务都会将使用到的一些变量,依赖的其他类进行配置。下面是
makeInjector
方法的具体实现
public Injector makeInjector()
{
try {
return Initialization.makeInjectorWithModules(
baseInjector, getModules()
);
}
catch (Exception e) {
throw Throwables.propagate(e);
}
}
这样一来,就通过客户端的方法重写,完成了指定配置。
Guice的一般使用步骤是:
1. 定义接口;
2. 实现接口;
3. 继承Module完成注入配置;
4. 创建Injector对象;
5. 注入实现类或者变量等。
上面的流程实际上已经完成了1~4,接下来我们在看看它是在什么地方进行注入的。
在上面的分析中,我们知道这几个类的顶级父类是Runnable
,按照面向接口编程的思维,在后面是会调用run方法的,而run方法是在ServerRnnable
中实现的,我们看一下其具体的逻辑:
public void run()
{
final Injector injector = makeInjector();
final Lifecycle lifecycle = initLifecycle(injector);
try {
lifecycle.join();
}
catch (Exception e) {
throw Throwables.propagate(e);
}
}
}
我们可以看到,创建出来的Injector方法主要是被initLifecycle
这个方法使用了。在这个方法中,上面的配置会被用来创建一个LifeCycle
对象,通过这个对象来完成middleManger
服务的启动。
其实在这里我是有个疑问的,我们先看看这个三个类之间的关系:
我的问题就是,为什么要重新定义一个
ServerRunnable
类来重写run
方法,而不是在GuiceRunnable
类或者CliMiddleManager
类中重写。然后,我看了一下类的继承关系,发现了GuiceRunnable
还被其他的一些类继承了,而他们run
的实现逻辑是不同的,所以是不能在父类中是实现。至于为什么不在
CliMiddleManager
中实现也很容易理解,这是为了代码复用,因为由相当一部分子类的run
实现逻辑是相同的,抽出这一部分代码封装到单独的类中就可以减少重复代码。