在开始讲解 Binder 思想之前我想先回顾一下我们的开发配置方式。
一般情况下我们开发了一个 Serivce 之后下一步就是要把它 run 起来,而这在有容器管理的项目中就需要把它配置到容器中。例如 Spring 用的是的 xml 文件、Guice使用的是注解方式。无论使用了何种描述方式,配置总是不可避免的。
Hasor 上的开发可以是完完全全无配置的,那么对于我们刚刚写好的 Service 是如何配置到容器中的呢?
虽然配置没有了,但是把写好的 Service 放到容器中这件事还是要做的。Hasor 是通过 Binder 的方式将要做的事告诉给 Hasor 容器。
举个栗子:下面这段代码就是告诉 Hasor 容器:我们有一个 UserService 的服务要放到容器里。
apiBinder.bindType(UserService.class);
这段代码的意义相当于 Spring 配置文件这样写,同时 Spring 的 autowire 要配置为 byType:
<bean class="com.xxx.UserService" />
假如说我想给 UserService 这个服务起一个名字,然后可以非常方便的通过名字来获取它。Binder 的方式为:
apiBinder.bindType(UserService.class).nameWith("userService");
Spring 的方式:
<bean id="userService" class="com.xxx.UserService" />
那如果又需要单例呢?Binder 的方式为:
apiBinder.bindType(RsfConsumer.class).nameWith("userService").asEagerSingleton();
Spring这样搞:
<bean id="userService" class="com.xxx.UserService" scope="singleton"/>
所以写到这里相比大家都意识到 Binder 其实就是一种通过代码配置的方式。但是我们不禁要问,为什么不直接 new 一个 Define 类,然后通过 set 属性的方式设置,岂不更加简单直观?例如:
BeanDefine define=new BeanDefine(); define.setType(UserService.class); define.setName("userService"); define.setSingleton(true);
在 Binder 看来 BeanDefine 是一组元信息,它是属于数据的一部分而并非配置本身。这里要分清“配置”和“数据”。还是以 Spring 为例:Spring 的 Xml 是配置而 Spring 解析 Xml 之后产生的 BeanDefinition 则是数据,数据是容器运行需要的元信息。而配置则是用来提供元信息的一种途径。而 Binder 就是刚刚提到的一种“配置”方式。其它的配置方式还有 @Bean 这种注解形式的。
Binder这种配置方式比 Xml 或 注解这种方式的优点是完全代码控制,可以随时根据运行时条件变换组合。而如果采用 Xml 或者注解,大多数情况下配置都被写死了不灵活、不具备适应性。
在本例中,如果UserService是通用的,那么Binder、Xml、@注解。三种方式都可以做到,同时 Xml 和 @注解 方式会让我们感到更加熟悉。倘若我们的例子中 UserService 是根据运行时环境参数决定其具体功能,那么 Binder 强大的代码亲和力就显得更加方便,例如:
String env = "common"; InjectPropertyBindingBuilder<?> inject = null; inject = apiBinder.bindType(UserService.class, UserServiceImpl.class); if ("common".equals(env)) { inject.injectValue("runAs", "Common"); } else { inject.injectValue("runAs", "Other"); }
这段代码展示了代亲和性的魅力,Binder利用它的代亲和性可以根据运行时数据来动态的配置 UserService。这是Xml、@注解 都做不到的。
或许大家觉得 Xml 能做到的 Binder 做不到,那么让我们尝试写一段代码一个自己的 Xml 解析器,负责解析 <bean> 标签,并将读取到的 Bean 通过 Binder 配置到容器中。这样的话 Binder 摇身一变具备了 Xml 的所有能力。同样的如果搭配注解解析器则变成了注解配置方式。这样看来 Binder 是超越 Xml 和 @注解 两种配置方式之外第三种全能的配置方式。
下面我们在来看为什么 Binder 看起来那么绕啊绕的,为了简单我选用了 RSF 的 Binder 来解释(Hasor 的 Binder 层次比这个深 2 层,而且有分支情况)下面这个是 RSF 的 Binder 接口层次关系:
乍一看或许大家都晕掉了这么多接口!这么多层!这是要死人的节奏啊!!!
不过不要担心,每个接口都只限定了配置的一部分,例如:声明一个RSF服务代码如下:
rsfBinder.rsfService(EchoService.class).toInstance(new EchoServiceImpl()).register();
可以看到,声明了一个名称为 EchoService 的 RPC 服务,服务的实现类为 new EchoServiceImpl。最后通过register方法将这个声明注册到了 RSF 环境中。
从源码上分析,RsfBinder接口的 rsfService 方法是配置一个 RPC 服务的入口,它会返回一个 LinkedBuilder 接口。而该接口的主要目的是用来配置服务接口是由谁提供的。从下面这张图中可以看到配置服务提供者有三种方式。
配置完服务提供者之后或许我们想在配置一些服务本身的信息。这就用到了 NamedBuilder 接口
设置好了 group,name,version属性之后,你可能就需要设置一些参数性的东西。这些参数性的配置就交给 ConfigurationBuilder 接口来搞定。
ConfigurationBuilder 不同于前两者,它的每一个参数配置都返回了它自身。这种设计是由于一些属性本身是可以被覆盖的或者是支持配置多个的,例如 RsfFilter的配置就是支持多个同时生效。
在最后我们只需要编写一个类,实现所有配置接口将配置信息收集起来。配置到容器里即可。
继承关系则体现了“约定即配置”原则。我们可以不必要按照每一层都进行配置转而跳跃其中的某些层直接配置必要的参数。例如:
rsfBinder.rsfService(EchoService.class,new EchoServiceImpl()).timeout(6000).register();
Binder 并不是 Hasor 的独创,Hasor 使用 Binder机制是受到 Google Guice, Binder 接口的启发。Guice 利用 Binder 接口配置依赖注入。
从字面意思上去解释,Binder 是一种粘结剂它负责将不同事物粘合起来使其形成一个整体。在上面 RSF 的例子中, Binder 机制的目的是实现一种配置方式将开发者计划发布的 RPC 服务注册到 RSF 容器中对外提供服务。在 Hasor 中 Binder机制,是负责给开发者提供一套配置 Bean 的途径,这和 Guice 中使用 Binder 机制是相同道理的。