首先引用Wiki的介绍一下Hasor:
“Hasor是一款开源框架。它是为了解决企业模块化开发中复杂性而创建的。Hasor遵循简单的依赖、单一职责,在开发多模块企业项目中更加有调理。然而Hasor的用途不仅仅限于多模块项目开发。从简单性、松耦合性的角度而言,任何Java应用都可以从中受益。Hasor与Struts,Hibernate等单层框架不同,它可以提供一个以统一、高效的、友好的方式构造整个应用程序。并且可以将这些单层框架建立起一个连贯的体系,可以说Hasor是一个搭建开发环境的框架。这一点与Spring比较相似,您可以理解Hasor可以作为Spring之外的一种选择。”
原则:引用Wiki
简单、清晰、可靠、方便、快捷,可以说是Hasor在设计所有模块时的目标。Hasor从现在到未来都会遵循下面这些原则。
- 用最简的实现提供最有用的功能,不做过度设计。
- 模块职责单一化、简单化。不做“全能模块”。
- 尽量在为开发者提供一个友好、简单的API。
在Hasor是一系列项目的组合,其中hasor的生命周期管理是由hasor-core提供。后面链接的这篇文章详细介绍了hasor提供的模块生命周期管理以及启动过程,http://my.oschina.net/u/1166271/blog/143873。
前言:
上面这张图是由(亿图7)绘制而成,它是一款国产绘图工具软件。中文版正版价格大概在70元左右,笔者呼吁使用合法版权的软件进行开发工作和学习。这是对提供者的一种尊敬。
hasor的核心是由hasor-core提供,它主要负责Settings、Context、Binder三个部分的功能支持。以及hasor模块生命周期支持。根据hasor所遵循的单一职责,所有hasor预期被设计的功能都被分割到不同的模块中。这些模块有些已经完成开发等待第二次重构,有些则只是处于规划状态或是开发到一半的状态。根据体系规划图来看hasor的设计还是很大的。对于波及面这么广的框架hasor应该如何架构,这显得尤其重要。
首先Hasor采用的是模块化设计;通过分割模块将不同功能彼此隔离起来。同时考虑到,这些功能不见得都会在项目中派上用场因此模块的设计要做到绝对隔离。这样开发者在选择模块时就会减少很多麻烦。对于项目而言物理级别的分割,可以有效的帮助项目开发者改造不符合要求的模块或者仿造一个相似功能的模块。能做到这一点也正因为Hasor的模块可以被分割的粒度很小,也够整体化。从而避免“牵一发而动全身”的现象。
(物理级别的隔离,具体是指封装在独立的jar包中,并且只和hasor-core产生依赖。模块和模块之间的避免产生任何依赖)
其次Hasor的模块设计被要求尽量面向接口,这样做的好处之一是即使有一天不在使用Hasor作为项目核心支持只要重新实现相关接口就能将原有项目重新运行起来。此外Hasor面向接口的设计还可以用来通过Hasor来桥接服务的不同实现,或者切换提供者。下一段内容就会讲解到hasor-cache是如何实现这种思想的。
ICache是如何桥接生产着消费者的?
在上面这张图中Cache和@NeedCache是hasor-cache模块面向应用程序提供的两个接口。它们分别以接口和注解形式体现。App Code代表需要使用缓存服务的程序代码段,这段代码可以通过@NeedCache方式或者直接调用Cache接口方式获得hasor-cache模块提供的缓存服务。而Cache接口的实现可以是由不同的提供者提供,通过实现Cache接口可以轻松的将应用程序使用的缓存服务替换成为其他缓存提供商。在一个恰当的设计中即使更换缓存服务提供商也可以不修改任何代码(通过代理Cache并且利用Hasor的动态配置载入监听器实现)。下面先看一下示例代码。
//下面两个类分别定义了MapCache、AuthSessionCache两个不同的缓存服务提供商。其中MapCache是作为默认缓存提供商 //在实际项目中,我们可以采用诸如OSCache、Cache4j等这样的缓存框架来提供服务,对于稍大一点的项目可以换成分布式缓存框架提供缓存。 @DefaultCache @CacheDefine(value = "MapCache" ......) public class MapCache<T> implements Cache<T> { .... } @CacheDefine(value = "AuthSessionCache" ...... ) public class AuthSessionMapCache<T> implements Cache<T> { ...... } /*下面这段代码是Cache接口的完整定义。*/ public interface Cache<T> { /**初始化Cache*/ public void initCache(AppContext appContext); /**销毁Cache*/ public void destroy(AppContext appContext); /**将一个对象放入缓存。*/ public boolean toCache(String key, T value); /**将一个对象放入缓存。*/ public boolean toCache(String key, T value, long timeout); /**根据key从缓存中获取缓存对象。*/ public T fromCache(String key); /**判断缓存中是否有要求的对象。*/ public boolean hasCache(String key); /**删除某个缓存的内容*/ public boolean remove(String key); /**刷新指定key的缓存时间*/ public boolean refreshCache(String key); /**清空缓存*/ public boolean clear(); }
然后可以通过下面两种方式获取MapCache、AuthSessionCache缓存接口。
方式1:(通过代码片段) AppContext appContext=... String cacheName = "MapCache" or "AuthSessionCache" CacheManager cacheManager = appContext.getInstance(CacheManager.class); Cache authSessionCache = cacheManager.getCache(cacheName); 方式2:(通过Guice注入) public class InternalSecurityContext{ @Named("MapCache" or "AuthSessionCache") private Cache cacheService = null; ...... }
当然直接使用Cache接口固然可以利用到Cache服务的众多功能,但是往往在实际项目中我们仅仅想将结果缓存。hasor-cache的设计可以更简单的让你使用Cache服务。见下面代码:
@NeedCache(cacheName="MapCache" or "AuthSessionCache") public String foo(String param1,int param2){ return index++; } //使用默认缓存,还记得上面定义的默认缓存了么? @NeedCache() public String foo(String param1,int param2){ return index++; }
如果仅仅缓存结果那么上面这种使用方式更加贴心,Hasor提供一种扩展的方式帮助你处理不同类型参数如何取得指纹。您可能要问为什么要对参数取指纹,这是由于Cache的key是字符串格式。它不支持对象,所以任何使用这种方式缓存对象时候都面临取得参数特征。哪怕你现在已经取得了Cache接口也会面临上述问题。
使用注解声明Cache这种方式不仅为我们剩下繁琐的代码,而且可以让应用程序可缓存服务接口进行隔离。即使您项目在后续维护中不打算使用hasor-cache作为缓存支持,那么在换做其他缓存框架时任然可以通过Aop监控@NeedCache重新将程序的缓存功能支撑起来。这也是hasor最小化入侵的一个体现。
这种思想在Hasor中有很多实例,权限控制、事件支持等都有缩影。
综述
在实际开发中我们经常会遇到需要(权限、缓存、Ioc/Aop、MVC、模板、上传下载、数据源、远程调用)等等格式各样的功能需求。同时在软件发展这么多年的今天,也不见得谁可以直接提供一个功能完备的体系结构或者框架出来。我们都是跟着需求走、跟着行业发展前进。正所谓“没有银弹”。既然如此,那么作为设计能提供一些什么可以共开发者反复不断的使用就成了设计本身要考虑的事情之一。
Hasor并没有走框架整合路线,他只是坚持最简化原则设计Api。并且在服务提供者和开发者之间建立一个友好的界面。
说一点Hasor的历史希望对正在设计自己框架的人有所帮助。在开发Hasor之前我已经做了有将近5年架构设计。在这几年中我一直在尝试打造一款全能的可以和.Net这样平台所比拟的平台框架。但是随着开发的进行和实践。我越来越发现凡是以(功能完备、完美整合、全面支持)这样目的设计最后都走进了死胡同。
其主要原因来自于以下两个方面:
- 功能越全其适用面越窄:
在其可见的领域内已经完备。但是需求是不受技术限制的,最后可能轰轰烈烈的死亡或者在狭窄空间中等待替代者现身! - 过度封装导致铁板一块:
封装需要节制,不能为了封装而封装。从开发角度来看,通常面对一个框架来说开发者在使用它的时候往往还会再次包装一层以封装出特色Api使用。每一次封装都会将一个或者几个功能模块绑定到一起,如果在框架层面直接将其封装最后在项目开发中框架就变成“一坨”了。这样做有什么坏处呢?首先你不敢随意修改里面的Bug,因为依赖它的代码很可能利用了这个Bug。其次,由于没有人敢于修改最后框架会变得越来越臃肿越来越“一坨”。使用某个独立功能的成本也越来越大。其运行效率和解决Bug所用的时间也越来越糟糕。这会给项目维护带来巨大的成本压力。