Dramehead 最近在他的Blog上发表了一篇关于Spring使用后的感受,我眼中的Spring,写的不错, :)
最近我自己也研究并比较了一些开源的应用框架, 虽然各有侧重,但Spring看起来更像是一个集成Application Framework,
The Core:Bean Container
从我看来, Spring 最核心的部件就是它的Bean Container, 这种IoC Type2 的轻量级容器,在整个框架中扮演了一个微内核的结构,更确切的说是一个软总线,它使框架内部的组件按照一定的耦合度组装起来,并能对使用它的应用提供一种基于服务的编程模式(SOP: Service-Orient Programming,, SOP已经不是什么很新的概念,大家可以在http://avalon.apache.org/ 找到更多SOP的影子)。从Spring本身来看,它已经将许多的System Level Service进行的集成,比如Persistence(Hibernate,etc) , 这样软件内部的编程结构就形成了下面的模式:
Application Code <---> Service Manager ( LightWeight Container) <---> Service
软件内部的体系结构的变迁,如下图所示:
更多关于使用种关于IoC Container所带来的好处,建议阅读MartinFlower的文章Inversion of Control Containers and the Dependency Injection pattern
Vs EJB Container
在应用的开发过程中,你也可以将你的Business Level Service封装成一种组件部署在Spring提供的Bean Container之上,我想这时候有人可能会问,这种部署和我部属一个EJB到EJB Container里是一样的吗? 我觉得不是, EJB Container 是一个Type1的重量级容器,部署在它内部的EJB都都被迫使用了EJB Container的提供的服务,比如缓冲池,并发控制,事物管理等等,所以你的EJB部署到容器内部之后,所有的控制管理已经完全托管给EJB Container, 通常有时候我们的应用实际上并不需要那么多的Service,可能只需要使用到其中的一个, 但是我们如果把它封装成EJB, EJB Container就会强迫的加载这种服务,不管你是否真的需要,所以我们在使用EJB的时候也要根据具体的应用进行技术选型,先问一下自己,我真的非常需要那些EJB Container提供的好处吗,它所提供的好处能在我的应用中发挥多大的百分比,通常这种选择是一个两难的事情,我们只能在特定的环境下取得一个平衡,没有最好,也没有最坏。
什么样的类更适合于放在轻量级容器里?
刚开始使用Spring进行开发的人常见的一种惯性思维就是,我写的这个类一定要放到Bean Container里进行管理,结果形成的一个现象就是系统中有太多的类使用Spring的描述文件(xml)来定义相关之间的依赖关系,在现实世界的系统中,当程序内部的组件,服务随着项目本身的规模逐渐增大时,这种结构就有可能会导致程序变得非常难于理解,这也是是后面的部分将要提到了使用Spring可能带来的风险。在我看来,Bean Container毕竟还是一个轻量级容器,他的主要职责是为应用组件之间的交互提供一个组装机制。
下面引用Martin Flow文章中的一段文字:
在一个真实的系统中,我们可能有数十个服务和组件。在任何时候,我们
总可以对使用组件的情形加以抽象,通过接口与具体的组件交流(如果组件并没有设计一个接口,也可以通过适配器与之交流)。但是,如果我们希望以不同的方式部署这个系统,就需要用插件机制来处理服务之间的交互过程,这样我们才可能在不同的部署方案中使用不同的实现。所以,现在的核心问题就是:如何将这些插件组合成一个应用程序?这正是新生的轻量级容器所面临的主要问题。
从这段文字我们不难看出,轻量级容器主要解决的问题是如何将插件组合成一个应用程序.
面向对象里的一个基本原则之一就是开闭原则(Open-Closed Principle)
Software Entites(Classes, Modules, Functions, and so forth) should be open for extension, but closed for modification
所以一个组件(或服务)对使用它的客户端来讲暴露的它外部的接口,看下面的使用log4j的代码片断:
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class FooTest {
private Log log = LogFactory.getLog("Foo.class");
public void foo() {
log.debug("debug Foo");
}
public static void main(String[] args) {
FooTest test = new FooTest()
}
}
从上面的代码我们可以发现,当我们使用Log4J这个组件时,我们并需要关心Log4J内部的实现和复杂的依赖关系,我们只需要看到它提供的org.apache.commons.logging.Log接口就可以。
这些对外暴露的部分就是插件, 这类插件的思想我们可以在eclipse里看更丰富的体现,可能实现的机制有所同。
因此,我认为从软件整体的角度来看,更适合放在这些轻量级容器里的因该是组件暴露出来的一些boundary class, 他们是应用代码访问具体服务的一个Entry Point, 应用代码通过轻量级容器来获得这些boundary class, 这样就形成了前面The Core:Bean Container里提到的编程模型。
再次引用Martin Flow文章中的一段文字:
我将开始介绍Dependency Injection 模式的几种不同形式。不过,在此之前,我要首先指出:要消除应用程序对插件实现的依赖,依赖注入并不是唯一的选择,你也可以用ServiceLocator 模式获得同样的效果。
Service Locator 模式背后的基本思想是:有一个对象(即服务定位器)知道如何获得一个应用
程序所需的所有服务。也就是说,在我们的例子中,服务定位器应该有一个方法,用于获得一个
MovieFinder 实例。当然,这不过是把麻烦换了一个样子,我们仍然必须在MovieLister 中获
得服务定位器,最终得到的依赖关系如图3 所示:
实际上在J2EE的体系结构中已经体现出来这些思想。
我们来看一下J2EE 中ServiceLocator的一些实现
当客户端的代码要访问服务器端提供的服务(比如我们所开发的EJB, 具体的某一种business service)时,客户端代码通过ServiceLocator来查找服务器里驻留的Service(在J2EE里是通过服务的JNDI命名), 获得服务对具体服务的访问,这种访问可以是本地方式,也可以是远程访问(比如使用RMI-IIOP),下面来看一下PetStore里的一段具体的代码,这样会更有助于我们来理解这个概念:
应用代码:
/*
* Use the Service locator pattern to located the Catalog
* Home and use the home to create an instance of the
* CatalogLocale EJB.
*/
private CatalogLocal getCatalogEJB() throws CatalogException {
try {
ServiceLocator sl = new ServiceLocator();
CatalogLocalHome home =
(CatalogLocalHome)sl.getLocalHome(JNDINames.CATALOG_EJBHOME);
return home.create();
} catch (javax.ejb.CreateException cx) {
throw new CatalogException(cx);
} catch (ServiceLocatorException slx) {
throw new CatalogException(slx);
}
}
ServiceLocator.java 远程调用实现
/**
* will get the ejb Remote home factory. If this ejb home factory
* has already been
* clients need to cast to the type of EJBHome they desire
*
* @return the EJB Home corresponding to the homeName
*/
public EJBHome getRemoteHome(String jndiHomeName, Class className)
throws ServiceLocatorException {
EJBHome home = null;
try {
if (cache.containsKey(jndiHomeName)) {
home = (EJBHome) cache.get(jndiHomeName);
} else {
Object objref = ic.lookup(jndiHomeName);
Object obj = PortableRemoteObject.narrow(objref, className);
home = (EJBHome)obj;
cache.put(jndiHomeName, home);
}
} catch (NamingException ne) {
throw new ServiceLocatorException(ne);
} catch (Exception e) {
throw new ServiceLocatorException(e);
}
return home;
}
使用Spring可能遇到的一些问题
1.错误检查
由于Spring使用了基于xml配置文件的方式来管理它的bean, 这样做的一个好处是使你的代码的结构更易于动态的配置(这是一种相对的动态), 但是带来的一个问题是,程序的错误的检查被延迟到了运行时,而不是编译时,虽然Spring有第三方的插件来帮助做编辑xml配置文件时的这里错误的检查,但这还是Spring本身的问题。 特别是当多个人协同开发,这些配置文件需要经常进行同步时,就会带来很多时间的开销,所以一个建议是进行持续的集成,保证刚项目随着时间的推移和规模的不断增大时保证这些配置文件的正确性和可控性。这里又涉及到很多软件过程控制的问题了,已经超越了本文所讨论的范围,可以阅读我的另一篇Blog: My First XP Practice
2.代码的可维护性
软件中代码的可维护性也是衡量一个软件质量的标准,当然最后为防止被人反编译,把原代码全部变成以a,b,c命名的类也是一种不错的反盗版方法。 我的一个同事在使用Spring进行开发的过程中的一个体会就是,他写完的类被装配到Spring中后,根本不知道拿去干了什么,这似乎是达到了汽车流水线装配的神奇效果,让我惊讶!如果软件开发的过程能像汽车组装工厂一样,那软件开发的过程和方法可以说是达到了相对完美的境界,但那似乎是还是一个比较遥远的梦想。在XP中说到的一个关键实践就是Collective Ownership, 保证你的代码在一定程度下更易于让他人理解和维护,对于整个项目来讲,是有益的。代码可维护性的风险将在下面的小节里谈到。
特定情况下可能带来的风险
软件结构与人员的变化的风险
如果你的人员会经常地发生变化,而且软件的规模在不断的增长,新来的人去读原来的代码就会变得异常的困难,为了尽量的避免这种风险,那你就需要经常地去和你的队友讲解程序内部的结构,保持一个良好的沟通,不会由于某一个关键人物的离开,到导致项目长时间的停滞甚至失败。我的一个实际的经验:在过去半年中带领了一个团队对原有的一个基于Model 1方式开发的Web系统使用Struts进行改造, 这个系统前后花费了2年的时间进行开发,维护,不断地升级,包括现在也是。为什么要进行改造(更确切的说是重写)?原因很简单,这个系统由于初期体系结构设计的问题,和人员的问题(参与这个项目的核心开发人员已经全部离职), 导致现在整个系统在后期需要新增一些功能的时候变得异常困难,一是没有人知道原有系统内部的结构,原有系统代码结构异常混乱,导致开发一个新的功能需要花费更多的时间,不能及响应客户的需求。 二是开发人员谁都不愿意去修改那令人恐惧的庞杂代码,相互推卸责任。
结论:
本文以Dramehead的一篇对于Spring使用感受为引线,分析了使用Spring框架后给软件内部体系结构带来的变化,以及轻量级容器与EJB容器的比较。同时结合实际项目中的一些经验描述了使用这类框架结构可能产生的风险。
事实上应用框架的大战才刚刚拉开战幕,让我们参与并期待有更多新的思想的出现,使我们的应用软件的开发变得更加高效和稳定。
最后列举出现有开源框架软件的一个分类(源自CambridgeUniversity Java Framework and Component一书),
Complete Application Frameworks
1. Name Avalon
Supplier: Apache Software Foundation
License: Apache License
Focus: Server Components
2. Name Cocoon
Supplier: Apache Software Foundation
License: Apache License
Focus: Website publishing and dynamic applications
3. Name Expresso
Supplier Jcorporate Ltd.
License Apahce-Style License
Focus Database-oriented Web Application
4. Name Arch4J
Supplier Sycamore Group, LLC
License SpiderLogic Open-sources Software
Focus Complete application framework;concentrates on model portion of the MVC architecture, business services
5. Name ACSJ
Supplier: Red Hat Software
License: ArsDigita Public License
Focus: Complete application development
6. Name Turbine
Supplier: Apache Softwre Foundation
License: Apache License
Focus: Complete Application Framework
7. Name realMethods Framework
Supplier: realMethods
License: Commercial license, per developer seat or unlimited
Focus: Complete application for full J2EE environments
8. Name: OpenSymphony
Supplier: The OpenSymphony Group
License: Apache-Style License
Focus: Flexible J2EE Framework
9. Name: Spring
Supplier: Spring Framework Community
License: Apache License, Version 2.0.
Focus: Java/J2EE Framework
Presentation Frameworks
1. Name: JavaServer Faces (JSF)
2. Name: Struts
Supplier: Apache Software Foundation
License: Apache License
Focus: JSP presentation,flexible connection to application logic
3. Name: Maverick
Supplier: Infohazard.org
License: MIT X-Consortium license
Focus: Presentation
4. Name: Scope
Supplier: Steve Meyfroidt
License: BSD-type license
Focus: HMVC Presentation
5. Name: Webmacro
Supplier: Semiotek Inc
License: Apache-style License
Focus: Template Processing
6. Name: Velocity
Supplier: Apache Software Foundation
License: Apache License
Focus: Template Engine
7. Name: Tapestry
Supplier: Howard Lewis Ship
License: LGPL
Focus: User Interface
8. Name: Barracuda
Supplier: Lutris Technologies/Enhydra.org
License: Enhydra Public License
Focus: Server Components
9. Name: Tea
Supplier: Walt Disney Internet Group
License: Tea Software License
Focus: Presentation and Templete Engine
10. Name: Freemarker
Supplier: Benjamin Geer, Johnathan Revusky
License: BSD License
Focus: Template engine for presentation
11. Name: Echo
Supplier: NextApp
License: LGPL
Focus: Sophisticated user interfaces for Web Application
Meta-Frameworks
1. Name: Keel Meta-Framework
Supplier: Keel Group
License: BSD-style License
Focus: Meta-framework: integration of multiple framework