UML软件工程组织

建造灵活与可维护的J2EETM 应用程序的设计模式
Vijay Ramachandran 2002 年1月 来源: Sun

鉴于JavaTM2 企业版 (J2EETM) 已经成为服务器端应用程 序平台的首选,共享开发者的经验与设计就成了一件至关紧要的事。本 文介绍了一些可重复使用的设计模式,以便读者在建造灵活与容易维护 的J2EE应用程序时使用。

本文不对范例的正式摸板与UML图作介绍。您可以在 J2EE蓝图程序 找到这些细节与代码样例。本文所要解决的是那些能够影响J2EE应用程 序灵活性和可维护性的问题,并且给出推荐的解决方案。

什么是设计模式?

当您设计建造不同的应用程序时,您时而不时地会碰到相同或者非 常类似的问题域。每次碰到这些问题时您都必须去重新寻找解决方案 。为了节省时间与精力,如果有一个知识库,能够捕获这样的具有共 性的问题域与被证明是正确的解决方案,将是非常理想的。

用最简单的话来说,这样的通用解决方案就是一个设计模式。这样 的知识库,或者说参考处,包含了这些模式,就是设计模式目录。

式摸板进行描述,比如最流行的、由“四个伙计” Gang of Four定义的摸板。

模式摸板通常包含一个用来描述模式所代表的意义的名字,紧跟着 的是模式的适用范围、动机、在实现过程中的问题等等。除了描述问 题与阐明解决方案,模式还解释在使用本模式的实现过程中可能存在 的问题和后果。

使用这样的模式让应用程序的设计变得透明。这些模式已经被不同 的开发者在不同的领域内成功地使用过,所以,一个模式的优点和缺 点(也包括实现过程中的问题)都已经事先知道。所有的设计模式都 是可重复使用的,并且在特定的场合中适用。这就给了您灵活性。同 J2EE应用程序有关的设计模式的使用在J2EE平台技术方面提供了更多 优势来展示了解决方案。

建造灵活与可维护的J2EE应用程序的设计模式

多层的J2EE应用程序由位于中间层的一些可能是分布式的不同视图 和组件组成。在本文的以下章节中,给出了一些能帮助您让典型的J2EE 应用程序具备可扩展性、灵活性和可维护性的设计模式。本文不使用那 些抽象的文字,而使用了一个假想的实际例子,试图让您尽可能地理解 这些模式。一旦您了解了这个应用程序例子,您就能很容易地将这个例 子中使用的模式应用到其他应用程序中。

以一个Web上的金融服务企业级应用程序作为例子。浏览者可以在这 个站点上浏览服务项目列表、建造帐户、订购该金融服务机构提供的各 种产品等等。我们假定这个应用程序允许已有的客户改变各自的帐户细 节与个人资料,并使用这些服务,等等。典型地,这样的应用程序有多 个视图或者屏幕,用户可以通过鼠标点击,在这些视图间切换,查找服 务项目列表,存取个人资料,使用各种服务项目,或者取得其他信息。 事务逻辑代表着用户的帐户、资料、服务目录、服务定单等等,这种形 式就如同在企业版JavaBeans(EJB)中的分开的实体。把这个例子放在心 里,然后再看看那些重复的问题,您就能明白如何使用特定的模式来建 造灵活的、可维护的应用程序。

模型-视图-控制器

问题域

如果您所建造的这个企业级应用程序只是给单一类型的客户使用的, 那问题就简单了。我们可以简单地将数据存取/数据修改逻辑与不同的 客户视图逻辑混合在一起。但是随着一个完全互连的无线世界的出现, 客户端的设备从PDA、蜂窝电话到一个功能强大的桌面系统上的浏览器 都有,这还不包括其他的传统设备。在这种情况下,将数据存取与视 图混合在一起来作为解决方案,将会有很多问题,这是因为:

  • 您必须开发这个应用程序的不同版本,以便适应与支持各种不同 的客户需要
  • 由于视图的代码与数据存取/修改的代码纠缠在一起,重复的数 据存取/修改代码散步在各处,这就使得应用程序几乎是不可维护的。
  • 开发生命周期被不必要地扩展了

建议的解决方案

为了找到这个问题的解决方案,请注意以下几点:

  • 不论客户类型如何,被存取与显示的数据必须来自同一个企业级 的数据源。
  • 所有的客户必须能够对数据源进行修改。
  • 不论是修改一个客户类型,还是修改数据存取/修改逻辑,都不应 该影响到应用程序的其他组件。

您需要一个能让您开发出松散耦合的应用程序的解决方案。建议采用 模型-视图-控制器(MVC)结构。MVC已经被非常有效地应用在GUI类型的 应用程序的设计上。通过在J2EE应用程序上采用MVC结构,您可以将数据 存取逻辑与数据表现逻辑分别开来。您也可以建造一个灵活的并且是很 容易扩充的控制器来控制应用程序的整个流程。下图展示了MVC结构。

MVC Architecture

如下所述,MVC结构可以映射到多层的企业级J2EE应用程序:

  • 所有的企业级数据与用于处理数据的事物逻辑都能用这个模型来 表示。
  • 视图可以通过这个模型存取数据,然后决定如何将数据表达给客 户。当模型改变时,视图必须确认给用户所表达的内容也跟着改变。
  • 控制器可以与视图交互,并且将客户的行为转换为模型可以理解 与执行的行为。控制器也根据最新的客户行为与相关的模型行为来决 定下一个要被显示的视图是什么。

将上述的逻辑应用到金融应用程序例子中,您将这样建造应用程序:

  • 应用程序的事务逻辑由组成MVC结构中的模型的EJB来代表。模型 对来自控制器的要求作出反映,并存取/修改相应的数据。
  • 应用程序界面的不同屏组成MVC结构中的视图。当模型改变时,视 图自动更新。
  • 应用程序的控制器是一些对象的集合,这些对象接收用户的行为, 并且将其转换为模型可理解的请求,一旦模型完成了对请求的处理, 就决定下一个要显示在屏幕上的内容是什么。

想要在一个小例子中将MVC结构完全展示出来是非常困难的。 J2EE蓝图 程序 中的Java宠物商店演示程序是一个很好的基于MVC结构的完整的J2EE应用 程序的例子。

注意事项

MVC结构适合于高

  • MVC结构适合于高度交互的系统,这种系统需要可扩展性、可维护性与多用 户视图。
  • MVC 将表述、用户交互与系统模型解耦合。
  • 由于不存在耦合,将多数据集表述在多视图中就变得很容易。同时也使得 为新的客户类型提供支持更为简单。
  • 使用这种结构,代码冗余被最大限度地减少了。
  • 通过将表述、模型、全面的应用程序流程这三者分开,这个结构使得不同 的开发者可以负责不同的模块,于是,产品上市的时间就快了。

前端控制器

问题域

MVC 为您提供了一种可以设计出具备松散耦合性质的应用程序的方 法。让我们看一个经常碰到的特定问题域。在这个例子应用程序中, 用户看到的视图或者屏幕内容是由用户的操作决定的。这些画面是高 度可交互的(比如期待用户选择服务项目、输入选择、联系信息等等) ,并且这些页面是高度相互依赖的。

如果不考虑任何模式,这个应用程序将是一些相互依赖的web网页的 集合。这使得该应用程序维护起来非常困难。想扩展这个应用程序,使 得它能够适应更多的web页面(比如提供更多的服务),将是困难的。 因为这些网页是相互依赖的:

  • 当移开了一个页面,其他任何存在同这个页面的链接的页面,都 必须被更新。
  • 当一些页面需要使用密码进行保护,则很多不同的配置文件就需 要被修改,否则这些页面本身就需要加入新的标记。
  • 当一个页面需要重新布局,这个页面中的标记就不许被重新编排。

随着应用程序复杂度的增加,这些问题将变得越来越严重。从MVC结 构来看,这个问题就变为如何操纵视图和控制器间的交互了。

建议的模式

为解决这些问题,建议使用前端控制器模式作为解决方案。这个模式 通过将所有的客户请求导入单一的对象——前端控制器,从而解决问题。 中心对象随之处理所有的请求,决定下一个要显示的视图,并且实现所 有为保护系统而需要的安全请求。视图的模板化也可通过这个中心化的 对象来完成。将下个视图如何决定,还有安全等其他问题中心化,能容 易地改变这些功能。您只需改变应用程序的一小部分,并且您所做的改 变将反映在应用程序的所有视图/画面中。

下列的小例程展示了如何实现这个思想。所要做的第一件事就是要确 认单一的对象——前端控制器,为所有的请求提供服务。在J2EE应用程 序中,用一个中心的小服务程序处理所有的请求将是最好的选择。为了 完成这个任务,需要按如下代码修改本应用程序的web组件中的 web.xml文件 (下面的代码片段假设/sample_app 是本例子应用程序所有视图的上下文根目录):

例程1:例子应用程序中的web.xml文件的一部分。本程序强制 所有的web请求,将其引导到一个单一的小服务器程序——前端控制器。

<web-app>
    <!-- the web.xml file of the web component of the sample application -->

    <!-- all other specs -->

    <servlet>
        <servlet-name>CentralEntryPoint</servlet-name>
        <servlet-class>FrontControllerImpl</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>CentralEntryPoint</servlet-name>
        <!-- The following forces all web page requests of this
             application to be routed through the front controller -->
        <url-pattern>/sample_app/*</url-pattern>
    </servlet-mapping>

    <!-- all other specs -->
</web-app>

以上web.xml中的规范确保所有的以/sample_app 作为上下文根目录的请求,都被引导到名为FrontControllerImpl 的中心小服务器程序。以下是FrontControllerImpl的代码:

例程2:FrontControllerImpl.java,用于处理所有请求的中 心小服务程序 ——前端控制器

// all required imports
// exceptions to be caught appropriately wherever applicable

public class FrontControllerImpl extends HttpServlet {

    // all required declarations, definitions

    public void init() {
        // do all init required
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
                 throws IOException, ServletException {
        doGet(request, response);
    }

    public void doGet(HttpServletRequest request, HttpServletResponse response)
                 throws IOException, ServletException {

        String currentPage= request.getPathInfo();

        // check the requested page
        // apply all required security checks
        // process the request
        // depending on the result of the processing, decide on the next page
        // forward to the next page
    }
}

注意在上面的FrontController小服务器程序的实现中,所有关于如 何处理请求与下一个页面的选择的决定,都是由这个小服务器程序完成 的。但是随着应用程序大小、视图的数量、不同的处理请求的增加,这 个小服务器程序代码将很快变得难以维护。但要解决这个问题是很容易 的,只需在一个XML配置文件中定义如何操作请求,如何对当前的请求 作出反应并显示在下一个视图上即可。如果您这样做,则中心小服务器 程序简单地从XML文件中建立一散列表。这个散列表以后将被用来决定 如何处理当前请求,以及下一个视图是什么,等等。能映射上述所有信 息的XML文件如下:

例程3: 将进来的请求映射到相应的处理器和下一屏的 RequestMappings.xml文件

<request-mappings>

    <url-mapping url="/request1" useRequestHandler="true"
            requiresSecurityCheck="true" nextScreen="screen2.jsp">
        <request-handler-class> com.blah1.blah2.blah3.request1Handler</request-handler-class>
    </url-mapping>

    <!-- other requests and their corresponding mappings -->

</request-mappings>

一旦有了上述给中心小服务器程序定义的规范,前端控制器小服务 器程序就必须做些改变,以下给出了改变后的代码:

例程4: 实现中心小服务器程序并且使用文件映射的 FrontControllerImpl.java

// all required imports
// exceptions to be caught appropriately wherever applicable

public class FrontControllerImpl extends HttpServlet {

    // all required declarations, definitions
    private HashMap requestMappings;

    public void init() {
        // load the mappings from XML file into the hashmap
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
             throws IOException, ServletException {
        doGet(request, response);
    }

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {

        String currentPage= request.getPathInfo();

        // get all mapping info for "currentPage" from the hashmap
        // if "securityCheckRequired = true", do the security check
        // if "useRequestHandler = true", pass on the incoming request to the specified handler
        // forward the results to the given "nextScreen"
    }
}

按这种方法实现的中心小服务器程序是很容易维护的,这是因为当 诸如屏幕流、安全需求、处理请求的方式改变时,所要更新的只有XML 映射文件。增加新屏幕也是非常简单的。要简化J2EE应用程序中复杂 的视图和控制器间的交互,前端控制器模式是非常有效的解决方法。

注意事项

以下是使用这个模式时的注意事项:

  • 对于有很多包含了动态数据的视图和屏幕,并且在这些视图和屏 幕间存在复杂的导航连接的web应用程序,这个模式是非常理想的。
  • 对于在所有的页面(或者重要的页面子集)中,存在政策、转换 与模板化的通用集合的web应用程序,这个模式也是很有用的。
  • 由于视图选择的中心化是在前端控制器中完成的,所以在不同的 视图间导航就更容易被理解与配置。
  • 视图可以很容易地被改变或者重用。
  • 在视图组件间的交互复杂性,转换成了前端控制器的复杂性。结 果是,随着应用程序的增长,控制器将变得越来越难以维护。但是, 在大多数情况下,这个问题可以用上面所描述的XML映射技术来解决。
  • 为应用程序实现安全检查需求被简化。
  • 这个模式不适合小应用程序或者显示很多静态内容的应用程序。

会话外观

问题域

前端控制器模式为您提供了一个操纵基于MVC结构的J2EE应用程序的 复杂的用户交互的有效方法。这个模式简化了屏幕流的处理与并发的用 户需求处理。它也使得增加或者改变屏幕流变得简单。

另一个通常的问题是,当代表应用程序事务逻辑的企业粒媒增加或 者改变时,应用程序的客户需求也被改变。这个问题请看例子中的情形。

典型地,为了表示用户的帐号,例子应用程序有一个实现事务逻辑 的、适合帐号细节(比如名字、登录ID与密码)的EJB,与一个处理用 户个人资料(比如个性化选择、语言选择、显示方式选择)的EJB。当 新的帐号建立,或者已经存在的帐号被修改时,客户(JSP,小服务器 程序,或者独立的应用程序客户)必须访问帐户细节的EJB和个人资料 EBJ,以便读取、保存或者更新所有的用户信息细节。这需要工作流介 入。

当然,这只是一个很简单的情况。当用户向系统要求了系统所提供 的服务,比如检查用户帐号或者信用状态、取得适当的承认、提交定单 等,工作流就更大了。在这种情况下,为了实现整个工作流,客户必 须具备对帐户EJB的访问能力,才能完成相应的任务。下面的例程展示 了处理由顾客提交的定单的小服务器程序的客户。

例程5:一个完成定单需求的工作流的小服务器程序

// all required imports;
// exceptions to be caught appropriately wherever applicable;
// This servlet assumes that for placing an order the account and
// credit status of the customer has to be checked before getting the
// approval and committing the order. For simplicity, the EJBs that
// represent the business logic of account, credit status etc are
// not listed

public class OrderHandlingServlet extends HttpServlet {

    // all required declarations, definitions

    public void init() {
        // all inits required done here
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
             throws IOException, ServletException {
        // other logic as required
        // Get reference to the required EJBs
        InitialContext ctxt = new InitialContext();
        Object obj = ctxt.lookup("java:comp/env/ejb/UserAccount");
        UserAccountHome acctHome = (UserAccountHome)
                 PortableRemoteObject.narrow(obj, UserAccountHome.class);
        UserAccount acct = acctHome.create();
        obj = ctxt.lookup("java:comp/env/ejb/CreditCheck");
        CreditCheckHome creditCheckHome = (CreditCheckHome)
                 PortableRemoteObject.narrow(obj, CreditCheckHome.class);
        CreditCheck credit = creditCheckHome.create();
        obj = ctxt.lookup("java:comp/env/ejb/Approvals");
        ApprovalsHome apprHome = (ApprovalsHome)
                 PortableRemoteObject.narrow(obj, ApprovalsHome.class);
        Approvals appr = apprHome.create();
        obj = ctxt.lookup("java:comp/env/ejb/CommitOrder");
        CommitOrderHome orderHome = (CommitOrderHome)
                 PortableRemoteObject.narrow(obj, CommitOrderHome.class);
        CommitOrder order = orderHome.create();

        // Acquire the customer ID and order details;
        // Now do the required workflow to place the order
        int result = acct.checkStatus(customerId);
        if(result != OK) {
            // stop further steps
        }
        result = credit.checkCreditWorth(customerId, currentOrder);
        if(result != OK) {
            // stop further steps
        }
        result = appr.getApprovals(customerId, currentOrder);
        if(result != OK) {
            // stop further steps
        }

        // Everything OK; place the order
        result = order.placeOrder(customerId, currentOrder);

        // do further processing as required
    }
}

上面的代码展示的是单个客户。如果应用程序支持多种类型的客户, 则不同客户分别有各自不同的实现工作流逻辑的代码。在这种情况下, 假如其中一个用于实现工作流某个部分的EJB被修改,那么所有初始化 工作流的客户就必须随之改变。如果在实现工作流的EJB间的交互的性 质发生了改变,则必须通知客户这个改变。如果在工作流中以新EJB的 形式引入了新的步骤,所有的客户就需要再更改一次。

管理需要在客户间进行通信更改的EJB是非常困难的。而且,为了实 现整个工作流,客户不得不为每一个EJB分别进行远程调用。这增加了 网络流量,并且效率不高。当应用程序的复杂度增加时,这个问题将变 得越发严重。

建议的模式

这个不断发生的问题的解决方案是,将客户和他们所使用的EJB集中 改变隔离开来。建议的解决方案是使用会话外观 模式。以会话粒媒来实 现的外观为一组实现工作流的企业粒媒提供一个统一的界面。于是,触 发工作流的客户使用这个由外观提供的统一界面。底层的实现工作流的 EJB的任何改变,与工作流本身的任何改变,只会导致外观的改变。

下面的例程实现了上述思想。先回顾一下处理顾客定单的工作流的 外观代码。

例程6:处理提交定单需求的工作流的会话外观.

// All imports required
// Exception handling not shown in the sample code

public class OrderSessionFacade implements SessionBean {

    // all EJB specific methods like ejbCreate defined here

    // Here is the business method that does the workflow
    // required when a customer places a new order

    public int placeOrder(String customerId, Details orderDetails)
                throws RemoteException {
        // Get reference to the required EJBs
        InitialContext ctxt = new InitialContext();
        Object obj = ctxt.lookup("java:comp/env/ejb/UserAccount");
        UserAccountHome acctHome = (UserAccountHome)
                 PortableRemoteObject.narrow(obj, UserAccountHome.class);
        UserAccount acct = acctHome.create();
        obj = ctxt.lookup("java:comp/env/ejb/CreditCheck");
        CreditCheckHome creditCheckHome = (CreditCheckHome)
                 PortableRemoteObject.narrow(obj, CreditCheckHome.class);
        CreditCheck credit = creditCheckHome.create();
        obj = ctxt.lookup("java:comp/env/ejb/Approvals");
        ApprovalsHome apprHome = (ApprovalsHome)
                 PortableRemoteObject.narrow(obj, ApprovalsHome.class);
        Approvals appr = apprHome.create();
        obj = ctxt.lookup("java:comp/env/ejb/CommitOrder");
        CommitOrderHome orderHome = (CommitOrderHome)
                 PortableRemoteObject.narrow(obj, CommitOrderHome.class);
        CommitOrder order = orderHome.create();

        // Now do the required workflow to place the order
        int result = acct.checkStatus(customerId);
        if(result != OK) {
            // stop further steps
        }
        result = credit.checkCreditWorth(customerId, currentOrder);
        if(result != OK) {
            // stop further steps
        }
        result = appr.getApprovals(customerId, currentOrder);
        if(result != OK) {
            // stop further steps
        }

        // Everything OK; place the order
        int orderId = order.placeOrder(customerId, currentOrder);

        // Do other processing required

        return(orderId);
    }

    // Implement other workflows for other order related functionalities (like
    // updating an existing order, canceling an existing order etc.) in a
    // similar way
}

只要这个外观可用,例程5的小服务器程序代码就很容易实现了,请 看下面的代码:

例程7:一个使用会话外观来下定单的小服务器程序

// all required imports
// exceptions to be caught appropriately wherever applicable

public class OrderHandlingServlet extends HttpServlet {

    // all required declarations, definitions

    public void init() {
        // all inits required done here
    }

    public void doPost(HttpServletRequest 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:comp/env/ejb/OrderSessionFacade");
        OrderSessionFacadeHome facadeHome = (OrderSessionFacadeHome)
                 PortableRemoteObject.narrow(obj, OrderSessionFacadeHome.class);
        OrderSessionFacade facade = facadeHome.create();

        // trigger the order workflow
        int orderId = facade.placeOrder(customerId, currentOrder);

        // do further processing as required
    }
}

如上面的代码所展示的,由于有了外观,客户逻辑(在本例中,就 是小服务器程序)变得非常简单。任何对工作流的改变,只需要在外观 中作相应改变即可。不再需要更新客户,因为它们可以继续使用相同的 界面。而且,相同的外观可以实现其他程序的工作流(在例程中,举个 例子,工作流与定单相关,比如更新或者删除一个已经存在的定单)。 这样,在需要不同的工作流来处理不同的客户行为时可以使用相同的外 观。在这种情况下,外观提供的灵活性,让应用程序更容易维护,就显 得尤为重要。

注意事项

在决定使用这个模式时,需要注意如下几点:

  • 由于外观并不代表数据库中的数据,所以它是以会话粒媒的形式来实现的。
  • 当复杂的企业粒媒子系统需要一个简单的界面时,这个模式是个理想的选 择。
  • 当没有工作流介入时,本模式绝对不适合。
  • 这个模式让通信减少,并且让客户对象与企业粒媒间的依赖程度减少。
  • 由于EJB间的交互仅仅由会话外观处理,所以客户错误地使用EJB的机会也 随之减少。
  • 这个模式使得支持多客户类型变得容易。
  • 外观也能减少网络流量。
  • 由于对客户而言服务器的实现细节是隐藏的,应用程序服务的实现和修改 就无需对客户重新配发。
  • 外观也可以作为一个中心,处理安全检查与所有同登录有关的任务。

数据存取对象

问题域

到目前为止您已经看到了一些能够建造灵活的与容易维护的J2EE应 用程序的模式。这些模式,从本质上而言,可尽量地解开应用程序中 不同层间的耦合。这里还有一种耦合需要提及:代表数据库中数据的 EJB,包括同特定数据库有关的代码(比如SQL)。对于具有特定数据 库SQL代码的EJB,从某种意义上说,是同数据库类型相耦合的。如果 数据库类型改变了,所有这样的EJB就必须改变。

这个问题更全面的定义是:在EJB中如果有同特定数据源有关的代 码,则应用程序和数据库类型间将形成紧密耦合。结果使应用程序失 去灵活性与可维护性。下面的代码介绍一段嵌有SQL代码的EJB。为了 简单起见,代码home与EJB的接口没有给出。

例程8: 一段嵌有SQL代码的EJB

// all imports required
// exceptions not handled in the sample code

public class UserAccountEJB implements EntityBean {

    // All EJB methods like ejbCreate, ejbRemove go here

    // Business methods start here

    public UserDetails getUserDetails(String userId) {

        // A simple query for this example
        String query = "SELECT id, name, phone FROM userdetails WHERE name = " + userId;

        InitialContext ic = new InitialContext();
        datasource = (DataSource)ic.lookup("java:comp/env/jdbc/DataSource");
        Connection dbConnection = datasource.getConnection();
        Statement stmt = dbConnection.createStatement();
        ResultSet result = stmt.executeQuery(queryStr);

        // other processing like creation of UserDetails object

        result.close();
        stmt.close();
        dbConnection.close();
        return(details);
    }
}

建议的模式

这个问题的答案就在于将EJB和数据源解耦。这样,您将可以很容易 地改变数据源的类型。本问题建议的模式是数据存取对象(DAO)模式。 这个模式将特定的数据存取逻辑从EJB中移开,将其放到另一个界面中。 于是,EJB实施它们的事务逻辑,在需要读或者修改它们所代表的数据 时,通过数据存取对象读写数据。这样的实现方式不会将EJB和数据源 耦合在一起。改变数据源只需要对数据存取对象进行改变。

下列的例程揭示了解开这种耦合的例子。先建立一个数据存取对象。 例程9揭示了给例程8的UserAccountEJB的DA0。

例程9:封装了所有数据源存取代码的数据存取对象

// All required imports
// Exception handling code not listed below for simplicity

public class UserAccountDAO {

    private transient Connection dbConnection = null;

    public UserAccountDAO() {}

    public UserDetails getUserDetails(String userId) {

        // A simple query for this example
        String query = "SELECT id, name, phone FROM userdetails WHERE name = " + userId;

        InitialContext ic = new InitialContext();
        datasource = (DataSource)ic.lookup("java:comp/env/jdbc/DataSource");
        Connection dbConnection = datasource.getConnection();
        Statement stmt = dbConnection.createStatement();
        ResultSet result = stmt.executeQuery(queryStr);

        // other processing like creation of UserDetails object

        result.close();
        stmt.close();
        dbConnection.close();
        return(details);
    }

    // Other data access / modification methods pertaining to the UserAccountEJB
}

既然已经有了一个数据存取对象,UserAccountEJB 就能使用这个对 象,并且将自己和特定的数据源代码解耦。如下面的代码所示。

例程10:使用DAO的EJB

// all imports required
// exceptions not handled in the sample code

public class UserAccountEJB implements EntityBean {

    // All EJB methods like ejbCreate, ejbRemove go here

    // Business methods start here

    public UserDetails getUserDetails(String userId) {

        // other processing as required
        UserAccountDAO dao = new UserAccountDAO();
        UserDetails details = dao.getUserDetails(userId);
        // other processing as required
        return(details);
    }
}

现在,任何数据源中的改变只需要对DAO进行相应改变即可。而且, 为了让应用程序能够支持不同类型的数据源,您可以为不同类型的数据 源开发不同的数据存取对象,然后将所使用的数据源指定为EJB配发环境 的一部分。在这种情况下,EJB可以通过使用工厂对象取得DAO,工厂对 象从配发环境中轮流取得DAO。按这种方式实现的应用程序能很容易地 从一种数据类型转变到另一种,因为不需要任何代码变动。在 J2EE蓝图程序 中的Java宠物店示范程序中,给出了例子。

注意事项

以下是决定使用本模式时需要注意的事项:

  • 本模式将事务逻辑和数据存取逻辑分开。
  • 对于使用粒媒管理持久性的应用程序,本模式特别有用。在以后, 如果需要移植到容件管理型的持久性,也能使用本模式实现平滑移植。
  • DAO可以在应用程序配发时再选择数据源。.
  • DAO改进了应用程序的灵活性和可扩展性,因为给新的数据源类 型增加支持是非常容易的。
  • DAO并不仅仅限制用在数据库存取上。DAO也可以存取其他类型的 数据源,比如XML数据源。
  • 使用这个模式需要设计额外的类,会让应用程序的复杂度有少许提高。

在这篇文

在这篇文章中,您看到了一些在建造J2EE应用程序时可以有效使用 的设计模式。MVC结构、前端控制器,会话外观与数据存取对象,这些 设计模式通过将不同的层解耦,从而取得应用程序的灵活性和可维护 性。

Java宠物店演示程序与 J2EE蓝图程 序中的模式目录给出了模式的正式定义,此外还有UML图与可工作的例 程。如果您想讨论这些模式:

  1. 加入 J2EE蓝图兴趣者列表
  2. 加入后,您可以使用 j2eeblueprints-interest@java.sun.com邮件别名。

资源

  1. J2EE蓝图 程序中的设计模式部分
  2. F. Buschmann, R. Meunier, H. Rohnert, P. Sommerlad, M. Stal. 基于模式的软件架构:一个模式的系统 John Wiley & Sons Ltd., 1996. John Wiley & Sons Ltd., 1996.
  3. M. Fowler's 的 信息系统结构
  4. E. Gamma, R. Helm, R. Johnson, J. Vlissides. 设计模 式:可重用面向对象软件的要素Addison-Wesley, 1995.
  5. “四人帮”模式模板

关于作者

Vijay Ramachandran是Sun微系统公司J2EE蓝图组的成员, 并且参与了最近的Java宠物店例子应用程序的设计。他目前正致力于 即将到来的J2EE版的Web服务功能的开发。


版权所有:UML软件工程组织