J2EE Design Patterns |
作者:佚名 发布时间:2005-04-02 来源:不详
|
简单来说,Design Pat 些相同或者相近的问题,每 们提供一些能够解决这些常
|
ten 就是一个常用的方案。 在 次我们都会去寻找一个新的解决 见问题的,被证实可行的方案,
|
我们的开发过程中,经常会遇到一 方法,为了节省时间提高效率,我 构成一个统一的资源库。
|
一个Design Patten描 最常用的模式。 这些模式 设计J2EE应用时得到体现。
|
述了一个被证实可行的方案。这 可以被重用,有良好的伸缩性,
|
些方案非常普通,是有完整定义的 而这些Design Patten的优势将在
|
如果开发一个企业级应 况是,我们必须面对运行在 ,我们不得不开发不同的应 起,可能会出现重复的数据
|
用,只需要一种客户端的话,那 各种设备上客户端,象PDA,WAP 用程序来处理来自不同客户端的 访问,导致整个开发周期没有必
|
么一切都非常容易解决。但真实情 浏览器以及运行在桌面上的浏览器 请求。数据访问与现实将混淆在一 要的延长。
|
Model-View-Controller (MVC) 开发 访问和数据表现。你可以开发一个有伸缩 所示为整个模式的结构。MVC模式可以被
|
模式被证明是有效的处理方法之一。它可以分离数据 性的,便于扩展的控制器,来维护整个流程。如图1 映射到多层企业级的J2EE应用上。
|
§ 视图可以通过模式访问数据,并 改变的时候,数据显示也必须同时改变。
|
根据客户端的要求来显示数据。视图必须保证当模式
|
§ 控制器用来结合模 并且根据请求以及执行结果
|
式和视图,把客户端来的请求转 来决定下一次显示那一个视图。
|
换成模式能够理解并执行的请求,
|
§ 应用的商业逻辑由MVC中的模式也 对数据的访问请求。
|
就是EJB来表现。模式必须处理由控制器传递过来的
|
§ 多个页面组成了MVC中的视图,这些视图必须随模式一起更新。 |
§ 控制器是一系列接 并决定显示那一个页面当模
|
收用户动作的对象,他们把用户 式处理完请求后。
|
的请求转换成模式可理解的请求,
|
§ MVC结构适用于那些多用户的,可扩展的,可维护的,具有很高交互性的系统。 |
§ 很方便的用多个视图来显示多套数据,是系统很方便的支持其他新的客户端类型。 |
§ 由于分离了模式中 产品推向市场的时间。
|
的流控制和数据表现,可以分清
|
开发者的责任,另外,也可以加快
|
MVC给出了一个整个应 某一个应用中,用户看到的 而这些页面之间含有高度的 面的集合,维护和扩展变得
|
用的松散的耦合架构。现在来看 视图和他所做的操作密切相关。 依赖性。在没有任何模式的时候 异常困难。
|
一下这样一个经常发生的情况。在 这是一些具有高度交互性的页面, ,这个应用只是一个许多独立的页
|
§ 当一个页面移动后,其他含有这个页面链接的文件,都必须修改。 |
§ 当有一系列页面需要口令保护时 记。
|
,许多配置文件需要修改,或者页面需要包含新的标
|
§ 当一个页面需要一个新的表示层时,页面中的标记要被重新安排。 |
当这个系统变得复杂时,这些问题将 管理控制器和视图之间交互的问题。
|
变得更糟。如果用MVC来解决的话,就变成一个如何
|
前台控制模式可以解决 主要的对象将处理所有的请 视图显示以及其他功能实现 可以在所有视图中反映出来
|
这个问题。这个模式中,所有的 求,决定以后显示那一个视图, 集中到一个主要的对象中,将使 。
|
请求都被传送到一个对象中。这个 以及实现必要的安全需求。对于把 修改变得很容易,对应用的修改,
|
§ 这个模式对于需要 效的。
|
在多个含有动态数据的页面之间
|
进行复杂导航的系统来说,是很有
|
§ 这个模式对于要在所有页面中都包含模板,转换等的应用来说,也是很有效的。 |
§ 由于视图的选择集中在前端控制 置。
|
器上,因此,视图的导航变得更加容易理解和便于配
|
§ 视图之间的复杂交 得难以维护。不过,大部分
|
互,使得控制器变得复杂。从而 情况下可以用XML映射来解决。
|
,当应用发展的时候,控制器将变
|
§ RequestMappings.x
|
ml 文件映射了传入的请求,处
|
理器以及下一个页面
|
requiresSecurityCheck="true" |
nextScreen="screen2.jsp"> |
com.blah1.blah2.blah3.request1Handler |
以上这个文件是控制器的指定配置,控制器的代码如下: |
§ FrontControllerImpl.java 利用
|
上面的XML实现了控制器
|
// exceptions to be
|
caught appropriately whereve
|
r applicable
|
public class FrontControllerImpl
|
extends HttpServlet {
|
// all required decl
|
arations, definitions
|
private HashMap requestMappings; |
// load the mappings
|
from XML file into the hash
|
map
|
public void doPost(HttpServletRe
|
quest request,
|
HttpServletResponse response) |
throws IOException, ServletException |
doGet(request, response); |
public void doGet(Ht
|
tpServletRequest request, Ht
|
tpServletResponse response)
|
throws IOException, ServletException { |
String currentPage= request.getP
|
athInfo();
|
// get all mapping info for "cur
|
rentPage" from the hashmap
|
// if "securityCheck
|
Required = true", do the sec
|
urity check
|
// if "useRequestHan specified handler
|
dler = true", pass on the in
|
coming request to the
|
// forward the results to the gi
|
ven "nextScreen"
|
用这种方法实现的控制器将很容易维 能解决了。前台控制模式将使在视图和控
|
护,当应用有新的变动的时候,只要修改XML文件就 制器之前有复杂交互的J2EE应用变得简单。
|
前台控制给出了一个基 模式可以使处理页面的现实 更加容易。
|
于MVC的,能有效管理用户与J2E 顺序和用户的并发请求变得简单
|
E应用之间进行的复杂交互。这个 。并且使增加和改变页面现实变得
|
另外一个常见的问题是 改变。我们来看一下这个问
|
,当EJB或者业务逻辑发生变化 题。
|
的时候,应用的客户端也必须随之
|
一般来说,为了表现一 用户名和口令,再用一个EJ 号或者修改一个已经存在的 保存,这样的一个流程。
|
个账户中的用户,我们使用一个 B来管理用户的个人信息,象爱 账号时,必须访问包含账号信息
|
业务逻辑来表示账户中的信息,象 好,语言等。当要创建一个新的账 的EJB,读取个人信息,修改并且
|
当然,这只是一个非常 些服务,检验客户信用卡的 客户端必须访问账户EJB来 何来控制一个用户订单。
|
简单的例子,实际情况可能比这 有效性,存放订单等。在这个案 完成一系列适当的工作。下面的
|
个复杂的多,象查看用户定制了哪 例中,为了实现一个完整的流程, 例子显示了一个Servlet客户端如
|
A servlet that does
|
the workflow required for pl
|
acing an order
|
// exceptions to be caught appro
|
priately wherever applicable;
|
// This servlet assu
|
mes that for placing an orde
|
r the account and
|
// credit status of
|
the customer has to be check
|
ed before getting the
|
// approval and committing the o
|
rder. For simplicity, the EJBs that
|
// represent the bus
|
iness logic of account, cred
|
it status etc are
|
public class OrderHandlingServle
|
t extends HttpServlet {
|
// all required decl
|
arations, definitions
|
// all inits required done here |
public void doPost(H
|
ttpServletRequest request, H
|
ttpServletResponse response)
|
throws IOException, ServletException { |
// other logic as required |
// Get reference to the required EJBs |
InitialContext ctxt = new Initia
|
lContext();
|
Object obj = ctxt.lo
|
okup("java:comp/env/ejb/User
|
Account");
|
UserAccountHome acctHome = (User
|
AccountHome)
|
PortableRemoteObject.narrow(obj,
|
UserAccountHome.class);
|
UserAccount acct = acctHome.create(); |
obj = ctxt.lookup("j
|
ava:comp/env/ejb/CreditCheck
|
");
|
CreditCheckHome creditCheckHome
|
= (CreditCheckHome)
|
PortableRemoteObject.narrow(obj,
|
CreditCheckHome.class);
|
CreditCheck credit = creditCheck
|
Home.create();
|
obj = ctxt.lookup("java:comp/env
|
/ejb/Approvals");
|
ApprovalsHome apprHome = (Approv
|
alsHome)
|
PortableRemoteObject
|
.narrow(obj, ApprovalsHome.c
|
lass);
|
Approvals appr = apprHome.create(); |
obj = ctxt.lookup("j
|
ava:comp/env/ejb/CommitOrder
|
");
|
CommitOrderHome orderHome = (Com
|
mitOrderHome)
|
PortableRemoteObject.narrow(obj,
|
CommitOrderHome.class);
|
CommitOrder order = orderHome.create(); |
// Acquire the customer ID and o
|
rder details;
|
// Now do the required workflow
|
to place the order
|
int result = acct.ch
|
eckStatus(customerId);
|
result = credit.checkCreditWorth
|
(customerId, currentOrder);
|
result = appr.getApprovals(custo
|
merId, currentOrder);
|
// Everything OK; place the order |
result = order.place
|
Order(customerId, currentOrd
|
er);
|
// do further processing as required |
以上的代码显示了一个单个的客户端 客户端制定一种处理方法来完成工作流程 有的参与这个流程的客户端都需要改变。 户端都必须知道这一点,如果流程中需要 改。
|
。如果这个应用支持多种客户端的话,必须为每一个 。如果有一个EJB的实现流程需要改变的话,那么所 如果不同的EJB之间的交互需要改变的话,所有的客 增加一个新的步骤的话,所有的客户端也必须随之修
|
这样一来,EJB和客户 ,致使网络速度变慢。同样
|
端之间的改变变得非常困难。客 ,应用越复杂,麻烦越大。
|
户端必须对每个EJB分开进行访问
|
解决这个问题的方法是,把客户端和 式。这个模式通过一个Session Bean,为 当客户端只是使用这个接口来触发流程。 户端无关。
|
他们使用的EJB分割开。建议适用Session Fa?ade模 一系列的EJB提供统一的接口来实现流程。事实上, 这样,所有关于EJB实现流程所需要的改变,都和客
|
看下面这个例子。这段代码用来控制与客户相关的订单的处理方法。 |
// Exception handling not shown
|
in the sample code
|
public class OrderSe
|
ssionFacade implements Sessi
|
onBean {
|
// all EJB specific methods like
|
ejbCreate defined here
|
// Here is the business method t
|
hat does the workflow
|
// required when a c
|
ustomer places a new order
|
public int placeOrder(String cus
|
tomerId, Details orderDetails)
|
// Get reference to the required EJBs |
InitialContext ctxt
|
= new InitialContext();
|
Object obj = ctxt.lookup("java:c
|
omp/env/ejb/UserAccount");
|
UserAccountHome acctHome = (User
|
AccountHome)
|
PortableRemoteObject
|
.narrow(obj, UserAccountHome
|
.class);
|
UserAccount acct = acctHome.create(); |
obj = ctxt.lookup("j
|
ava:comp/env/ejb/CreditCheck
|
");
|
CreditCheckHome cred
|
itCheckHome = (CreditCheckHo
|
me)
|
PortableRemoteObject.narrow(obj,
|
CreditCheckHome.class);
|
CreditCheck credit = creditCheck
|
Home.create();
|
obj = ctxt.lookup("j
|
ava:comp/env/ejb/Approvals")
|
;
|
ApprovalsHome apprHo
|
me = (ApprovalsHome)
|
PortableRemoteObject
|
.narrow(obj, ApprovalsHome.c
|
lass);
|
Approvals appr = apprHome.create(); |
obj = ctxt.lookup("j
|
ava:comp/env/ejb/CommitOrder
|
");
|
CommitOrderHome orderHome = (Com
|
mitOrderHome)
|
PortableRemoteObject
|
.narrow(obj, CommitOrderHome
|
.class);
|
CommitOrder order = orderHome.create(); |
// Now do the requir
|
ed workflow to place the ord
|
er
|
int result = acct.checkStatus(cu
|
stomerId);
|
result = credit.chec
|
kCreditWorth(customerId, cur
|
rentOrder);
|
result = appr.getApp
|
rovals(customerId, currentOr
|
der);
|
// Everything OK; place the order |
int orderId = order.
|
placeOrder(customerId, curre
|
ntOrder);
|
// Do other processing required |
// Implement other workflows for
|
other order related functionalities (like
|
// updating an existing order, c
|
anceling an existing order etc.) in a
|
在模式允许的情况下,Servlet代码将很容易实现。 |
// exceptions to be caught appro
|
priately wherever applicable
|
public class OrderHandlingServle
|
t extends HttpServlet {
|
// all required declarations, de
|
finitions
|
// all inits required done here |
public void doPost(HttpServletRe
|
quest request, HttpServletResponse response)
|
throws IOException, ServletException { |
// other logic as required |
// Get reference to the session facade |
InitialContext ctxt
|
= new InitialContext();
|
Object obj = ctxt.lookup("java:c
|
omp/env/ejb/OrderSessionFacade");
|
OrderSessionFacadeHome facadeHom
|
e = (OrderSessionFacadeHome)
|
PortableRemoteObject.narrow(obj,
|
OrderSessionFacadeHome.class);
|
OrderSessionFacade facade = faca
|
deHome.create();
|
// trigger the order workflow |
int orderId = facade.placeOrder(
|
customerId, currentOrder);
|
// do further processing as required |
就象上面显示的,客户端的逻辑变得 地方就可以了。客户端可以仍旧使用原来 来响应其他处理器的流程处理。这让你能 例子中,模式提供了很好的伸缩性和可维
|
非常简单。流程中的任何改变只要修改模式中的一处 的接口,而不必做任何修改。同样,这个模式可以用 用同样的模式来处理不同客户端的不同流程。在这个 护性。
|
§ 既然这种模式不涉及到数据访问,就应该用Session Bean来实现。 |
§ 对于用简单接口来实现复杂EJB的子系统来说,是一个理想的选择。 |
§ 这个模式可以减少客户端于EJB之间的通信和依赖。 |
§ 所有和EJB有关的交互,都有同一 用。
|
个Session Bean来控制,可以减少客户端对EJB的误
|
§ 所有的服务器端的实现细节都对客户端隐藏,在改变发生后,客户端不用重新发布。 |
§ 这个模式可以同样看成一个集中处理器来处理所有的安全或日志纪录。 |
目前为止,你看到的模 的把应用在多个层上来实现 的数据库语言。如果数据库
|
型都是用来构建可伸缩的,易于 。但是,还有一点必须强调:EJ 有改变的话,相应的SQL也必须
|
维护的J2EE应用。这些模式尽可能 B的数据表现。它们包括象EJB这样 改变,而EJB也必须随之更新。
|
这些常见问题就是:访 下的代码。
|
问数据源的代码与EJB结合在一
|
起,这样致使代码很难维护。看以
|
An EJB that has SQL code embedde
|
d in it
|
// exceptions not ha
|
ndled in the sample code
|
public class UserAcc
|
ountEJB implements EntityBea
|
n {
|
// All EJB methods l
|
ike ejbCreate, ejbRemove go
|
here
|
// Business methods start here |
public UserDetails getUserDetail
|
s(String userId) {
|
// A simple query for this example |
String query = "SELECT id, name, userId;
|
phone FROM userdetails WHERE name = " +
|
InitialContext ic =
|
new InitialContext();
|
datasource = (DataSource)ic.look
|
up("java:comp/env/jdbc/DataSource");
|
Connection dbConnect
|
ion = datasource.getConnecti
|
on();
|
Statement stmt = dbC
|
onnection.createStatement();
|
ResultSet result = s
|
tmt.executeQuery(queryStr);
|
// other processing like creatio
|
n of UserDetails object
|
为了解决这个问题,从而让你能很方 式把数据访问逻辑从EJB中拿出来放入独 在需要数据的时候,通过DAO来访问数据 更新DAO的对象就可以了。看以下的代码
|
便的修改你的数据访问。建议使用DAO模式。这个模 立的接口中。结果是EJB保留自己的业务逻辑方法, 库。这样的模式,在要求修改数据访问的时候,只要 。
|
A Data Access Object that encaps
|
ulates all data resource access code
|
// Exception handling code not l
|
isted below for simplicity
|
public class UserAccountDAO { |
private transient Connection dbC
|
onnection = null;
|
public UserAccountDAO() {} |
public UserDetails getUserDetail
|
s(String userId) {
|
// A simple query for this example |
String query = "SELECT id, name, userId;
|
phone FROM userdetails WHERE name = " +
|
InitialContext ic =
|
new InitialContext();
|
datasource = (DataSo
|
urce)ic.lookup("java:comp/en
|
v/jdbc/DataSource");
|
Connection dbConnect
|
ion = datasource.getConnecti
|
on();
|
Statement stmt = dbC
|
onnection.createStatement();
|
ResultSet result = stmt.executeQ
|
uery(queryStr);
|
// other processing like creatio
|
n of UserDetails object
|
// Other data access
|
/ modification methods pert
|
aining to the UserAccountEJB
|
现在你有了一个DAO对象,利用这个对象你可以访问数据。再看以下的代码。 |
// exceptions not handled in the
|
sample code
|
public class UserAccountEJB impl
|
ements EntityBean {
|
// All EJB methods like ejbCreat
|
e, ejbRemove go here
|
// Business methods start here |
public UserDetails g
|
etUserDetails(String userId)
|
{
|
// other processing as required |
UserAccountDAO dao =
|
new UserAccountDAO();
|
UserDetails details
|
= dao.getUserDetails(userId)
|
;
|
// other processing as required |
任何数据源的修改只要更新DAO就可 数据源类型,你可以开发多个DAO来实现 般情况下,EJB可以通过一个Factory对象 改变它的数据源类型。
|
以解决了。另外,为了支持应用能够支持多个不同的 ,并在EJB的发布环境中指定这些数据源类型。在一 来得到DAO。用这种方法实现的应用,可以很容易的
|
§ 这种模式特别适用于BMP。过一段时间,这种方式同样可以移植到CMP中。 |
§ DAOs增强了应用的可伸缩性,因为数据源改变变得很容易。 |
§ DAOs对数据访问没有任何限制,甚至可以访问XML数据。 |
§ 使用这个模式将导致增加一些额外的对象,并在一定程度上增加应用的复杂性 |
|
|
|
|
|