




已阅读5页,还剩50页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
Service框架指南1.前言目前阿里巴巴的Java框架,全部是基于Service框架的。本文主要从使用者的角度来介绍我们的Service框架。不过在开始介绍之前,我想先介绍一下Service框架的历史。1.1.历史简介Service框架是用来创建可重用“组件”的通用框架。用现在的眼光来看,似乎这是很平常的一种设计。现在流行的每一种Web框架,在底层几乎都会有一个通用组件框架来支持它。例如, Tapestry是建立在HiveMind通用框架之上的; Webwork是建立在Xwork通用框架之上的; Spring MVC是建立在Spring通用框架基础之上的。HiveMind、Xwork、Spring都是功能类似于我们的Service框架的通用组件框架。除了这些常见的以外,还有很多。但有一些项目由于用的人少,或者是不够好,就慢慢淡出我们的视线了,例如Avalon。这很正常 技术总是在进步的。我们的Service框架的设计,来源于几年前流行的Jakarta Turbine框架(/turbine/)。和Turbine同时流行的,还有Struts框架(/struts/)。虽然Struts的知名度比较高,但是从设计的角度而言,Turbine在当时是非常领先的。虽然Turbine项目最后没有成功,但它的思想却被现在的许多框架借用了。Turbine有很多重要的创举,例如: Turbine开创了在Web应用中的Convention over Configuration的先河。在Turbine中,被称为Page-driven。相对于当时Struts的Configuration-driven,方便了许多。 Turbine的screen/navigation/layout的页面布局模型,今天用起来还是够方便。 Turbine的很多子项目都获得了局部或全部的成功,例如:Maven、Torque。 Turbine鼓励使用轻量级的模板,例如Velocity代替JSP,打破了JSP一统天下的局面。而且Turbine建立了良好的Template框架,允许插入任意类型的模板引擎。 Turbine的Pull Model,非常像现在常说的“注入”机制,只不过是注入到页面。 Turbine 3.0(一直没有完成)第一次引入了开创性的pipeline机制。 Turbine上述所有的功能,全部建立于一套Service框架基础之上。Turbine创建了很多很有用的Service。正由于Turbine建立在Service的基础之上,才使得Turbine框架具有极大的弹性。Turbine正如它的名字所示(涡轮),它可以把很多系统整合到它里面来。相对而言,在当时,想在Struts中加点什么新功能就没这么容易。也许是Turbine的雄心太大,什么都想做,什么都想整合进来,导致它看起来异常庞大。就像现在的Spring,整合了很多内容。人们普遍认为Turbine是一个很Heavy的系统,因而害怕去接近它。此外,Turbine框架中也包含了很多不好的设计(如过度使用Singleton,分层不够清晰,框架的侵入性太大等),影响了它的扩展性和易用性,最终导致整个项目的失败。但不管怎么说,Turbine的Service框架是整个Turbine的精髓。阿里巴巴最初直接使用Turbine和它的Service框架。后来有感于Turbine中很多的设计缺陷,却等不及Turbine极为缓慢的发展速度,因而取其所长、克其所短地自建了一套框架。这就是我们将要介绍的Service框架。我们在这套Service框架之上建立了Webx框架及业务层框架。随后,Turbine也将它的Service框架分离了出来,改名叫Fulcrum。再后来,Turbine将其合并到Avalon框架中。Avalon框架是最早使用“依赖注入”的框架之一。很可惜,先行者成了后来人的垫脚石,Avalon被历史无情地淘汰了 真的很可惜。1.2.怎样看待Service框架任何技术都是一步一步前进的。先进的东西很快会变成落后的。Service框架也不例外。从设计理念上,Service框架继承了Turbine Service框架所声称的IoC(Inversion of Control)的观念。然而,从今天的眼光来看Service框架不算是IoC的,因为现在的IoC往往意味着DI(Dependency Injection,参见Martin Fowler著名文章)。但Service框架很好地符合了DIP的原则,即Dependency Inversion Principle 通俗地讲,就是利用接口,反转依赖关系,从而解除紧密的耦合关系,实现良好的系统架构。DIP也被称为Hollywood Principle,即:Dont call us, well call you if we needed。另人迷惑的是,从现在的眼光看,所有的这些名词:IoC、DI、DIP、Hollywood Principle都代表一个意思,就是“注入”。再去争论这些名词变得没有意义。我们从实用的角度来看,Service框架确实缺失了“注入”、“轻量”等现在我们觉得一个组件框架所必须具备的特性。因为Service框架是在这些新玩意成熟之前的产物。那我们应该怎样看待Service框架呢?我曾听见有人质问我:我们为什么要使用没有“注入”机制的Service框架?为什么不直接使用Spring这样的优秀框架呢?下面是我的回答:首先,要客观地评价Service框架的技术。事实上,只要你的框架很好地吻合了DIP的原则,就已经达到了所有框架为之奋斗的基本目标 “解耦”的目的。Service框架解决了原Turbine框架在滥用Singleton带来的种种弊端。因此,在松散耦合、易测试性等方面,还是做得不错的。至少,我们现在的Webx框架、业务层框架都建立在此之上,并且非常稳定。其次,Service框架中有一些创新特性,是现在的Spring等框架中所没有的。例如:资源装载、多实例等。这些特性在我们目前的系统中工作得非常好。多实例的特性是Webx组件的基础。最后,我们已经对Service框架进行了适应性的改进。例如,我们整合了Spring框架。这样,所有应用都可以使用Spring带来的一切便利。更进一步,使用我们所整合的Spring框架,能够无缝衔接独特的资源装载器,简化我们的应用和测试代码。总之,既然Service框架目前工作得既稳定又好,我们就没有理由去轻易地替换掉它。本文将要介绍Service框架使用的方法和技巧。1.3.Service框架的发展但没有“注入”功能的组件框架毕竟不流行了。依赖注入给我们带来的好处是显而易见的。因此,我们的Service框架必须朝那个方向去发展。我们能不能通过改造Spring框架,加入一些它无我有的特性,来形成自己的新框架呢?当然可以的。但是我们想做更多。如果你不能走在前面,就算Spring这样的先进框架,也很快会变得落后。当我审视Spring,我发现,它还是遗漏了一些事情,而这些事情正是我们所需要的。因此我想,开发一个新的框架,吸收现有框架的优越性,融入自己的创新,也许是更好的方法。新框架的代号为:Citrus。有关新框架的开发思路、理念,我将在另外的文档中慢慢展现。现在,让我们先用好手上兵器。2.Service框架基础本章通过由浅入深的方式,介绍Service框架在各种环境下的使用方法。2.1.预备环境首先,我们将要创建一些代码范例。为了统一起见,建议你先创建一个项目,以便顺利地测试所有的代码。不要怕,用antx创建一个项目非常简单。1. 首先,请创建一个目录,来存放新的项目。例如:C:testservice2. 进入该目录,在命令行上运行:antx gen,生成项目的基本目录结构。C:testservice project.jelly project.xmlsrc java3. 修改project.xml,改成下面的样子: 4. 在testservice目录下,执行antx eclipse,生成eclipse项目文件。5. 打开Eclipse集成环境,导入该项目。现在,你可以在eclipse下工作了,所有依赖的jar包都已经设置好了。2.2.启动Service框架Service框架是一个Service的容器。这个容器可以通过ServiceManager来启动。下面是一个极简单的程序,启动了ServiceManager,并从中取得一个Service。package test.service;import com.alibaba.service.DefaultServiceManager;import com.alibaba.service.ServiceManager;import com.alibaba.service.resource.ResourceLoaderService;import com.alibaba.service.resource.ResourceNotFoundException;public class Main public static void main(String args) throws ResourceNotFoundException ServiceManager manager = getServiceManager(); ResourceLoaderService resourceLoaderService = (ResourceLoaderService) manager .getService(ResourceLoaderService); System.out.println(resourceLoaderService.getResource(classpath/java/lang/String.class); private static ServiceManager getServiceManager() ServiceManager manager = new DefaultServiceManager(); manager.init(); return manager; 从程序中可以看见,创建一个Service容器可以只用一行代码:ServiceManager manager = new DefaultServiceManager();随后,需要对manager进行初始化:manager.init();ServiceManager初始化完成以后,就可以从容器中取得Service了。当然,上面的代码只不过初始化了一个空的ServiceManager,并没有告诉ServiceManager应该初始化哪些Service。在这种情形下,只有一个Service可用,就是:ResourceLoaderService。下面的代码取得了ResourceLoaderService:ResourceLoaderService resourceLoaderService = (ResourceLoaderService) manager.getService(ResourceLoaderService);在ServiceManager中,Service是用一个字符串的key来索引的。在绝大多数的情况下,这个key和Service的类名相同。因此我们也可以用常量来引用ResourceLoaderService:ResourceLoaderService resourceLoaderService = (ResourceLoaderService) manager .getService(ResourceLoaderService.SERVICE_NAME);当然,在后面的例子中,我们可以看到,你完全可以用不同的key来创建同一个Service。请注意,在这个取得ResourceLoaderService的例子中,ResourceLoaderService是一个接口。使用Service框架,你的应用不需要关心Service是怎样实现的,而是通过接口来访问一个Service。这样就实现了DIP原则(Dependency Inversion Principle)。这样做有什么好处呢?很明显,你的程序从某个具体的Service实现中解脱出来。你的Service可以被扩展、被替换 只要接口不改变,你的程序就不需要改变。有人争辩说,应该用“注入”的方式才能实现真正的解耦。而事实是:这两种方式达到了同样的目的,区别只是程序和Service对于框架的依赖性。在将来的改进中,我们会尽可能减少Service对于框架的依赖性。2.3.配置Service框架(原理)怎样让ServiceManager来初始化一些Service呢?答案是通过配置。ServiceManager使用了Jakarta commons-configuration(/commons/configuration/)来配置Service。Commons-configuration是一个通用的配置框架。这使得你可以使用任何格式来定义你的配置文件,无论是传统的properties格式,或是用XML的格式,甚至从JNDI、Database数据库中获取配置文件,只要最终表现成一个统一的接口,应用程序就照单全收。让我们来看一个具体的例子,仍然以ResourceLoaderService为例。虽然刚才我们在未指定的时候,也可以取得ResourceLoaderService,不过这只不过是一个BootstrapResourceLoaderService 它除了能取得classpath下的资源以外,什么也不能做。现在我们来配置一个真正的ResourceLoaderService。首先,你需要在project.xml中添加ResourceLoaderService的依赖: 然后,请修改前例中的Main.java如下:public class Main private static ServiceManager getServiceManager() ServiceManager manager = new DefaultServiceManager(); manager.setConfiguration(getConfiguration(); manager.init(); return manager; private static Configuration getConfiguration() Configuration config = new BaseConfiguration(); config.setProperty(services.ResourceLoaderService.class, com.alibaba.service.resource.DefaultResourceLoaderService); config.setProperty(services.ResourceLoaderService.resource.descriptors, /classpath/resources.xml); return config; 和前例的区别在于,本例中增加了一个额外的步骤:manager.setConfiguration(getConfiguration();在getConfiguration方法中,我们创建了一个简单的BaseConfiguration:Configuration config = new BaseConfiguration();我们指定了ResourceLoaderService的实现类:config.setProperty(services.ResourceLoaderService.class, com.alibaba.service.resource.DefaultResourceLoaderService);我们还指定了ResourceLoaderService的配置文件:config.setProperty(services.ResourceLoaderService.resource.descriptors, /classpath/resources.xml);显然,你还需要在src/java目录下建立一个resources.xml配置文件,该文件将可以从classpath中找到(即:/classpath/resources.xml)。本文的目的不是讲解ResourceLoaderService,因此不给出详细的资源配置文件了。请参见ResourceLoader服务指南。2.4.配置Service框架(实际)既然commons-configuration对产生Configuration对象的机制没有特别要求,那么我们就可以定义出更容易掌握的配置文件格式。在实际应用中,我们不大可能通过前面例子所示的方法,用纯代码来初始化Service框架。配置Service框架最好的方法,是通过XML文件。这个XML配置文件的格式是我们自己定义的,符合DTD:/dtd/toolkit/service/services.dtd。最终,这个配置文件将被解释并转换成和前面例子中相似的Configuration对象。现在,让我们用这个XML配置文件来初始化Service框架。和前例相同,我们仍然以ResourceLoaderService为例。首先,请确信你的project.xml项目定义文件中,包含了toolkit/service/resource的引用。(详见前例)其次,请在src/java目录下建立一个services.xml文件。你可以用/classpath/services.xml来引用它。文件的内容如下: classpath/resources.xml 最后,请创建一个Java类来读取这个配置文件:package test.service;import com.alibaba.service.DefaultServiceManager;import com.alibaba.service.ServiceInitializationException;import com.alibaba.service.ServiceManager;import com.alibaba.service.resource.ResourceLoaderService;public class Main2 private static String applicationRoot = null; private static String serviceConfig = classpath/services.xml; private static boolean initAll = false; public static void main(String args) throws Exception ServiceManager manager = getServiceManager(); ResourceLoaderService resourceLoaderService = (ResourceLoaderService) manager .getService(ResourceLoaderService.SERVICE_NAME); System.out.println(resourceLoaderService.getClass(); System.out.println(resourceLoaderService.getResource(classpath/java/lang/String.class); private static ServiceManager getServiceManager() throws ServiceInitializationException return new DefaultServiceManager(applicationRoot, serviceConfig, initAll); 你会发现和前例相比,程序被简化了:1. 创建ServiceManager的方法有所改变,使用了三个参数:a) applicationRoot BootstrapResourceLoaderService的根目录。可以为null。b) serviceConfig Service框架的配置文件。c) initAll 是否初始化所有Service。2. 不需要调用manager.init()方法了。值得注意的是:service配置文件是从哪里装载的?答案是从BootstrapResourceLoaderService装载的。由于在本例中,我们没有为BootstrapResourceLoaderService提供一个applicationRoot(即:applicationRoot=null),所以我们只能从classpath中装载该配置文件(classpath/services.xml)。然而在很多情况下,指定一个applicationRoot,并将所有配置文件和其它资源放在这个目录及它的子目录下,是很方便的。例如,在单元测试时,我们可以将src/conf.test目录指定为applicationRoot。这样,我们就可以把包括services.xml在内的所有配置文件都放在src/conf.test目录。引用这些文件也变得非常自然 不需要在前面冠以“classpath/”前缀了。请参见另一篇文章:业务层、数据访问层单元测试指南,其中包含了利用这种方案进行单元测试的例子。如果第三个参数:initAll=true,那么所有的Service都会被立即被初始化。这样一来,一旦哪个Service初始化失败,就立刻可以晓得。否则,只有当第一次使用到Service时,才会开始初始化该Service。在生产环境中,指定initAll=true是非常有用的。因为我们不希望等到用户使用到这个Service时,才报错。我们希望在布署系统的时候就知道所有Service是否初始化成功。2.5.创建一个Service创建一个Service是相对容易的事情。现在让我们来尝试建立一个读取properties的Service。所谓properties是指下列形式的(key, value)对:xxx.yyy = 111xxx.yyy.zzz = 222首先,让我们创建一个Service接口:PropertyService。package test.service;import com.alibaba.service.Service;public interface PropertyService extends Service String getProperty(String key);注意,所有的Service必须从com.alibaba.service.Service接口派生。在这个接口中,包含了管理Service生命期的一些方法:public interface Service void init(ServiceConfig serviceConfig) throws ServiceInitializationException; void destroy(); Logger getLogger(); ServiceConfig getServiceConfig(); Service getParentService(); boolean isInitialized();说老实话,这个接口确实设计得不太好。它使Service和Service框架紧密地结合在一起了。在将来的改进中,我们将完全取消这种耦合,实现更轻量的Service。好在这种设计也没给我们带来严重的问题。我们推荐从com.alibaba.service.GenericService类派生出Service的实现。GenericService中包含了Service接口的默认实现,从而简化了Service的实现。让我们创建一个PropertyService的实现:PropertyServiceImpl。package test.service;import com.alibaba.service.GenericService;public class PropertyServiceImpl extends GenericService implements PropertyService 当然这个类肯定不能通过编译,因为我们还没有实现PropertyService接口中的方法。让我们来实现它:public class PropertyServiceImpl extends GenericService implements PropertyService private Properties properties; public String getProperty(String key) return properties.getProperty(key); 然而,properties中内容从何而来呢?幸运的是,commons-configuration的Configuration对象本身就是一种类似properties的数据结构。因此,我们可以直接从services.xml中获取这些properties。为此,我们需要覆盖GenericService.init()方法。public class PropertyServiceImpl extends GenericService implements PropertyService public void init() throws ServiceInitializationException super.init(); Configuration config = getConfiguration(); properties = new Properties(); for (Iterator i = config.getKeys(); i.hasNext();) String key = (String) i.next(); String value = config.getString(key); properties.setProperty(key, value); 实现init()方法时,需要注意几点:1. 如果初始化失败,请抛出ServiceInitializationException。2. 务必调用super.init()方法。(这也是一个不好的设计L)3. 通过getConfiguration()方法可以取得Service的配置信息。该方法将会返回一个commons-configuration的Configuration接口,接下来,你就可以参照该接口所提供的方法来配置你的Service。在本例中,我们简单地把config中的(key, value)对设置到我们的properties对象中。对于这个例子,我们还可以做一些改进。1. 创建日志适当的时候输出一些日志是一个好习惯,它会有助于你发现问题。 public void init() throws ServiceInitializationException for () getLogger().debug(Created property: + key + = + value); 其中,getLogger()方法是GenericService提供的一个简单的取得Logger的方式。你只需要直接调用它就可以了,不必亲自创建它。2. 调用其它的Service调用另一个Service是很常见的功能。很遗憾,Service框架目前不支持“注入”的功能,因此你不能指望写一个setter方法就取得另一个Service的引用。好在取得另一个Service也不是太复杂。下面的代码取得ResourceLoaderService。public class PropertyServiceImpl extends GenericService implements PropertyService private ResourceLoaderService resourceLoaderService; public void init() throws ServiceInitializationException resourceLoaderService = (ResourceLoaderService) getService(ResourceLoaderService.SERVICE_NAME); 请注意,我们取得的是另一个Service的接口 我们既不知道这个service的实现是什么,也不用关心这个service的配置是什么。因此,这两个Service依然是解耦合的 你可以替换任何一个Service,或者更改它的配置,只要保持它的名称和接口不变就可以了。3. 创建析构方法有时,一个Service不仅需要init(),也需要destroy(),以便释放所占用的资源。创建destroy()的方法如下:public class PropertyServiceImpl extends GenericService implements PropertyService public void destroy() super.destroy(); 注意,super.destroy()仍然是必须调用的L。最后,我们需要略微修改一下services.xml,以便将刚刚创建的PropertyService放到Service容器中: 111 222 在这个配置中,我故意把事情“做复杂”了,目的是为了演示配置文件的各种写法。事实上,上面的配置和下面的配置是完全等效的: 其中,“xxx.yyy.aaa”有两个值,这是完全可以的。在代码中,你可以用config.getStringArray(xxx.yyy.aaa)来取得所有的值。有时使用前一种格式更易读,有时使用后一种更好,这个完全由你来决定。不管哪种形式,它们最终都被转变成标准的Configuration对象。现在,我们可以修改一下Main类,测试一下PropertyService:public class Main2 public static void main(String args) throws Exception ServiceManager manager = getServiceManager(); PropertyService propService = (PropertyService) manager.getService(PropertyService); System.out.println(propService.getProperty(xxx.yyy.zzz); System.out.println(propService.getProperty(xxx.yyy.aaa); System.out.println(propService.getProperty(hello); 如果我们在PropertyService中创建一个常量:public interface PropertyService extends Service String SERVICE_NAME = PropertyService; 那么,取得PropertyService的代码可以稍稍改进如下:PropertyService propService = (PropertyService) manager.getService(PropertyService.SERVICE_NAME);3.在Webx应用中使用Service3.1.在Screen/Action/Control中使用ServiceWebx框架是建立在Service框架基础之上的。因此,在Webx模块中,你可以和前述Main例子一样简单地使用任何Service,。下面这段代码演示了在Screen/Action/Control等Webx模块中,如何引用Service:public class LoginAction extends TemplateAction private FormService getFormService() return (FormService) getWebxComponent().getService(FormService.SERVICE_NAME); public void doLogin(RunData rundata, TemplateContext context) throws WebxException Form form = getFormService().getForm(rundata); if (form.isValid() 在Webx中,你不能直接使用ServiceManager,而应该使用WebxComponent对象,但它们的接口是完全类似的。上面的代码取得FormService,并用它来验证表单。然而,还有一种更流行更简单的使用方案 注入。请看下面的代码,它实现了和上面代码完全相同的功能:public class LoginAction extends TemplateAction private FormService formService; public void setFormService(FormService formService) this.formService = formService; private FormService getFormService() return formService; public void doLogin(RunData rundata, TemplateContext context) throws WebxException Form form = getFormService().getForm(rundata); if (form.isValid() 事实上,还可以更
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025江苏南京中医药大学青年特聘教授选聘工作模拟试卷及答案详解(名师系列)
- 2025江苏省宿迁经济技术开发区教育系统招聘教师42人考前自测高频考点模拟试题及答案详解(全优)
- 班组安全季度培训教案课件
- 班组安全培训鉴定意见课件
- 2025年广东技术师范大学招聘辅导员40人模拟试卷及答案详解(夺冠)
- 2025北京中国音乐学院第一批招聘10人考前自测高频考点模拟试题附答案详解(完整版)
- 超精密加工新方法-洞察与解读
- 2025河北沧州孟村饶安高级中学招聘1人模拟试卷有答案详解
- 2025广西百色市第三人民医院(百色市应急医院)公开招聘5人考前自测高频考点模拟试题及答案详解参考
- 班组安全培训的原因
- 2023年全国职业院校技能大赛-融媒体内容策划与制作赛项规程
- 《电力建设施工企业安全生产标准化实施规范》
- 糖尿病周围神经病变知多少课件
- 新概念英语青少版入门 A-Unit-1课件(共98张)
- 儿童肺炎支原体肺炎诊疗指南(2023年版)解读
- 个人履职考核情况表
- 中小学消防安全、交通安全、食品安全、防溺水、防欺凌系统安全教育主题课件
- 建筑垃圾减量化专项方案
- 关于农民工工资催付告知函
- GB/T 6426-1999铁电陶瓷材料电滞回线的准静态测试方法
- 广西版建筑装饰装修工程消耗量定额说明及计算规则
评论
0/150
提交评论