1. 我们不禁要问,什么是"服务集群"?什么是"企业级开发"?
既然说了EJB 是为了"服务集群"和"企业级开发",那么,总得说说什么是所谓的"服务集群"和"企业级开发"吧!
这个问题其实挺关键的,因为J2EE 中并没有说明白,也没有具体的指标或者事例告诉广大程序员什么时候用EJB 什么时候不用。
于是大家都产生一些联想,认为EJB"分布式运算"指得是"负载均衡"提高系统的运行效率。
然而,估计很多人都搞错了,这个"服务群集"和"分布式运算"并没有根本解决运行负载的问题,尤其是针对数据库的应用系统。
为什么?
我们先把EJB 打回原形给大家来慢慢分析。
2. 把EJB 掰开了揉碎了 ,我们把EJB 的概念好好的分析一下,看看能发现些什么蛛丝马迹。
3.1 EJB 概念的剖析
我们先看一下,EJB 的官方解释:
商务软件的核心部分是它的业务逻辑。业务逻辑抽象了整个商务过程的流程,并使用计算机语言将他们实现。
……
J2EE 对于这个问题的处理方法是将业务逻辑从客户端软件中抽取出来,封装在一个组件中。
这个组件运行在一个独立的服务器上,客户端软件通过网络调用组件提供的服务以实现业务逻辑,而客户端软件的功能单纯到只负责发送调用请求和显示处理结果。
在J2EE 中,这个运行在一个独立的服务器上,并封装了业务逻辑的组件就是EJB(Enterprise JavaBean)组件。
这其中我们主要关注这么几点,我们来逐条剖析:
剖析1:所谓:"业务逻辑" 我们注意到在EJB 的概念中主要提到的就是"业务逻辑"的封装,而这个业务逻辑到底是什么?
说的那么悬乎,其实这个所谓的"业务逻辑"我们完全可以理解成执行特定任务的"类"。
剖析2:所谓:"将业务逻辑从客户端软件中抽取出来,封装在组件中……运行在一个服务器上"
既然我们知道了"业务逻辑"的概念就是执行特定任务的"类",那么,什么叫"从客户端软件中抽取出来"?
其实,这个就是把原来放到客户端的"类",拿出来不放到客户端了,放到一个组件中,并将这个组件放到一个服务器上去运行。
3.2 把EJB 这个概念变成大白话
变成大白话就是,"把你编写的软件中那些需要执行制定的任务的类,不放到客户端软件上了,而是给他打成包放到一个服务器上了"。
3.3 发现问题了
不管是用"八股文"说,还是用大白话说这个EJB 概念都提到了一个词--"客户端软件"。"客户端软件"?难道EJB 的概念中说的是C/S 软件?
是的,没错!
EJB 就是将那些"类"放到一个服务器上,用C/S 形式的软件客户端对服务器上的"类"进行调用。
快崩溃了吧!
EJB 和JSP 有什么关系?EJB 和JSP 有关系,但是关系还真不怎么大,至多是在JSP 的服务器端调用远端服务上的EJB 类,仅此而已。
4 .1 EJB 的最底层究竟是什么
我们揭开了EJB"八股"概念的真谛,那么,再来分析EJB 的底层实现技术,通过底层实现技术来分析EJB 的工作方式。
4.2 EJB 的实现技术
EJB 是运行在独立服务器上的组件,客户端是通过网络对EJB 对象进行调用的。在Java中,能够实现远程对象调用的技术是RMI,而EJB 技术基础正是RMI。
通过RMI 技术,J2EE将EJB 组件创建为远程对象,客户端就可以通过网络调用EJB 对象了。
4.3 看看RMI 是什么东东
在说RMI 之前,需要理解两个名词:
对象的序列化
分布式计算与RPC
名词1:对象的序列化
对象的序列化概念:对象的序列化过程就是将对象状态转换成字节流和从字节流恢复对象。
将对象状态转换成字节流之后,可以用java.io 包中的各种字节流类将其保存到文件中,或者通过网络连接将对象数据发送到另一个主机。
上面的说法有点"八股",我们不妨再用白话解释一下:
对象的序列化就是将你程序中实例化的某个类的对象,
比如,你自定一个类MyClass,或者任何一个类的对象,将它转换成字节数组,也就是说可以放到一个byte 数组中,
这时候,你既然已经把一个对象放到了byte数组中,那么你当然就可以随便处置了它了,用得最多的就是把他发送到网络上远程的计算机上了。
如图所示。
名词2:分布式计算与RPC
RPC 并不是一个纯粹的Java 概念,因为在Java 诞生之前就已经有了RPC 的这个概念,RPC是"Remote Procedure Call"的缩写,也就是"远程过程调用"。
在Java 之前的大多数编程语言,如,Fortran、C、COBOL 等等,都是过程性的语言,而不是面向对象的。
所以,这些编程语言很自然地用过程表示工作,如,函数或子程序,让其在网络上另一台机器上执行。
说白了,就是本地计算机调用远程计算机上的一个函数。
如下图所示。
名词3:二者结合就是RMI
RMI 英文全称是"Remote Method Invocation",它的中文名称是"远程方法调用",它就是利用Java 对象序列化的机制实现分布式计算,实现远程类对象的实例化以及调用的方法。
说的更清楚些,就是利用对象序列化来实现远程调用,也就是上面两个概念的结合体,利用这个方法来调用远程的类的时候,就不需要编写Socket 程序了,也不需要把对象进行序列
化操作,直接调用就行了非常方便。远程方法调用是一种计算机之间对象互相调用对方函数,启动对方进程的一种机制,使用这种机制,某一台计算机上的对象在调用另外一台计算机
上的方法时,使用的程序语法规则和在本地机上对象间的方法调用的语法规则一样。
如图所示。
4.4 优点
这种机制给分布计算的系统设计、编程都带来了极大的方便。只要按照RMI 规则设计程序,可以不必再过问在RMI 之下的网络细节了,如:TCP 和Socket 等等。
任意两台计算机之间的通讯完全由RMI 负责。调用远程计算机上的对象就像本地对象一样方便。
RMI 可将完整的对象作为参数和返回值进行传递,而不仅仅是预定义的数据类型。也就是说,可以将类似Java 哈西表这样的复杂类型作为一个参数进行传递。
4.5 缺点
如果是较为简单的方法调用,其执行效率也许会比本地执行慢很多,即使和远程Socket机制的简单数据返回的应用相比,也会慢一些,原因是,其在网络间需要传递的信息不仅仅
包含该函数的返回值信息,还会包含该对象序列化后的字节内容。
4.6 EJB 是以RMI 为基础的
通过RMI 技术,J2EE 将EJB 组件创建为远程对象,EJB 虽然用了RMI 技术,但是却只需要定义远程接口而无需生成他们的实现类,这样就将RMI 技术中的一些细节问题屏蔽了。
但不管怎么说,EJB 的基础仍然是RMI,所以,如果你想了解EJB 的原理,只要把RMI的原理搞清楚就行了。你也就弄清楚了什么时候用EJB 什么时候不需要用EJB 了。
5. EJB 中所谓的"服务群集"
既然已经知道了,RMI 是将各种任务与功能的类放到不同的服务器上,然后通过各个服务器间建立的调用规则实现分布式的运算,也就明白EJB 所谓的"服务群集"的概念。
就是将原来在一个计算机上运算的几个类,分别放到其他计算机上去运行,以便分担运行这几个类所需要占用的CPU 和内存资源。
同时,也可以将不同的软件功能模块放到不同的服务器上,当需要修改某些功能的时候直接修改这些服务器上的类就行了,修改以后所有客户端的软件都被修改了。
如图所示。
6. 这种部署难道是无懈可击
图2 14所示的这个"服务群集"看似"无懈可击",其实是它这个图没有画完整,我们来把这个图画完整,再来看看有什么问题没有。
6.1 瓶颈在数据库端 仔细观察之后,发现这种配置是有瓶颈的,如图所示。
我们看看图2 15的结构图,现在如果想实现各个服务器针对同一个数据库的查询,那么,不管你部署多少个功能服务器,都需要针对一个数据库服务器进行查询操作。
也就是说,不管你的"计算"有多么"分布"也同样需要从一台服务器中取得数据。
虽然,看起来将各个功能模块分布在不同的服务器上从而分担了各个主计算机的CPU 资源,然而,真正的瓶颈并不在这里,
而是,数据库服务器那里。数据库服务器都会非常忙的应付各个服务器的查询及操作请求。
因此,通过这个结构图使我们了解到了EJB 根本不能完全解决负载的问题,因为,瓶颈并不在功能模块的所在位置,而是在数据库服务器这里。
6.2 假如分开数据库,数据共享怎么办
有的读者一定会想到下面的这个应用结构,如图所示。
就是把每一个功能服务器后面都部署一个数据库,这样不就解决了上节所说的问题了吗?
是的解决了数据库查询负载的问题,然而又出现了新的问题,就是"数据共享"的问题就又不容易解决了。
6.3 网络面临较大压力,让你的应用慢如老牛我们再向前翻,看看如图2 15所示的这种架构中存在两个网络,
一个是"A 网"一个是"B网",这两个网络是不同的。
"B 网"往往是局域网,一般带宽是10M/100M,速度较快,因此到还好说,然而,"A 网"往往是互联网或者是利用电信网络互联VPN 网或称广域网。
"A 网"的特点是带宽一般较窄,如ADSL 的网络仅仅有512K-2M 的带宽,由于广域网互联的成本较高,所以一般不会有较高的带宽。
而在这个网络上恰恰跑的是功能模块和客户端软件之间交换的数据,而这部分数据恰恰优势非常占用带宽的。
因此,这个应用架构其运行速度可以想见是多么的慢了。说句不夸张的话,有点想老牛拉破车一样的慢。
一个如老牛的系统:
目前在中国互联网做运营商网络管理系统的一个大公司,它的一个早期的网管软件就是采用了这种架构来做的C/S 结构的应用系统。
有一次,我作为评估者来对其应用系统进行评估,将其部署到一个非运营商大型的网络中的时候,便出现了我们上述描述的情况,速度已经到了难以忍受的地步,打开一个流量图,
有时候需要用15分钟的时间才能呈现完整。然而,该系统在开发阶段并没有发现这个问题,为什么呢?因为,他们没有考虑到应用的实际用户连接网络的复杂性,从而给该公司造成较
大损失,以至于,这个开发架构被最终遗弃。
7. EJB 活学活用,J2EE 不是必须使用EJB
通过上面小节的讲解似乎好像EJB 和开发Web 应用的B/S 结构的系统关系并不大,其实倒也不然。我们如果把"客户端程序"理解成某一台服务器,这样也是可以被应用的,而且,
如果是服务器互相之间做EJB 的调用的话,也就不存在广域网带宽限制的问题了。但是,如下情况尽量就不要使用EJB 了:
1、较为简单的纯Web 应用开发,不需要用EJB。
2、需要与其他服务程序配合使用的应用,但调用或返回的自定义的网络协议可以解决的应用程序,不需要使用EJB。
3、较多人并发访问的C/S 结构的应用程序,尽量不要使用EJB。
总结:
a.EJB实现原理: 就是把原来放到客户端实现的代码放到服务器端,并依靠RMI进行通信。
b.RMI实现原理 :就是通过Java对象可序列化机制实现分布计算。
c.服务器集群: 就是通过RMI的通信,连接不同功能模块的服务器,以实现一个完整的功能。
=====================================================
EJB的优势和使用场景
即使在EJB 2.0备受诟病的时期,笔者也从不掩饰自己对EJB的喜爱,因为它确实体现了一种非常优秀的设计思想和理念。
即使在EJB饱受争议的时期,笔者也一直希望大家用更理智的眼光来看一种技术。我们可以尽量从以下两方面来看待一种技术:
这种技术的设置初衷是什么?
这种技术到底给我们带来了什么?
从某种意义上来看,EJB是一种大型分布式企业应用开发架构的先驱尝试者,它试图解决这种企业应用底层那些系统级的问题,系统提供一种可重用的、通用的解决方案。
回顾EJB出现以前的Java应用开发,大部分开发者直接用JSP页面,再加上少量Java Bean就可以完成整个应用,所有的业务逻辑、数据库访问逻辑都直接写在JSP页面中。
系统开发前期,开发者不会意识到有什么问题,
但随着开发进行到后期,应用越来越大,开发者需要花费大量时间去解决非常常见的系统级问题,
反而无暇顾及真正需要解决的业务逻辑问题。
对于EJB来说,它提供了一种良好的组件封装,EJB容器负责处理如事务、访问控制等系统级问题,而EJB开发者则集中精力去实现业务逻辑;
对页面开发者而言,EJB的存在无须关心,EJB的实现无须关心,他们只要调用EJB的方法即可。图8.1显示了以EJB为核心的应用程序结构。
图8.1 以EJB为核心的应用程序结构
从图8.1中可以看出,在以EJB为核心的应用程序中,业务逻辑开发者的主要精力将集中在EJB组件的开发上,EJB组件是一种可移植的、与前端技术无关的服务器端组件。
虽然图8.1的结构中绘制的是以JSP页面来调用EJB组件,但实际上前端可能还需要使用MVC框架。
但无论如何,基于EJB的程序架构总体有一个非常优秀的思想:
业务逻辑相关的实现集中在EJB中完成,而EJB容器则负责提供带有重复性质的、系统级的功能,这样EJB组件就可对外提供完整的业务服务。
按照Sun公司的设计初衷:
EJB容器应该是标准的,那么开发者写好的EJB组件就可以在任何EJB容器之间自由移植;
而且按Sun的最初的EJB 1.0规范,EJB本身就是建立在RMI基础之上的,这样就允许客户端程序从远程来调用EJB内的方法。
这就难怪Sun公司对EJB寄予厚望了,因为它确实是无数软件开发人的梦想:
一个一个的EJB,只要将它们部署在EJB容器中,它们就会组成一个完备的业务层,具有很好的可移植性、很好的可扩展性。
笔者一直对Sun公司充满一种很复杂的感情:伟大的Sun公司,设计出EJB这种划时代的规范,却被无数开发者诟骂,直至沦落为今天被收购。
由于EJB 2拥有无比健壮的特性,而且还考虑到远程访问等大量特性,所以导致EJB 2的开发有点复杂。因此导致许多开发者的批评,以致后来直接催生了Spring框架。
在笔者看来,Spring框架实际上大量参考了EJB的设计理念(不知道Spring的开发者是否意识到这一点),只是Spring摈弃了EJB开发中的3大烦琐之处:
EJB组件的接口和类必须继承指定接口或类。
需要大量使用XML配置文件。
EJB组件必须打包成JAR包。
而Spring框架的整体设计,与EJB的设计可谓殊途同归,图8.2显示了以Spring为核心的应用程序结构。
将图8.1和图8.2两张结构图放在一起对比,发现它们之间的差异主要有两点:
Spring容器取代了原有的EJB容器,因此以Spring框架为核心的应用无须EJB容器支持,可以在Web容器中运行。
Spring容器管理的不再是复杂的EJB组件,而是POJO(Plain Old Java Object) Bean。
对于Spring的作者而言,他已经深深地吃透了EJB的设计理念,并遵循这种理念开发出了一个开源的Spring框架。
换个角度来看,Spring容器又何尝不是另一个Bean容器,只是这个Bean容器并未遵循Sun公司的EJB容器规范。
图8.2 以Spring为核心的应用程序结构
不管Spring的作者是否意识到,其实他对EJB的很多设计思想还是推崇的,甚至可以说他也是需要EJB、EJB容器这种结构的,只是他觉得开发EJB组件的步骤有点复杂、烦琐。
而且对于有些中小型的应用来说,EJB的远程访问支持并不是必需的,而Spring的POJO Bean反而更加简单、易用。
对于应用开发者而言,Spring容器要求的Bean简单得多,这些Bean无须实现任何接口或继承任何基类,无须单独为每个Bean类使用XML配置文件,无须打包成JAR文件……
这个世界简单了,于是开发者们笑了。
对于应用使用者而言,轻量级的Spring容器已经取代了昂贵的EJB容器,以Spring为核心的轻量级Java EE应用不再需要应用服务器(如WebLogic、WebSphere等),
只需要普通的Web服务器(如Resin、Tomcat等)即可,花费降下来了,所以应用使用者们也笑了。
通过上面介绍,读者可能会发现一个问题,不管是以Spring+Hibernate为核心的轻量级Java EE应用,还是以EJB为核心的经典Java EE应用,它们的结构其实殊途同归。
如果从花费上来看,轻量级Java EE应用往往更加具有吸引力,这就是我们看到许多中小型企业开发的应用都是以Spring+Hibernate为核心的原因。
EJB 3的出现成为了EJB规范的巨大转机。
就简单、易用性方面来说,EJB 3的开发并不比Spring容器中POJO Bean复杂多少(后面我们会看到)。
而且真正商用的应用服务器提供了Spring容器更多的支持,例如EJB的池化管理、服务器节点的集群管理等。
由此可见,对于规模较小、伸缩性要求不大的企业级应用而言,使用以Spring+Hibernate为核心的技术来开发即可。
但对于具有如下3个特征的企业级应用来说,选择以EJB为核心的经典Java EE技术可能更合适。
应用的规模较大,而且增长速度快速。
应用的伸缩性要求很高。
应用可能需要使用除JSP页面之外的其他客户端。
=======================================================================
EJB(Enterprise Java Bean)是JavaEE中面向服务的体系架构的解决方案。
可以将功能封装在服务器端,以服务的形式对外发布,客户端在无需知道方法细节的情况下来远程调用方法,大大降低了模块间的耦合性,提高了系统的安全性和可维护性。
本章将介绍EJB的作用,创建一个基于EJB的程序,讲解EJB的配置以及会话Bean的使用。
EJB(Enterprise Java Bean)是JavaEE中面向服务的体系架构的解决方案,可以将功能封装在服务器端,以服务的形式对外发布,
客户端在无需知道方法细节的情况下来远程调用方法,大大降低了模块间的耦合性,提高了系统的安全性和可维护性。
本章将介绍EJB的作用,创建一个基于EJB的程序,讲解EJB的配置以及会话Bean的使用。
17.1 为什么需要EJB
要想知道为什么要使用EJB,就需要知道"面向服务"的概念。
"面向服务",是软件开发过程中,异构环境下模块调用的一个比较重要的思想。
同样,面向服务也只是一种设计思想,不是一种编程技术。由"面向服务"的思想,业界提出了"面向服务的体系结构(Service Oriented Architecture, SOA)"的概念。
用一个实际案例来引入"面向服务"的概念。在某些大型应用场合,我们要在不同的运行环境之间传递数据,比如:
A公司需要从B公司的数据库中查询一些内容之后返回,进行处理,如何实现?
最简单的结构,如图17-1所示:
图17-1 最简单的两公司之间互相调用的结构
但是,以上程序在实际操作中,是不能实现的。
因为JDBC代码写在A公司部分,那就必须让A公司的程序知道B公司数据库的详细结构。
在一般情况下,这是不合理的。比如,一个公司通过自己的平台向银行转账,不可能知道银行数据库的结构。
于是,程序可以变为如图17-2所示结构:
图17-2 改进的结构
该结构详述如下:B公司编写自己的程序,访问数据库,对外发布一个接口,并发布一个服务的名称。
我们知道,接口里面并没有核心代码。该接口也被A公司获取,A公司网上寻找相应的B公司发布的服务名称,然后通过接口调用B公司程序里面的方法。
但是,该技术不是简单就可以实现的,因为A公司和B公司的程序,可能运行在不同的虚拟机内,甚至可能是不同的语言。
EJB可以解决A公司和B公司使用的都是Java语言,但是处于不同的Java虚拟机的情况。
该问题的原型是:一个Java虚拟机内的对象能否远程调用另外一个Java虚拟机里面的对象内的方法?
实际上,在Java内,该技术可以用RMI(远程方法调用)实现。而EJB的底层,就是用RMI实现的。
实际上,即使是在同一个Java虚拟机内,将某个功能以服务的形式对外发布,被该虚拟机中的另一个模块调用,也是可以大大降低耦合性的。
因为模块之间打交道的,只是一个接口和一个服务名称。
不过,顺便需要提到的是,如果两个程序使用的是不同语言平台,如一个是C,一个是Java,业界中也提出了一些方法来解决数据交换问题,如WebService、CORBA等。
17.2 EJB框架的基本原理
17.2.1 EJB框架简介
如前所述,EJB实际上是服务器端运行的一个对象,只不过该对象所对应的类并不被客户端所知,该对象对外发布的是一个服务名称,并提供一个可以被客户端调用的接口。
通俗点说,EJB就是一个可以被客户端调用,但是并不让客户端知道源代码的类的对象。
因此,EJB并不是普通的Java Bean,普通的JavaBean是一个符合某种规范的Java类文件,只能作为一个类被调用,只有调用的时候才运行,是一个进程内组件。
而EJB并不是一个单独的文件,其组成包括:
1. 类文件:实现基本方法的类,封装了需要实现的商务逻辑,数据逻辑或消息处理逻辑,具有一定的编程规范,代码不能被客户端得知。
2. 接口文件:接口是EJB组件模型的一部分,里面提供的方法一般和需要被远程调用的方法一致,一般情况下,要求类文件必须和接口中的定义保持一致性。
3. 必要的情况下,编写一些配置文件,用于描述EJB部署过程中的一些信息。
EJB可以作为一个服务被调用,可以单独运行,是一个进程级组件。
EJB中还提供了一些安全管理、事务控制功能,使得我们调用EJB时,不需要太多地束缚于这些问题的编码。
EJB 定义了四种类型的组件:
1. Session Bean:会话Bean,封装业务逻辑,负责完成某个操作。根据生命周期的不同,又可以分为:
(1) Stateless Session Bean: 无状态会话Bean,不存储用户相关信息,一般说来,在服务器端,一个Bean对象可能为很多客户服务,如图17-3所示:
无状态会话Bean的使用
由于一个Bean对象可能为多个客户服务,因此,一般不在对象内保存某个客户的状态,保存也没有意义。
(2) Stateful Session Bean: 有状态会话Bean,可以存储用户相关信息,在服务器端,一个Bean对象只为一个客户服务,如图17-4所示:
图17-4 有状态会话Bean的使用
由于一个Bean对象只为一个客户服务,因此,可以在对象内保存某个客户的状态。
2. Entity Bean:实体Bean,类似Hibernate,封装数据库中的数据,代表底层数据的持久化对象,把表中的列映射到对象的成员,主键在实体Bean中具有唯一性,一个实体Bean对象 对应表中的一行,
3. Message Driven Bean:消息驱动Bean,是一种异步的无状态组件,和无状态会话组件具有相似性,是JMS消息的消费者,可以和JMS配合起来使用。
17.2.2 EJB运行原理
本章所讲解的EJB,特指会话Bean。
在EJB中,常用的的组件有:客户端、接口(远程接口或者本地接口)、EJB实现类、JNDI名称等。它们之间的关系如图17-5所示:
图17-5 EJB组件之间的关系
对于一个业务操作,其执行步骤为:
首先,服务器端将EJB发布为一个JNDI名称,并提供一个接口文件。
不过,值得注意的是,如果客户端和EJB运行在同一个容器内,可以提供的是本地(Local)接口,如果运行在不同的Java虚拟机内,提供的是远程(Remote)接口。
接下来步骤如下:
1. 客户端向服务器发起连接,在服务器上寻找相应的JNDI名称,如果找到,返回一个对象。
2. 客户端将该对象强制转换为接口类型。
3. 客户端调用接口中的方法,实际上调用了服务器端EJB内的方法。
因此,利用EJB编程,有以下几个步骤:
1. 编写EJB实现类。
2. 编写接口。
3. 部署到服务器中,设定JNDI名称。
4. 编写客户端,并将接口拷贝给客户端,将JNDI名称公布,客户端调用EJB。