这篇是承接《轻量级 Java 开发框架 设计》系列Blog文的后续文章,写了好多有关 Hasor 的文章发现却没有一篇是从整体上介绍 Hasor 是如何设计的,在这里正好补上。
设计 Hasor 我觉得并不很简单,但是一旦体系设计清晰之后 Hasor 看上去就比较清晰。如果没有一个好的设计 Hasor 不会有现在这么大的一个规模。首先通过下面这张图来袅揽一下 Hasor 目前已开发完成的主要模块。
这是整个 Hasor 的核心成员,Hasor 体系的支撑依靠它。所有 Hasor 承认的模块或插件也都是由这个软件包提供加载和启动。它还包括了 6个不同的小插件来提升 Hasor 开发友好性,这些插件都是围绕核心 API 构建的。
Core 有一个非常明确的 API 层,所有代码都是围绕实现这一层 API 而设计的。这一层的代码全部位于“net.hasor.core”包内,其中 99% 为接口。这些接口明确定了 Hasor 可以完成的所有功能。这 14 个接口不依赖 hasor 其它包中的任何类。学习 Hasor-Core 只要了解它们就可以了。
Core 中提供了:IoC/Aop、Xml配置文件解析和监听、事件、应用程序环境、模块及模块依赖管理。
Aop插件:通过提供 @Aop 注解来简化 Aop 方面的开发。@Aop 注解可以配置在方法上也可以配置在类上。根据配置的位置来决定拦截范围。此外 @Aop 注解还可以同时配置多个拦截器,以形成拦截器链。它还能叠加使用。这里有一篇 Aop 方面的专门文章(http://my.oschina.net/u/1166271/blog/178369)
Bean 插件:使用过 Guice 的朋友都知道,Guice 不像 Spring 先通过配置文件确定了哪些类会被管理。正因为如此它才变得比 Spring 更加轻量。由于 Guice 并不知道那些类会被管理,因此例如 Spring 中 getBean('....') 这样的方法在 Guice 中并不存在。
Bean 插件恰恰是用来拟补 Guice 这个先天不足。通过标记 @Bean('...') 注解可以通过 AppContext 接口以字符串的形式获取注册的 Bean。
Cache 插件:Cache 插件通过 @NeedCache 这个注解为任意方法的调用提供了调用缓存的功能。同时开发者也可以通过 AppContext 对象获取 ICache 接口直接操作缓存器。而真正的缓存交给了底层进行适配。它的工作原理类似于代理模式。
使用 Cache 缓存接口开发缓存相关的功能,开发者可以不必关心缓存服务是 ehcache 还是 OSCache 或者是 memcached。
Guice 插件:这个插件是提供给那些使用 Guice 原生接口编写的 Guice 扩展模块。插件通过 @GuiceModule 注解将一个 Guice 原生模块直接作为 Hasor 的一个插件装载进来。有了这个插件功能。开发者几乎可以将任何 Guice 扩展工具都集成进来了。 这样做 Hasor 除了自身的18个插件之外,又可以与全世界分享 Guice 扩展。
Settings 插件:与 Event 插件一样。配置文件的支持也是 Hasor 原生架构中设计好的。Settings 插件的作用是可以方便的开发一个配置文件改变监听器。
一直以来 Hasor 从发布的第一天就有这个软件包,但是没怎么重要介绍。其实 Hasor-Core 与 Hasor-Web 组合起来就是一个轻量级 Java Web 开发框架。Hasor-Core 提供基本 Java 开发支持,Hasor-Web 提供 Web 下开发支持。
与 Hasor-Core 结构类似,Hasor-Web 中也有着一个 主要部分和附属插件群组成。可以简化 Web 开发。它的原型是依照 “Guice-Servlet”构建的,其中核心部分的代码有60%来自于 Guice-Servlet。
它提供了一种代码方式组织注册 Servlet 和 Filter 的功能,这相当于开发 Web 程序时候可以很自由的通过代码编写 web.xml 配置文件,这点而言即使是基于 Servlet3 规范下也做不到。
基础之后就是 Hasor-Web 提供的各种插件,其中请求控制方面由 Controller 和 Restful 负责提供。这两个插件分别提供了不同形式下的 MVC 开发。
Controller插件:通过访问一个地址,中央转发器会派发到相应的控制器类方法中执行。然后有控制器转发视图。
Restful插件:的工作方式与 Controller 一样只不过地址是通过 Restful 形式组织传入的。
Servlet3插件:则是通过@WebServlet、@WebFilter 注解提供类似 Servlet3 支持。这种独立的支持 Servlet 新规范方式使得即使 Web 容器不支持 Servlet3 也可以享受到到 Servlet3 带来的丰盛午餐。
Result插件:这个插件补充了 Controller 和 Restful 两个插件中 请求方法返回值的处理。例如:Action 返回了一段字符串代表 跳转到某个页面,这个功能就由 Result 插件完成的。目前这个插件可以处理:JSON 格式速回据返回、请求转发、重定向、以及其它一些不常用到的操作。
Resource插件:这个插件的功能是可以将 classpath/jar/zip/ 中某个资源作为 http 进行请求响应。
Hasor体系中 JDBC 是一个新增的成员,目的是补充 Hasor 在数据库方面开发的需要。Hasor-JDBC 可以说是一个“轮子”,而且重量十足。JDBC 部分主要接口来源于 Spring JDBC,并且了做了很大的重构,删除了 Spring JDBC 中大量无关和不重要的类和异常体系。并且支持多数据源,在未来版本还会加入多数据源下的事务控制。
可以说 Hasor-JDBC 可以说是具备了 Spring JDBC 强大功能,但又不臃肿。下面是 Hasor-JDBC 的模块提供的插件(红叉部分表示暂未支持):
DataSource插件:Hasor 的多数据源支持是通过这个插件实现的。数据源的配置是通过配置文件,Hasor 可以为每个数据源使用不同的数据库连接池。并且可以指定一个数据源为默认数据源。在 Hasor 中获取数据源只需要通过 AppContext.getInstance(DataSource.class) 或 AppContext.findBeanByType("name", DataSource.class) 方法取得。
template插件:由于 JDBC 模块核心类是 JdbcTemplate 类,这个插件的作用是。通过AppContext.getInstance(JdbcTemplate.class) 或 AppContext.findBeanByType("name", JdbcTemplate.class) 方法获取一个与相关数据源绑定的 数据库操作接口。
Hasor-Core 模块中包含了整个 Hasor 架构的核心部件,它是基于 Guice 构建的。在此基础上封装了 Guice 的 Binder 接口,通过新的绑定接口 ApiBinder 提供了更加强大的功能。同时 Hasor 为 Guice 构建了一个 AppContext 环境接口这样一来 Guice 就不在是一个单一的 IoC/Aop 框架。它更加趋向于 Spring ,提供了一种基于 IoC/Aop 的容器。下面这张图表现了 Hasor Core 中各个主要部分的分层以及依赖关系。
在设计 Hasor 时非常注重它的接口,当您使用 Hasor 开发时除了学习 Hasor 提供的若干接口,可以不需要过多关注实现细节。您只要知道这个接口方法会为您完成某个事情即可。
即便是 Hasor 的心脏 ”Hasor-Core“ 学习它你也只需要了解几个接口即可,我觉得这样的设计会让开发人员尽量绕过它繁琐的内部实现。同时也为框架和应用程序之间建立了一个良好的保护层。
面向接口的设计还可以保证结构的设计非常清晰。
在前面文章中我曾说过 Hasor 具有模块管理功能(http://my.oschina.net/u/1166271/blog/161782)这使得 Hasor 具备了一个比较独特的设计,或许这个独特之处并没有什么太大的作用。这一点我在设计开发它时越来越显得明显。但是在这里还是把它列出来把。
Hasor 的模块包含了 三个生命周期方法,分别是 init(模块初始化)、start(模块启动)、stop(模块停止)它们的关系如下图所示(图中黄色部分表示相应的容器事件):
init:在这个阶段每个模块都会被调用它的 init 方法,加入模块 init 过程中抛出了异常并不影响下一个模块的 init 过程。但是这个模块由于没有正常 init 系统会放弃后面的 start stop 生命周期调用。
start:当 Hasor 的 AppContext 初始化完毕时,会按照模块依赖顺序依次调用 所有模块的 start 方法。假如某个模块没有正常 init 或者它所依赖的模块没有正常 init 或 start ,都会导致正在 start 的模块放弃 start。
stop:所有模块都会经过这个阶段。当然那些没有正常 init、start 的模块除外。
Hasor 有很多方式可以扩展它的功能,其中最主要的是 Plugin 和 Module。其中 Plugin 是在 Module 基础上建立起来的。因此 Module 就成了 Hasor 架构中唯一的扩展入口,这样的设计可以使开发者在扩展 Hasor 时更加容易便于理解和上手,而且 Hasor 在管理扩展模块时也变得更加容易。
下面这张图展现了扩展 Hasor 的几个重要接口它们之间的关系。其中图中 “粉色” 部分表示的是用户开发的插件或模块,灰色部分表示的是接口,而蓝色部分是用于加载插件的工具类。
Hasor 通过四个核心接口(灰色部分)为扩展提供了一切支持。图中粉色部分可以分为两组,分别表示 用户自模块 和 用户插件。开发者可以在粉色部分去添加自己的功能为 Hasor 提供更多更强大的功能。
蓝色的 PluginSupportModule 也是一个模块,它的作用是提供一个比 Module 接口更加简单的扩展接口(HasorPlugin)。它们的却别在于,Module 支持生命周期方法,而 HasorPlugin 不支持生命周期方法。
Plugin 机制恰好满足了大部分扩展所需要的功能。而生命周期仅保留给那些具有复杂依赖关系的模块。
其实从图中不难发现,Hasor 的扩展最后都落到 ApiBinder 和 AppContext 两个接口身上。这两个接口分别用于 Hasor 启动过程中和启动之后。
使用事件机制可以将两个相对独立但又具有耦合关系的业务分解开。例如如下场景: “Word处理组件将几个不同的文档切片按照既定规则合并成一个最终文档,文档合并动作由一个Action调用并传入所需参数。” 对于上面这个需求两个处理方式:
一、 Action的调用需要等待文档合并完成才可以进行后续处理。
二、 Action在通知文档合并之后需要马上返回一个操作状态。相关合并操作交给后台程序进行。
对于第一种处理方式使用事件机制中,“同步事件”方式解决。而第二种处理方式则使用“异步事件”方式解决。例子代码如下:
AppContext appContext = ...; /*发送异步事件*/ appContext.getEventManager().doAsynEventIgnoreThrow("EventName", ...);
AppContext appContext = ...; /*发送同步事件*/ appContext.getEventManager().doSyncEventIgnoreThrow("EventName", ...);
下面就见识一下事件监听器的写法:
@Listener("EventName") public class Type_B_EventListener implements EventListener { public void onEvent(String event, Object[] params) { System.out.println("Type_B onEvent :" + event); try { Thread.sleep(1000); } catch (InterruptedException e) {} } };
Hasor 是使用 Xml 作为它的配置文件载体。而且 Hasor 的配置文件被分为两类“静态配置文件(static-config.xml)、主配置文件(hasor-config.xml)”。其中静态配置文件主要是留给软件包中默认配置使用。
加载机制:
静态配置文件的存在目的是为了那些独立的模块可以将自己的默认配置信息有一个地方可以存储,从而避免在使用 Hasor 的各种功能时避免进行各种配置。
在 Spring 中要想使用某个功能无论你需要制定还是简单的使用,都需要在配置文件里配置一番。 Hasor 提倡约定优于配置,因此不会烦劳开发人员对 Hasor 的各类模块进行配置之后才进入工作状态。 这也为开发者减少了大量的学习成本。
很多的配置信息都被写到默认配置文件中(静态配置文件)。举个例子: MVC 部分请求一个 Action 的默认扩展名是 “*.do”。这个信息就是写在 Hasor-Web 软件包的静态配置文件中。这样一来平时的开发工作就不会要求开发人员编写任何配置文件。
但是开发者或许会担心,配置文件被打入 jar 之后基本不会改动。那么我要想改变它该怎么办?
Hasor 考虑到这一点在设计配置文件时还留有一个“主配置文件”。当你需要覆盖默认配置时,只需要在主配置文件中重新配置一下新的配置信息就可以了。 Hasor 在启动时,会先加载所有静态配置文件,然后使用主配置文件覆盖它们。
读取方式:
以上就是配置文件的加载机制,下面介绍一下如何读取配置文件的内容。为了便于理解这一部分我使用一个例子代替:
<config xmlns="http://noe.xdf.cn/platform/schema/main"> …… <appSettings> <!-- 应用程序显示 Title --> <appTitle>模板应用32</appTitle> <!-- Code,程序标识码,每个应用程序一个。 --> <appCode>Platform</appCode> </appSettings> …… </config>
Settings settings = appContext.getSettings(); String cfg1 = settings.getString("appSettings.appTitle"); String cfg2 = settings.getString("appSettings.appCode");
如果你有一段配置无法使用上面这样的方式取得可以通过 XmlDOM 的方式取得它们。
/*虽然根节点不参与Key/Value转换但是可以获取到它*/ XmlProperty xmlNode = setting.getXmlProperty("config"); for (XmlProperty node : xmlNode.getChildren()) { if ("userInfo".equals(node.getName())) { if ("001".equals(node.getAttributeMap().get("id"))) { System.out.println(node); } } }
配置文件监听器:
当 Hasor 启动完成之后它会启动一个监控线程,这个监控线程会监视 Hasor 的配置文件。如果配置文件内容发生变化,它会自动重新载入配置文件,然后抛出一个 事件 这个事件由 SettingsListener 接口接收。
而 Settings 插件的功能是可以通过注解的形式 注册这个监听器。
-----------------------------------------------------------
至此 Hasor 的主体架构算是简要的介绍了一遍,后续我想应该对每个部分从 Demo 开始做详细的使用介绍。
Aop 部分的专门介绍:
Guice Aop 与 Hasor Aop 原理及其实现(http://my.oschina.net/u/1166271/blog/178369)
模块部分的专门介绍:
模块依赖管理(http://my.oschina.net/u/1166271/blog/161782)
模块依赖管理(续1)- 依赖循环检测的实现(http://my.oschina.net/u/1166271/blog/162022)
模块依赖管理(续2)- 依赖排序实现(http://my.oschina.net/u/1166271/blog/162738)
当前最新版本
Hasor-Core : v0.0.6
Hasor-Web : v0.0.4
Hasor-JDBC: v0.0.1
----------------------------------------------------------------
目前的开发代码存放于(包括Demo程序):
Github: https://github.com/zycgit/hasor
git@OSC: http://git.oschina.net/zycgit/hasor
非常感谢您百忙之中抽出时间来看这一系博文。可以通过Maven 中央仓库网站 http://search.maven.org/ 搜索 Hasor 下载 hasor 的相关代码。