基于J2EE架构的企业应用开发新思维:Web应用以谁为中心?浏览器?服务器
企业Web应用,指的是企业内部使用B/S架构搭建的企业信息系统,用户一般局限在企业内部,为了适应企业某个业务流程而设计开发使用的系统。
出于跨地域部署升级的考虑,一般采用B/S模式进行开发,避免在每个客户端安装配置的麻烦。
一般情况下,前台浏览器特指IE浏览器,前台操作系统选择Windows操作系统。
非Windows操作系统的客户机与非IE的浏览器不在本文讨论范围之内。
本文主要讨论以J2ee架构为基础的Web应用,其他架构的暂不讨论。
在这种情况下,数据存储在数据库上,一般没有多大疑问。而附件文件一般存放在Web服务器上,也没有太大疑问。
最主要的问题是:整个应用应该是以浏览器为中心,还是以服务器为中心?
在J2ee出现的早期,这一问题是不存在的,当时的浏览器,基本上可以看成是互联网时代的终端机。当时的计算模式,是完全基于后台服务器的计算。
3.1B/S的历史发展沿革
计算时代划分 | 浏览器 | 服务器 | 总结 |
史前时代/静态页面的时代 | 仅支持Html,只能显示文本和图片 | 静态文件存储; | 静态时代,没有动态内容 |
史前时代/CGI动态页面时代 | 仅支持Html | 利用CGI方式动态生成一个HTML文件给浏览器 | 动态时代,动态信息由后台服务器计算产生,与前台无关 |
Java Applet ActiveX控件时代 | 浏览器里面可以嵌入其他应用程序来显示动态内容 JavaScript出现 | 浏览器第一次拥有了计算能力,把计算资源从服务器上解放了出来 | |
J2EE时代 应用服务器时代 | Applet被废弃 Javascript开始发展 | 后台采用J2EE架构,JSP等多种方式生成动态页面 | J2EE架构把系统的重心牢牢地绑定在后台的应用服务器上 |
后J2EE时代 开源运动时代 | Javascript开始大发展,Ajax出现 | J2EE开始走向没落,为弥补其缺陷,开源框架出现。 SSH大行其道 | 应用服务器不堪重负,轻量级别的框架开始出现作为替代品。 |
客户端王者归来 Ajax时代 | Ajax风靡一时 Javascript框架大量出现。 Flex/SL/ExtJs各行其道 | 服务器端处于停滞状态,JSF昙花一现 | 后台的问题已经基本解决,现在关注的重点又转向前台,解决用户界面和友好性问题。 |
未来 自定义浏览器时代 | 浏览器中的浏览器 Flex大发展 SL大发展 自定义ActiveX 大发展 JavaScript逐渐衰退 | 服务器进一步弱化,弱化为为前台提供必要服务,应用中心转移 | 通过Ajax技术,利用DWR等工具。 浏览器终于把握了应用的全面主动权。 |
从整体上来看,整个应用架构的发展,体现了从理想的B/S架构到C/S架构的回归过程。
3.2计算模式历史
3.3初步结论
核心问题在与浏览器和服务器的合理分工。
把所有问题压在浏览器或者都压在服务器并不合适。
3.4新模式技术架构
按照上面的演化过程,可以提出一种全新的,以浏览器为中心的技术架构模式。
序号 | 通用功能 | 浏览器功能 | 服务器功能 | 备注 |
1 | 用户界面渲染 | 渲染Html为标准用户界面; 可能内嵌一个自定义的界面渲染工具; | 以纯静态HTML页面为主 少数以JSP形式提供,动态生成 | 以浏览器为主,服务器为辅助功能。 访问采用标准HTTP协议进行 |
2 | 动态数据访问 (数据库读取/存储) | 调用后台功能接口,得到标准格式的数据包信息; 并对这些数据进行渲染 | 提供标准访问接口,供浏览器进行调用 | 前台发送调用要求,后台响应返回结果。 调用方式可采用DWR模式进行 |
3 | 界面跳转 | 浏览器根据本身状态变化主动跳转 | 存储页面跳转规则,将此规则以标准方式发送给浏览器 | |
4 | SQL生成 | 可以在浏览器生成 | 可以在服务器生成 | 两面都可生成,后台生成SQL,前台可以看到 |
5 | 业务逻辑 | 可以在浏览器实现 | 可以在服务器实现 | 如果在后台实现,前台通过Ajax或者DWR方式调用 |
6 | 开发语言工具 | JavaScript ExtJs ActiveX控件 COM调用 | Pure Java开发 SSH等开源框架 EasyCare开发平台 | 根据需要选择 |
7 | 核心问题 | 数据交换规范 | 数据交换规范 | 数据交换的标准化是系统框架的核心问题 |
3.5新模式技术范围
序号 | 运行点 | 所使用技术 | 说明 |
1 | 数据库服务器 | SQL Oracle | 标准SQL语句在服务器端编程开发,创建,维护数据表 |
2 | 应用服务器 | Java Servlet JSP | 采用标准Java语言,在应用服务器上编写Servlet,对外提供标准服务 |
3 | 浏览器 | HTML CSS | 标准的HTML,CSS样式来实现渲染,此功能要弱化 |
4 | 浏览器 | JavaScript ExtJS | JavaScript调用Extjs等来实现数据处理,动态界面内容的渲染 |
5 | 浏览器 | ActiveX Flex | 采用插件形式来实现对界面动态数据的界面渲染 |
6 | 浏览器 | JavaScript Ajax DWR | 通过DWR等Ajax技术实现前后台的功能调用,后台提供数据,前台负责对返回数据的动态界面渲染 |
3.6新模式下人员分工
序号 | 角色 | 技术特长 | 说明 | 技术难度 |
1 | 项目经理 | 负责项目进度管理 | 中级 | |
2 | 架构师 | ALL | 确定系统技术架构,明确那些技术可用于此项目,如何使用此技术 | 中级 |
3 | DBA | 数据库管理 | 数据库规范设计,编写数据字典 | 中级 |
4 | 后台服务开发 | Java Servlet JSP | 利用Java语言,编写后台的通用服务功能,编写前后台的标准数据访问规范 | 高级 |
5 | 前台界面开发人员 | JavaScript DWR | 利用JavaScript,调用后台提供服务,编写前台的显示界面,主要语言为JavaScript,可能嵌入其他ActiveX控件,JS控件 | 初级 |
6 | 前台控件开发人员 | JavaScript ExtJS ActiveX VB,VC | 利用JavaScript, VB, VC等工具,编写前台通用的页面级别的控件。这一控件专注于对数据显示的界面渲染以及对数据的处理过程,但此过程不涉及与后台的数据信息交互。 | 高级 |
7 | 项目秘书 | Word | 编写各种文档,将原始文档归档,格式化。 | 初级 |
高效的开发:J2EE允许公司把一些通用的、很繁琐的服务端任务交给中间件供应商去完成。这样开发人员可以集中精力在如何创建商业逻辑上,相应地缩短了开发时间。高级中间件供应商提供以下这些复杂的中间件服务:
状态管理服务 -- 让开发人员写更少的代码,不用关心如何管理状态,这样能够更快地完成程序开发。
持续性服务 --让开发人员不用对数据访问逻辑进行编码就能编写应用程序,能生成更轻巧,与数据库无关的应用程序,这种应用程序更易于开发与维护。
分布式共享数据对象CACHE服务 --让开发人员编制高性能的系统,极大提高整体部署的伸缩性。
支 持异构环境:J2EE能够开发部署在异构环境中的可移植程序。基于J2EE的应用程序不依赖任何特定操作系统、中间件、硬件。因此设计合理的基于J2EE的程序只需开发一次就可部署到各种平台。这在典型的异构企业计算环境中是十分关键的。J2EE标准也允许客户订购与J2EE兼容的第三方的现成的组件,把他们部署到异构环境中,节省了由自己制订整个方案所需的费用。
可伸缩性:企业必须要选择一种服务器端平台,这种平台应能提供极佳的可伸缩性去满足那些在他们系统上进行商业运作的大批新客户。基于J2EE平台的应用程序可被部署到各种操作系统上。例如可被部署到高端UNIX与大型机系统,这种系统单机可支持64至256个处理器。J2EE领域的供应商提供了更为广泛的负载平衡策略。能消除系统中的瓶颈,允许多台服务器集成部署。这种部署可达数千个处理器,实现可高度伸缩的系统,满足未来商业应用的需要。
J2EE使用多层的分布式应用模型,应用逻辑按功能划分为组件,各个应用组件根据他们所在的层分布在不同的机器上。传统的J2EE多层企业级应用模型将两层化模型中的不同层面切分成许多层。一个多层化应用能够为不同的每种服务提供一个独立的层,以下是 J2EE 典型的四层结构:
Ø运行在客户端机器上的客户层组件
Ø运行在J2EE服务器上的Web层组件
Ø运行在J2EE服务器上的业务逻辑层组件
Ø运行在EIS服务器上的企业信息系统(Enterprise informationsystem)层软件
通常认为,J2EE平台就广泛的认为是这个架构,运行在J2EE服务器上的EJB容器可以认为是此结构的核心,EJB容器管理着所有EJB的执行,以及EJB的生命周期,并且为EJB提供所有系统级的服务。EJB组件则负责接受,处理WEB容器的客户请求和连接提供整个企业使用的数据,服务的EIS层。
此“经 典”架构中,所有的数据访问都要通过entity bean,业务对象都是带远程接口的无状态sessionbean,运行在EJB容器中。EJB中包含了各种服务(比如声明式的事务管理),而且提供了一个共享的中间层,可支持可支持各种类型的J2EE客户端。但结构中应用性能和开发开销的负担很重,一些负载来在于EJB,而很大还是与分布式架构的特性有关。此外为了分布化,牺牲了OO原则,并且难以测试,因为业务逻辑通常编写在EJB的实现类中,而这些类完全依赖于EJB容器的。
此“经典”架构的一种改进,便是把远程EJB替换为本地EJB,实现了架构的重用,解决了分布化的种种问题。但架构还是相当的复杂。EJB的很多负担还是存在,从EJB中获得益处反而不多。
所以随着企业级应用开发的不断复杂,对架构设计的要求也会提出新的要求:
ü架构简单,但功能强大。
ü架构可以通过配置WEB容器集群来达到横向扩展。
ü在不同的应用服务器之间具有高移植性。
ü便于在应用服务器之外进行业务对象的单元测试,而且,一些集成测试甚至可以让一些轻量级容器(如Junit)来完成。
为了解决经典架构中有EJB引起的一系列问题以及满足不断发展的企业应用,提出了非EJB架构的“轻量级容器”。轻量级容器与EJB架构都是有容器管理业务服务对象,然后再围绕着这个服务层组织整个架构。但是业务对象不是运行在EJB容器中,而是运行在“轻量级容器”中。轻量级容器并没有和J2EE绑定,所以它既可以运行在WEB容器里,也可以在一个标准应用程序中运行,如必要也可以运行在EJB容器中。这个容器也没有和servletAPI绑定?D?D这一点与MVC结构的WEB框架不同。轻量级容器的启动开销很小,而且无需EJB的部署。
轻量级容器提供了一种管理、定位业务对象的办法。用不着JNDI寻址、定制服务器之类的额外辅助;轻量级容器为应用对象提供注册服务。其较之EJB容器而言,不仅功能强大,而且避免了容器强制业务对象采用特定的接口,最低程度的降低了侵入性,实现了效果极佳的架构重用。
轻量级容器中所有的Java类都运行在同一个虚拟机中。
WEB层是由MVC框架提供的(Struts或WebWork,或Spring架构的MVC结构)
业务对象是POJO,运行在轻量级容器里。AOP的拦截机制能够增强业务对象,从而实现企业级服务。与EJB容器不同,业务对象不依赖于容器的API,所以这些对象在容器外也可以使用,更利于单元测试。业务对象仅仅通过接口来访问,当更改具体业务对象的实现类后,业务对象无需修改。实现了面向接口编程。
数据访问机制可以通过轻量级的O/R Mapping,该层能提供透明的持久化,该持久层实现了对数据访问方式JDBC的轻量级封装。
MVC Framework
POJO 通过AOP实现业务服务
O/R Mapping 层
RDBMS
其他资源
WEB 层
中间层
EIS层
典型的轻量级架构如下图所示:
此轻量级架构通过AOP为POJO提供声明式的企业级服务,具备了EJB的主要特性,同时避免了EJB的复杂性。
轻量级架构(如Spring)不但提供“扩展”的能力,也提供“收缩”的能力,当一个结构功能相对简单的系统可以直接使用容器提供的声明式事务管理,无需使用JTA。而需要使用JTA管理事务时,无需修改代码,只需要在XML文件中配置即可。
轻量级架构WEB设计
从J2EE应用程序自身的特点看,很多的系统都是基于WEB的应用,所以架构应提供完善且易于扩展的WEB层接口,与下面的控制,业务逻辑结构形成明确的逻辑分层。
WEB层的发展从最初的JSP/Servlet到现在业界流行的基于MVC框架的WEB表示层设计。轻量级架构的表示层同样是基于MVC框架的表示方法。
一个清晰的WEB层,将流程控制和业务对象的调用与数据的展示分开。WEB层启动用户动作的业务处理以及显示结果之外,不应该包含其他的Java代码,应该只包含WEB专用的控制逻辑,较少的涉及业务逻辑。
基于MVC的框架将WEB层分成三类对象:
1.模型 (Model) 对象,提供要显示的数据,提供控制器和视图之间交互的规则。
2.视图(View)对象,显示由控制器提供的模型对象。通常由JSP建立,包含扩展自定义标签库,可以简化创建完全国际化用户界面。
3.控制器(Controller)对象,接受用户输入的信息,并调用业务逻辑去创建和更新模型对象。
当前流行的WEB框架:Struts,WebWork2,以及Spring的WEB MVC框架。
lStruts框架:
Struts 有一组相互协作的类、Serlvet以及Jsp TagLib组成。基于Struts构架的web应用程序基本上符合JSPModel2的设计标准,可以说是MVC设计模式的一种变化类型。Struts是一个webframwork,而不仅仅是一些标记库的组合。但 Struts 也包含了丰富的标记库和独立于该框架工作的实用程序类。
Struts有自己的控制器,同时整合了其他的一些技术去实现模型层和视图层。在模型层,Struts可以很容易的与数据访问技术相结合,包括EJB,JDBC和Object RelationBridge。在视图层,Struts能够与JSP, Velocity Templates,XSL等等这些表示层组件想结合。
Struts的体系结构
struts framework是MVC 模式的体现,下面是各个部分工作的原理。
从视图角度(View)
首先,Struts提供了Java类org.apache.struts.action.ActionForm,开发者将该类细分来创建表单bean。在运行时,该bean有两种用法:
1.当JSP准备相关的HTML,表单以进行显示时,JSP将访问该bean(它保存要放入表单中的值)。那些值是从业务逻辑或者是从先前的用户输入来提供的。
2.当从Web浏览器中返回用户输入时,该bean将验证并保存该输入以供业务逻辑或(如果验证失败的话)后续重新显示使用。
其次,Struts提供了许多定制JSP标记,它们的使用简单,但是它们在隐藏信息方面功能强大。例如,除了bean名称和给定bean中每个段的名称之外,页面设计者不需要知道有关表单bean的更多信息。
从模型角度(Model)
Struts虽然不直接有助于模型开发。在Struts中,系统模型的状态主要由ActiomForm Bean和值对象体现。
从控制器角度(Controller)
在Struts framework中,Controller主要是ActionServlet,但是对于业务逻辑的操作则主要由Action、ActionMapping、ActionForward这几个组件协调完成(也许这几个组件,应该划分到模型中的业务逻辑一块)。其中,Action扮演了真正的控制逻辑的实现者,而ActionMapping和ActionForward则指定了不同业务逻辑或流程的运行方向。
下面是Struts中各组件的关系:
Struts的基本组件关系图
lSpring MVC框架
Spring WEB MVC,同Struts类似,是基于MVC的WEB框架。在Spring WEBMVC使能应用中,能够直接使用Spring的IoC和AOP的功能。借助于它提供的DispatcherServlet控制器(类似于Struts中的org.apache.struts.action.ActionServlet控制器),能够统一分发Web请求。在整个Spring WebMVC中,DispatcherServlet是最为重要的组件之一。直接在Web.xml中配置使用。
HttpServlet
HttpServletBean
ResourceServlet
FrameworkServlet
DispatcherServlet
Spring工作过程:
l一旦客户Http请求到来,DispatcherServlet将负责分发它。DispatcherServlet可以认为是Spring提供的前端控制器。所有的客户请求都需要经过它的统一分发。
l在DispatcherServlet将请求分发给SpringController之前,需要借助于Spring提供的HandlerMapping定位到具体的Controller。
lSpring Controller将处理来自DispatcherServlet的请求。Controller相当于Struts中的Action。可以接受HttpServletResquest和HttpServletResponse。SpringController需要为用户处理请求,同时实现线程安全和重用性。
l一旦Controller处理完用户的请求,则返回ModelAndView对象给DispatcherServlet前端控制器。ModelAndView包含了模型和视图。
lDispatcherServlet返回的视图可以是视图的逻辑名,此时需要借助于Spring的视图解析器(ViewResolver)在Web应用中查找View对象。从而将结果返回给用户。
轻量级架构业务O/R Mapping设计
在分层的软件结构中,业务逻辑层代表了业务数据和业务逻辑。域对象位于业务逻辑层,实体域对象代表应用运行时的业务数据,它存在于内存中,过程域对象代表应用的业务逻辑。数据库用于存放永久性的业务数据。业务数据在内存中表现为实体域对象形式,而在关系数据库中表现为关系数据形式。数据访问代码负责把实体域对象持久化到关系数据库中。
表示层
业务逻辑层
实体域对象
(业务数据)
过程域对象
(业务逻辑)
持久化层(O/R Mapping中间件,负责封装数据访问细节)
数据库层
O/RM模式:在单独的持久化层由ORM中间件封装数据访问细节,ORM中间件提供对象-关系映射服务,当向数据库保存一个域对象时,把业务数据由对象形式映射为关系数据形式;当从数据库加载一个域对象时,把业务数据由关系数据形式映射为对象形式。
持久化层封装了数据访问细节,如事务管理,执行SQL语句,关闭数据库连接,设置数据缓存等功能,可以从JDBC编码中解脱出来。
目前的持久层框架,大多建立在面向对象的设计思想之上,为业务逻辑层提供了面向对象的API。
基于Java的跨平台特性,系统可以在不同的操作系统之间切换。但由于数据库的差异,使系统的移植变得复杂,难于实现。但设计良好的持久层框架,很好的隔离性,提供了跨越不同数据库的支持,只需修改代码的配置文件,即可实现数据库的切换。
目前开源的的持久化中间件Hibernate,充分体现了上面持久层的设计理念,并在实际应用中提供了大量的补充。
Hibernate提供了强大,高性能的对象到关系数据库的持久化服务。利用Hibernate,可以按照java的语义进行持久层开发。Hibernate提供的HQL是面向对象的查询语言,它在对象型关系数据库之间构建了一条快速,高效,便捷的沟通渠道。
Hibernate中间件特性可以用下图显示:
上层业务逻辑
持久化层(Hibernate)
网上购物
数据库系统
网上银行
数据库系统
电子邮件系统
数据库
下图是Hibernate系统结构图:
以下是图中一些对象的定义:
SessionFactory (net.sf.hibernate.SessionFactory)
对编译过的映射文件的一个线程安全的,不可变的缓存快照。它是Session 的工厂。
会话,Session (net.sf.hibernate.Session)
单线程,生命期短促的对象,代表应用程序和持久化层之间的一次对话。封装了一个JDBC 连接。也是Transaction的工厂。持有持久化对象的缓存。
持久化对象(Persistent Object)及其集合(Collection)
生命期短促的单线程的对象,包含了持久化状态和商业功能。它们可能是普通的JavaBeans,唯一特别的是他们现在从属于且仅从属于一个Session。
临时对象(Transient Object)及其集合(Collection)
目前没有从属于一个Session的持久化类的实例。他们可能是刚刚被程序实例化,还没有来得及被持久化,或者是被一个已经关闭的Session 所实例化的。
事务,Transaction (net.sf.hibernate.Transaction)
单线程,生命期短促的对象,应用程序用它来表示一批工作的原子操作。是底层的JDBC,JTA 或者CORBA事务的抽象。一个Session 可能跨越多个Transaction事务。
ConnectionProvider(net.sf.hibernate.connection.ConnectionProvider)
JDBC 连接的工厂和池。从底层的Datasource 或者 DriverManager 抽象而来。对应用程序不可见。
TransactionFactory (net.sf.hibernate.TransactionFactory)
事务实例的工厂。对应用程序不可见。
完善的J2EE架构Spring
1.Spring-AOP
传统的编程技术,采用分解的方式将一个软件系统划分为相对较小的、易于分析、理解的模块,如果这些模块仍难以分析、理解,则迭代的将其分解为更小的模块,直到这些模块可以容易的分析、理解、设计与编码。通过对这些模块进行分析,设计,然后使用相应的编程语言实现这些模块,再以编程语言所定义的方式将这些模块组装起来,形成最终的软件系统。面向对象编程中,通过分析,抽象出一系列具有一定属性与行为的对象,并通过这些对象之间的协作完成系统的功能。
传统的编程技术倾向于按照功能或行为对软件系统进行分割,这个分割的结果是实现某一功能的模块。但在编程实践中,人们认识到,软件系统中有些行为无法封装在单个的模块中,例如日志记录、事物处理、对上下文敏感的错误处理、性能优化等等。通常这些功能与行为不实现系统的业务功能,但辅助这些功能的实现,并散布在实现系统功能的诸多模块中,从而造成代码的纠结,这使得实现系统功能的模块的代码难于阅读、理解、调试、维护和扩展等,并使得纠结的代码难于复用。如果将实现系统业务功能的模块看作系统的纵向分解单元,那么上述分散在功能模块中的功能与行为就形成了一种横向的方面(Aspect),方面与模块形成了横切(Crosscutting),从而造成传统的编程技术无法将方面模块化,造成两种代码纠结(Tangling)在一起。
因此,需要一种新的编程思想,对系统中的横切方面进行处理,解决由此造成的代码纠结问题,并使方面可模块化,促进功能模块与方面彼此的复用。
面向方面编程(Aspect-Oriented Programming,AOP)就是这样一种区别于传统编程技术的新的编程思想,AOP正是基于方面与模块形成横切,造成代码纠结这一观察提出的,并提出了一种新的编程思路来解决这一问题。
AOP的基本思想
如前所述,造成代码纠结的原因在于传统编程技术中,软件系统中非业务功能实现的代码无法模块化,散布在实现业务功能的代码中造成的。这里,引入关注点(Concern)的概念,关注点就是软件系统中需要解决的问题。软件系统的业务功能组成了核心关注点(CoreConcerns),也就是软件系统要解决的问题,而诸如日志记录,事物处理等关注点就形成了横切关注点(CrosscuttingConcerns),因为,这些关注点散布在核心关注点中,相互形成了横切的关系,横切关注点也就是前面提到的方面这一概念。
鉴于此,AOP提出的解决方法是对这两种相互横切的关注点分别进行编码,使用传统的编程语言对核心关注点编程,使用面向方面的编程语言对横切关注点,也就是方面进行编程。然后使用一种机制将这两种代码自动混合起来,形成最终的代码。在这里,面向方面的编程语言可以是已有编程语言的扩展或是一种新的语言,设计用于编写方面的代码。而将两种代码混合的机制称为织入(Weaving),实现这一机制的工具称为织入器(Weaver)。因此,对方面单独编码,并通过适当的织入机制使两种代码混合,构成了AOP解决代码纠结问题的基石。
如前所述,织入是实现AOP的一个重要机制,织入的实现机制有多种,基本上可以分为两类,静态织入与动态织入。静态织入是指在业务功能代码中的适当位置,比如某段代码执行前,或执行后,将方面中的编码插入,从而形成混合的编码。方面中的编码在程序运行前,已被内联至业务功能代码中,因此,代码可以被优化,从而使织入产生的开销最小化,最终产生的混合代码,其执行速度接近为使用AOP方式编写的代码。但是,静态织入无法做到在程序运行时,根据运行上下文动态的决定插入的方面代码,动态织入则可以做到这一点。动态织入可以在程序运行时,根据上下文决定调用的方面,它们的先后顺序,增加或删除一个方面等。
而根据织入的时间,又可以分为三类,编译时,载入时,运行时。编译时织入可以在编译前进行预处理,将两种代码自动混合,将方面中的代码自动插入到功能模块代码的合适位置处,也可在编译后,对编译后的代码进行操作。载入时织入是在代码载入时,实现代码的织入。运行时织入则在运行时,根据对方法的调用执行适当的方面代码以实现织入。
以AOP的Java实现为例,包括使用反射(Reflection),基于动态代理(DynamicProxy)或其它机制的拦截框架,基于元数据(Metadata)的操作,以及类载入时对字节码的操作等。
2.Spring-IOC
Inversion of Control 容器
Spring 设计的核心是 org.springframework.beans 包, 它是为与JavaBeans一起工作而设计的。这个包一般不直接被用户使用,而是作为许多其他功能的基础。下一个层面高一些的抽象是"Bean Factory"。一个Springbean factory是一个通用的Factory,它使对象能够按名称获取,并且能管理对象之间的关系。
Beanfactories 支持两种模式的对象:
Singleton:在此模式中,有一个具有特定名称的共享对象实例,它在查找时被获取。这是默认的,而且是最为经常使用的。它对于无状态对象是一种理想的模式。
Prototype:在此模式中,每次获取将创建一个独立的对象。例如,这可以被用于让用户拥有他们自己的对象。
由于org.springframwork.beans.factory.BeanFactory是一个简单的接口,它能被大量底层存储方法实现。你能够方便地实现你自己的BeanFactory,尽管很少用户需要这么做。最为常用的BeanFactory定义是:
XmlBeanFactory: 可解析简单直观的定义类和命名对象属性的XML结构。 我们提供了一个DTD来使编写更容易。
ListableBeanFactoryImpl:提供了解析存放在属性文件中的bean定义的能力,并且可通过编程创建BeanFactories。
每个bean定义可能是一个POJO(通过类名和JavaBean初始属性定义),或是一个FactoryBean。FactoryBean接口添加了一个间接层。通常,这用于创建使用AOP或其他方法的代理对象:例如,添加声明性事务管理的代理。
BeanFactories能在一个层次结构中选择性地参与,继承ancestor(祖先)的定义。这使得在整个应用中公共配置的共享成为可能,虽然个别资源,如controllerservlets,还拥有他们自己的独立的对象集合。通过BeanFactory概念,Spring成为一个Inversion ofControl的容器。
Inversion ofControl背后的概念经常表述为Hollywood原则的:“Don’t call me, I’ll call you。”IoC将控制创建的职责搬进了框架中,并把它从应用代码脱离开来。使用IoC容器则只需指出组件需要X对象,在运行时容器会提供给它。容器是通过查看方法的参数表(例如JavaBean的属性)做到的,也可能根据配置数据如XML。
IoC有几个重要的好处:
因为组件不需要在运行时间寻找合作者,所以他们可以更简单的编写和维护。在Spring版的IoC里,组件通过暴露JavaBean的setter方法表达他们依赖的其他组件。这相当于EJB通过JNDI来查找,EJB查找需要开发人员编写代码。
同样原因,应用代码更容易测试。JavaBean属性是简单的,属于Java核心的,并且是容易测试的:仅编写一个自包含的Junit测试方法用来创建对象和设置相关属性即可。
一个好的IoC实现保留了强类型。如果需要使用一个通用的factory来寻找合作者,就必须通过类型转换将返回结果转变为想要的类型。使用IoC,在代码中表达了强类型依赖,框架将负责类型转换。这意味着在框架配置应用时,类型不匹配将导致错误;在代码中,无需担心类型转换异常。大部分业务对象不依赖于IoC容器的APIs。这使得很容易使用遗留下来的代码,且很容易的使用对象无论在容器内或不在容器内。例如,Spring用户经常配置JakartaCommons DBCP数据源为一个Springbean:不需要些任何定制代码去做这件事。一个IoC容器不是侵入性的:使用它并不会使代码依赖于它的APIs。任何JavaBean在Springbean factory中都能成为一个组件。
最后是,IoC 不同于传统的容器的体系结构,如EJB,应用代码最小程度地依靠于容器。这意味着你的业务对象可以潜在的被运行在不同的IoC框架上??或者在任何框架之外??不需要任何代码的改动。
SpringFramework 是一个采用了反转控制(Inversion of Control,IoC)策略的基于J2EE的轻量级应用框架。SpringFramework的核心是IoC容器,对于其它应用,如数据库访问,日志等,SpringFramework多使用现有的、成熟的框架。SpringFramework采用了模块化的方式,各模块可以共同使用,也可以单独使用其中的一个模块,SpringFramework的一个模块提供了对动态AOP的支持,SpringFramework中提供的声明式事务管理就是基于动态AOP的。
SpringFramework 中AOP的实现基于动态代理(Dynamic Proxy),动态代理源于代理模式,即通过接口实现对业务对象的访问,但动态代理无需为每一个需代理的业务对象静态的生成代理对象,只需提供需要代理的接口与代理实现,就可以在运行时动态的生成代理对象,代理对上述接口的访问,同样的机制也使用于对类的代理,使用类似于修饰者的模式,通过子类化实现。SpringFramework默认使用JDK提供的动态代理机制,此时,业务对象通过接口编程,若需要代理对类的访问,则需要使用CGLIB,这是一个开源的动态代理实现。
SpringFramework的AOP实现不同于AspectJ与AspectWerkz,它不是完全的AOP实现,而是设计用于在应用服务器环境下实现AOP,与SpringFramework的IoC容器配合使用。SpringFramework中参考,切入点与方面均由普通Java对象实现,其中连接点模型与AspectJ相同,目前只提供对方法调用的拦截。有4种类型的参考,分别为方法调用时,之前,返回时与抛出异常时,通过实现SpringFramework的参考接口可以自定义参考类型。在SpringFramework中,方面称为Advisor,是一个包含参考与切入点的Java类。像其它由IoC容器管理的组件一样,参考,切入点与方面也由IoC容器管理,由XML配置文件定义。配置的内容包括业务对象的接口与实现,自定义的或由SpringFramework提供的切入点与参考类,或使用Adviser类取代单独的切入点与参考类。在运行时,通过IoC容器进行名称查找,就可以由容器使用代理机制自动产生代理对象,并在符合切入点定义的连接点处执行参考。SpringFramework除自身实现的AOP框架外,还在寻求与其它AOP实现机制的整合,目前已经实现了与AspectJ的整合,以利用AspectJ丰富的切入点语法,并利用AspectJ的方面实现。
Spring架构的远程服务
Spring架构提供对经典的J2EE架构的RMI方式的分布式远程方法调用的支持,同时也可以兼容EJB的分布式的解决方案sessionbean。
每个基于WSDL的Web Service都是由一个服务构成,其中定义了一个或多个端口。每个端口对应服务器上的一个服务端点。同样Spring通过JAX-RPC提供的服务,实现基于J2EE的远程客户端的访问。
Spring还有另外的一种选择-使用轻量的级的远程方案:Hessionhe和Burlap。这两种协议都不是绑定在Java之上的,它们有自己的序列化语义,并且支持集合类型。他们最主要的目标还是提供Java应用之间的通信。并且也确实能够满足语义丰富的Java远程调用要求的最简单的通信协议。它们的配置比RMI简单,并且比基于WEB服务的SOAP协议简约的多。
在“J2EE”这个缩略语被第一次介绍给世人的时刻,也许没有几个人可以预料出它在日后的奇特历程。那是在1999年6月的JavaOne年会上,时任Sun公司Java企业开发部门主管的MalaChandra兴奋地预告了Java世界的这位新成员。
那些不熟悉背景的听众们,揣摩着她演说中出现的一串串全新术语,表情大概又是惊喜、又是迷惑:一个完整的“多层企业开发架构”、以“容器”和“组件”的形式提供服务、一套“厂商中立的开放技术规范”、对开发者隐藏了不同平台和“中间件”的技术细节、实现了企业级应用间的“无缝集成”等等。
在今天的开发者看来,这些似乎都已经是老生常谈,但在当时的场景下,闪动在幻灯片上的每一个口号,都意味着听众们事后又要经历一段困难的学习过程。
幸亏Chandra有一副了不起的口才;这位本科念建筑学的印度裔高层主管,谈起软件架构来也有特强的空间想象力。她清晰地说明了设计J2EE架构的两个初衷:首先,对于厂商,J2EE意味着一套开放标准,加入这个标准,他们的产品就可以运行在各种不同的操作系统和工作环境下,成为一个成熟的企业运算体系中可替换的部件。
其次,对于开发者,J2EE是一套现成的解决方案,采用这个方案,企业应用开发中的很多技术难题(包括跨平台移植、事务处理、安全性等等)就会迎刃而解,“信息像一条不间断的河流,经过各种各样的平台和设备,从企业应用系统的这一端流向那一端”。
要想理解这段话在当时的实际效应,我们仍然要把时间指针拨回1999年。除了预备迎接千年虫之外,99年你做了什么?为了回答这个犀利的问题,我翻出6年前的工作记录,发现了自己那时参与的一个项目的规格说明书,它正好能提供一幅“Java企业开发”在1999年的标准照。
这是一家日本知名IT厂商的企业信息管理系统,运行在NetScape 3.0 Gold浏览器中的JavaApplet界面,通过一个专用的中间层系统与Oracle8数据库连接。这个中间层已经相当现成、完善,能够提供远程对象调用、事务处理等一系列的底层服务;留给我们的任务只是完成服务器端业务对象代码,以及相应的客户端交互开发。
除了Applet客户端有些特别之外,上述系统与今天常见的J2EE架构很接近;尤其是业务对象编码也由home类、PK(主键)类、entity类等部分构成,很多机制都与EJB如出一辙——只不过这些类并没有继承javax.ejb包的接口,而是采用了专用的API。它与EJB之间的相似不像是偶然的,设计者肯定参照了Sun在1997年底推出的EJB 1.0技术规范。
换言之,在J2EE诞生伊始的语境中,市面上已经存在着很多程度不一的“准J2EE中间件”了。它们主要用于解决三大类问题:事务处理、分布式对象管理和Web请求处理。首先,事务处理管理器(Transaction ProcessingMonitor)一直是高端企业计算领域的热门产品,著名的应用服务器厂商BEA,正是通过收购事务处理软件Tuxedo进入中间件市场的。另一方面,从90年代初开始,越来越多的人把“N层分布式对象架构” 当成传统的客户端/服务器架构的替代方案。
那时刚刚兴起的CORBA技术是推动这一趋势的重要力量(比如说,前面提到的那个由日本厂商自行开发的专用中间层,就采用了CORBA作为基础架构)。最后,Java技术在Web领域中的应用也是当时初露头角的热点。
1997年6月,Sun在发布一款“Java Web Server”的同时第一次公布了ServletAPI;没想到这项技术副产品(连同1998年问世的JSP)正好迎合了厂商的战略需要。对于上面提到的N层架构来说,HTTP服务是一个非常理想的前端;所以基于Java的Web引擎,也在此时成了企业级Java解决方案的一个必不可少的部分。
Java、Web、事务、分布式对象,这几股开发潮流汇合在一处,形成了当时最热门的产品“应用服务器(ApplicationServer)”或“中间件(Middleware)”。
为了给定语“最热门”作个注释,我们可以参照一下BEA公司在1998年收购Web应用服务器厂商Weblogic的成交价:1.92亿美元。而这并不是一桩孤立的收购,NetScape和Sun也以相近的价格买下了另外两家企业Kiva和NetDynamics。
而这也正是J2EE规范出台的背景:几乎所有要厂商都推出了、或是正在赶制自己的应用服务器产品,但这个“应用服务器”究竟应该是什么东西,竞争者们又各有表述、莫衷一是。
说到这里,我们才梳理出了J2EE技术规范的第一个版本在1999年12月问世的实际意义。首先,它为Java企业开发提供了一幅清晰的全景,各项分支技术在这个领域中的地位和作用得到了客观、准确的定义。
至此大家才对一个Java企业解决方案的构成要素有了基本共识。其次,它使用“容器”和“组件”等概念描绘了Java企业系统的一般架构,明确地划分了中间件厂商和应用开发者的职责所在。
最后(但绝非最不重要地),J2EE通过一套公开标准规定了应用服务器产品的具体行为,在执行此标准的厂商产品之间实现了一定程度的可替换性和互操作性。
当时的媒体用“B2B开发的默认标准”之类的说法欢呼这项里程碑式的成就——那些撰稿人哪里知道,在J2EE与那个被称为“B2B”的短命新贵之间,其实并不会有太多故事发生;同样,他们也不会想到,J2EE要想成为一种真正成熟的开发范式,前方还有一段远为艰辛的旅程。
社区的形成
记得Kruglinski在名著《Inside VisualC++》的某个版本中给出了一个Web浏览器的代码例子;在这一节的开头他说到:如果你几年前开发了一个Web浏览器,那肯定会给你带来上千万的收益;但如果你现在才想到开发这个东西——那也就是个C++语言的练习罢了。
在今天的程序员眼中,应用服务器似乎也成了价格低廉(如果不是全然免费)的日用消费品。所以,想要理解它们在那几年的大行其道,就非得借助Kruglinski这样的智慧不可。
在1999年底,市面上可以找到30种以上自称“Java应用服务器”的产品,可见当时这类软件是网络风险投资的宠儿。但是此时出台的J2EE规范就像是一阵席卷整个产业的劲风,在一夜之间,所有人都有了判断什么是一个“应用服务器”的权威途径。
为了获得一张J2EE竞技场的入场券,各家厂商面临两项考验:首先,要具有能够覆盖J2EE中所有主要技术的产品线。这在当时是一项非常苛刻的要求,在没有开源产品可供参照的情况下,短时间内推出包括EJB容器、Web引擎和JMS中间件的整体解决方案,这决不是随便哪家创业公司都能办到的。
完成了若干次成功的并购之后,BEA在这一点上抢占了先机,完整的产品线使它成了人们心目中的首选J2EE平台提供商。其次,要让产品通过Sun的J2EE兼容性测试。要做到这一点同样不易:就连IBM的WebSphere也一时还没达到百分之百的EJB支持。
到2000年底为止,共有15家厂商能够提供完整的J2EE解决方案,其中9家(包括Sun本身)实现了“J2EE兼容”,他们中间包括了日后这个领域的主要竞争者。毫无疑问,这是一次非常残酷的行业洗牌,但留在场内的厂商也相应地形成了推动J2EE发展的主体力量。
上面说过,在它的孵化阶段,Sun的J2EE团队主管是女强人MalaChandra,她本人虽不是工程师出身,但对技术有着很强的感知能力和想象力;J2EE一出台就能够为人们提供一幅完整、直观而不失深邃的图景,此中当然有Chandra本人的大量贡献。
在她直接领导下工作的几位工程师,也都是Sun内部非常杰出的人才。无论是制定了JDBC、JMS等规范的MarkHapner、JavaMail的设计者Bill Shannon,还是EJB的主要设计者VladaMatena,后来都是业界一言九鼎的技术领袖。
这个班子的合作时间并不太长:2000年左右的那个时期正是IT界创业的黄金年月,Chandra很快就和Sun公司Java部门的总裁(也是创造Java的功臣之一)AlanBaratz一起,到一家刚起步的Email中间件公司Zaplet淘金去了;捷克裔的开发天才Matena也离开Sun开办了自己的公司。留下的两个人Hapner和Shannon先后担任了J2EE技术的首席设计师。
多年以后,Hapner回忆起J2EE初创的那个时期,深感如今Sun对Java的左右能力已经大不如前:“现在,Java事实上属于整个技术社区,它的发展有赖全体参与者的推动。”的确,如今Sun已经不太可能重演当年的开拓性功绩,很难再为一个已经成形的领域重绘版图。
但正如上文所说,即使是在1999年,J2EE设计者们面对的也不是一张从未着墨的白纸。他们的设计始终要以各大厂商的现有产品为出发点,这也是天才的设计师们做出的设计却远非完美的原因之一:与从头设计一门全新的编程语言不同,J2EE规范从一开始就是各方博弈和妥协的产物。
很容易注意到,J2EE与Java社区的决策机制JCP(Java CommunityProcess)是几乎同步产生的。J2EE下属的各种技术规范,包括1.4版之后的J2EE本身,都作为待决规范议案(JSR,JavaSpecification Request)被纳入了JCP的议程。
这些议案的审议过程很少是一帆风顺的,几乎每一个都要经历18个月以上的拉锯战。在多项技术规范的审议过程中,我们都见到了这样的现象:最初列名审议委员会的某家主要厂商,没能等到该规范通过就已经被收购或倒闭了。
与微软在.NET平台上的乾刚独断相比,J2EE发展中的这个“牛步”特征虽说是审慎和民主的表现,但终归不符合软件演化应有的速度。
J2EE社区中的另一股重要力量,当然是种类极为丰富的开放源代码项目。2002年以来,在J2EE领域的各个层面上,几乎所有主流产品都有来自开源项目的替代方案,在其中很多位置上,开源产品反而是胜过商业产品的首选。
但请别误解,这里的“开源”并不意味着完全的自动自发,J2EE世界中的开源项目也与Linux或PHP世界颇为不同。在很多非常成功的J2EE开源项目背后,我们都能发现商业机构的推动作用:Apache的Jakarta社区是IBM扶植的结果;实现了开源应用服务器JOnAS的ObjectWeb,则是许多法国IT厂商(包括若干政府部门)合资支持的一个联盟组织……这些有商业背景的开源项目资金雄厚,人员齐整;更重要的是,从投资者到开发者,参与这些项目的很多人都体现了软件工业中难得的非功利心态,因而最终推出的产品质量甚至高于同类型的商业软件。在主流厂商之外,它们是支撑J2EE大厦存在的一组基石。
另一方面,不少开发者也间接地通过自己的开源产品获得了可观的盈利。这些人大多以免费的开源产品为依托,以收费方式提供附加的咨询、方案实施以及技术支持服务。Marc Fleury,开源应用服务器的JBoss创始人,不无矛盾地把自己倡导的这种商业模式称为“职业开源开发”。
无论叫它什么,高端产品的开源化/免费化运动注定要在J2EE产业的发展过程中制造显著的后果。“JBoss的行径恶化了J2EE的商业环境,”这是McNealy先生2002年的著名论断。他的推理过程如下:只有做好商业推广,J2EE产品才能最终击溃邪恶的.NET平台;但开源服务器会降低主流厂商的销售利润;销售利润越低,用于商业推广的预算就越少;因此,整个J2EE阵营都将受损于JBoss。
但在狂热的开源运动支持者看来,以上论证的大前提就是可疑的。“难道只有会做广告的软件才是好软件?MySQL有过多少广告预算”争论的双方都认为对手误解了软件商业模型的实质。究竟谁才掌握了这里的真理呢?也许只有根据J2EE的未来——也就是它的目标和终点(Telos)——才能做出最终的裁决。
技术的离心力
考察事物的演化,通常有两种对立的方法。考古学家(Archaeologist)探究肇始和起源;目的论者(Teleologist)则揭示目的和终点。对于前者,“开端(希腊语Arche)”从根本上决定了此后的发展,参天大树的繁茂都包含在种子最初的萌芽中;而对于后者,“目的(Telos)”才是事物的根本和旨归:谁没见过样态完善的树,谁也就没法弄懂种子到底是怎么回事。
在J2EE五年之后,人们只能交替地用这两种目光审视它的演化历程。它的起源与它的目的、“它从何处来”与“它往何处去”的问题紧密地交织在一起,谁拾起了其中的一个,谁也就要连同另一个一起回答。
今天的J2EE在多大程度上符合它的初衷?回答这个问题并不涉及对J2EE技术成败的评判,而只是要考察一下:它是否还运行在最初开辟的那个空间之中。在事务处理、对象分布化和Web请求处理这三个方面中,也许J2EE对事务和Web保持了一贯的忠诚。
我们记得Fleury喜欢重复的一个信条:“He who owns the transactional Web owns theWeb(谁掌握了带事务处理的Web,谁就掌握了Web)”Web接口是今天大部分J2EE应用暴露的唯一接口;而虽然事务处理的常用方法已经有了很大改变(借助AOP机制,很多非EJB架构的系统也自如地实现了声明式的事务处理),但对事务的重视当然仍将是J2EE开发中的要素之一。
换言之,在5年的演化中,J2EE发生的最大变化可能就在于它放弃了对“分布式对象模型”的强调。EJB2.0引入的本地接口使得Web层与EJB层可以运行在同一个Java虚拟机中,从而使Web容器与EJB容器的物理分离部署变成一种昂贵的冗余;J2EE 1.4以后版本支持的WebServices兼容性,使得客户端可以通过粗粒度的Web接口调用远程服务——这两次变化事实上都是在论证“分布式对象架构”的无用性。
人们发现,同一系统的各个分层最好采用细粒度接口调用,并且运行在同一个进程中;之所以划分不同的层次,与其说是为了实现物理上的可扩展性,不如说是设计美学上的考虑。
而对于异质系统之间的调用,则应该尽量选用异步的、粗粒度的服务接口(所以WebServices成为了非常理想的选择)。换句话说,传统上的“分布式对象架构”,现在看来似乎只适合于银行远程支付等要求极为苛刻的应用场景,而绝不是所有J2EE应用都该考虑的标准方案。
前面描述的离心现象毕竟还遵循了J2EE发展的内在逻辑,说到底,EJB的革新和WebServices的引入更多地是主流厂商倡导的结果。但在近年来,还有一股更强劲的离心潮流在深刻地影响着J2EE的演进,它肇始于上文提到的开源软件运动。
最初它只在RickardOberg的动态代理RMI设计与JBoss服务器的微内核架构中显露过邪恶的一角,但是两三年来,经过多个项目、各种技术杂志/论坛/Blog的折射和放大,它已经形成了一个名为“轻量级容器架构”的完整解决方案,并暴露出完全取代传统EJB架构的终极野心。
按照这一运动信徒们的说法,J2EE的发展史上只出现过一个错误——不幸的是,这个错误名叫EJB。与EJB提供的重量级架构不同,借助AOP和IoC机制,轻量级容器能够最大程度地降低代码对于专用接口的依赖性,以简短、轻便、专注、可移植的方式实现业务对象。
从“轻量级容器架构”这个词被发明出来的那一刻起,人们对J2EE远景的考虑就发生了根本性的分裂:Sun和大部分主流厂商更多地关注于“WebServices”和“快速开发工具”这些利润增长点,而一部分离经叛道的独立专家和开发者则认为,如果不把轻量级容器纳入规划,J2EE的发展蓝图就注定无足称道。
其实,双方争执的关键是传统意义上的“应用服务器”的存亡——如果所有企业级服务都可以通过AOP机制提供给普通Java对象,如果管理业务对象生命周期的可以是一个最微不足道的“微内核”,那么深盔重铠的应用服务器还有什么存在理由?
而如果失去了应用服务器的这个产品类型,那些靠这项销售起家的厂商又将何以自处?正是在这里,两个阵营之间存在着最深刻的利益分歧;而这场争执的结局当然也将决定J2EE(乃至Java企业开发)的最终走向。
或许两年之后,我们将从纷争中胜利者一方的角度重述J2EE的整部历史——或许两年之后的J2EE本身也将随着纷争的解决而成为历史。但让我们换个乐观的口吻:问世五年,J2EE的历史仍在持续的创生之中;此时善待这树种的人,也必在今后的树荫下获得它的祝福。