Petstore源码追踪记(3)-商业逻辑处理(五)

 (接上期)

Web tier
当使用者输入http://localhost:8080/petstore/customer.doMainServlet接收到

Request
,转到doProcess()函数:

private void doProcess(HttpServletRequest request, HttpServletResponse response)
                   throws IOException, ServletException {
    // set the locale of the user to default if not set
    if (request.getSession().getAttribute(WebKeys.LOCALE) == null) {
       request.getSession().setAttribute(WebKeys.LOCALE, defaultLocale);
    }
    try {
       getRequestProcessor().processRequest(request);
//
进行转导动作(forward)              
getScreenFlowManager().forwardToNextScreen(request,response);
    } catch (Throwable ex) {
       String className = ex.getClass().getName();
       String nextScreen = getScreenFlowManager().getExceptionScreen(ex);
       // put the exception in the request
       request.setAttribute("javax.servlet.jsp.jspException", ex);
       if (nextScreen == null) {
          // send to general error screen
          ex.printStackTrace();
          throw new ServletException("MainServlet: unknown exception: " +
className);
       }
          context.getRequestDispatcher(nextScreen).forward(request, response);
    }

}

请开启

Petstore_home/src/waf/src/controller/com/sun/j2ee/blueprints/waf/controller/web/flow/ScreenFlowManager.java
,在约112列可找到forwardToNextScreen()函数:

public void forwardToNextScreen(HttpServletRequest request, HttpServletResponse
response) throws java.io.IOException, FlowHandlerException,
javax.servlet.ServletException {
      // set the presious screen
      String fullURL = request.getRequestURI();
      // get the screen name
      String selectedURL = defaultScreen;
      int lastPathSeparator = fullURL.lastIndexOf("/") + 1;
      if (lastPathSeparator != -1) {
          selectedURL = fullURL.substring(lastPathSeparator, fullURL.length());
      }
      //
请加入侦察码,以本例来说,selectedURL=customer.do
      System.out.println("selectURL="+ selectedURL);
      String currentScreen = "";
      URLMapping urlMapping = getURLMapping(selectedURL);
      if (urlMapping != null) {
            if (!urlMapping.useFlowHandler()) {
                currentScreen = urlMapping.getScreen();
        //请加入侦察码,以本例来说,
currentScreen =customer.screen
                System.out.println("currentScreen="+currentScreen);
            } else {
                // load the flow handler
                FlowHandler handler = null;
                String flowHandlerString = urlMapping.getFlowHandler();
                try {
                    handler = (FlowHandler)getClass().getClassLoader()
                       .loadClass(flowHandlerString).newInstance();
                    // invoke the processFlow(HttpServletRequest)
                    handler.doStart(request);
                    String flowResult = handler.processFlow(request);
                    handler.doEnd(request);
                    currentScreen = urlMapping.getResultScreen(flowResult);
                    // if there were no screens by the id then assume that the
result was
                    //the screen itself
                    if (currentScreen == null) currentScreen = flowResult;
               } catch (Exception ex) {
                   System.err.println("ScreenFlowManager caught loading
handler: " + ex);
               }
            }
        }
        if (currentScreen == null) {
            System.err.println("ScreenFlowManager: Screen not found for " +
selectedURL);
            throw new RuntimeException("Screen not found for " + selectedURL);
        }
        //
进行转导动作
(forward)
        System.out.println("forward to "+ currentScreen);
        context.getRequestDispatcher("/" + currentScreen).forward(request,
response);

}

我们知道它会转导至customer.screen,从screendefinitions_en_US.xml,可找到对应各画面区块:


<screen name="customer">
  <parameter key="title" value="Customer" direct="true"/>
  <parameter key="banner" value="/banner.jsp" />
  <parameter key="sidebar" value="/sidebar.jsp" />
  <parameter key="body" value="/customer.jsp" />
  <parameter key="mylist" value="/mylist.jsp" />
  <parameter key="footer" value="/footer.jsp" />
</screen>

其实大部份与main.screen都相同,唯一差异部份就是主体区块(body),开启
customer.jsp
瞧它的内容,位置在Petstore_home/src/apps/petstore/src/docroot
,在约50列:


<table cellpadding="5" cellspacing="0" width="100%" border="0">
<tr>
<td colspan="3"><p class="petstore_title">Contact Information</p></td>
</tr>
<tr>
<td class="petstore_form" align="right"><b>First Name</b></td>

<td class="petstore_form" align="left"
colspan="2"><c:out value="${customer.account.contactInfo.givenName}"/></td>
</tr>
以下略...

以上程序代码作用要将使用者名称(First Name)显示出现,它利用JSTL Tags

资料打印出来,可是问题来了,资料是如何得来的?答案就是本文前头所提到的四个隐形角色之一-SignOnNotifier Attribute Listener

SignOnNotifier
请读者回想前头所提,当使用者身分验证成功后,SignOnFilter会将SIGNED_ON_USER对应变量设定真值(true),存入Session

hreq.getSession().setAttribute(SIGNED_ON_USER, new Boolean(true));

此时便会触发SignOnNotifier,请开启:
Petstore_home/src/apps/petstore/src/com/sun/j2ee/blueprints/petstore/controller/web/SignOnNotifier.java
,在约89列:

/**
 * Process an attribute added
 *属性新增后

 */
public void attributeAdded(HttpSessionBindingEvent se) {
   processEvent(se);
}

/**
 * Process the update
 *
属性覆盖后

 */
public void attributeReplaced(HttpSessionBindingEvent se) {
   processEvent(se);
}

private void processEvent(HttpSessionBindingEvent se) {
   HttpSession session = se.getSession();
   String name = se.getName();

   /* check if the value matches the signon attribute
   * if a macth fire off an event to the ejb tier that the user
   * has signed on and load the account for the user
   */
   //
判别新增或覆盖属性为”SIGNED_ON_USER”则进行处理,否则略过
   if (name.equals(SignOnFilter.SIGNED_ON_USER)) {
      boolean aSignOn  = ((Boolean)se.getValue()).booleanValue();
      if (aSignOn) {
          String userName =
(String)session.getAttribute(SignOnFilter.USER_NAME);
//
请加入侦察码
System.out.println("SignOnNotifier() userName="+userName);
          // look up the model manager and webclient controller
          PetstoreComponentManager sl =
(PetstoreComponentManager)session.getAttribute(PetstoreKeys.COMPONENT_MANAGER);
          WebController wc =  sl.getWebController(session);
          SignOnEvent soe = new SignOnEvent(userName);
          // set the EJBAction on the Event
          EventMapping em = getEventMapping(session.getServletContext(), soe);
          if (em != null) {
             soe.setEJBActionClassName(em.getEJBActionClassName());
System.out.println("EJBActionClassName="+
   em.getEJBActionClassName());
          }

          try {
             //
更新资料时会用到,以本例来说只需读取,所以没有作用
             wc.handleEvent(soe, session);
          } catch (EventException e) {
             System.err.println("SignOnNotifier Error handling event " + e);
          }
          //
取得Customer EJB Local Interface Reference
          CustomerLocal customer =  sl.getCustomer(session);
          // ensure the customer object is put in the session
          //
Customer EJB Local Interface存入
Session
          if (session.getAttribute(PetstoreKeys.CUSTOMER) == null) {
             session.setAttribute(PetstoreKeys.CUSTOMER, customer);
          }
          // set the language to the preferred language and other preferences
          ProfileLocal profile = sl.getCustomer(session).getProfile();
          Locale locale =
I18nUtil.getLocaleFromString(profile.getPreferredLanguage());
          session.setAttribute(PetstoreKeys.LOCALE, locale);
      }
   }
}
以下略
...

     SignOnNotifier
会透过PetstoreComponentManager取得
ShoppingClientFacade
reference
ShoppingClientFacadePetstoreEJB tier的代理接口,Web tier

Request均需透过它来存取所需资料,这个Design Pattern的好处是可以降低EJB tierWeb tier间的藕合性,将商业逻辑集中控管在EJB tier

PetstoreComponentManager
,源码在
D:/petstore1.3.1/src/apps/petstore/src/com/sun/j2ee/blueprints/petstore/controller/web/
PetstoreComponentManager.java
,约110列:

public CustomerLocal  getCustomer(HttpSession session) {
   ShoppingControllerLocal scEjb = getShoppingController(session);
   try {
   //取得ShoppingClientFacade reference
      ShoppingClientFacadeLocal scf = scEjb.getShoppingClientFacade();
      //scf.setUserId(userId);
      //
取得
CustomerLocal reference
      return scf.getCustomer();
   } catch (FinderException e) {
      System.err.println("PetstoreComponentManager finder error: " + e);
   } catch (Exception e) {
      System.err.println("PetstoreComponentManager error: " + e);
   }
  
return null;
}

ShoppingClientFacadeLocalEJB
Session Bean,源码在

D:/petstore1.3.1/src/apps/petstore/src/com/sun/j2ee/blueprints/petstore/controller/ejb/
ShoppingClientFacadeLocalEJB.java
,约101列:

/*
 * Asume that the customer userId has been set
 */
public CustomerLocal getCustomer() throws FinderException {
   //
请加入侦察码
   System.out.println("ShoppingClientFacadeLocalEJB.getCustomer()");
   if (userId == null) {
      throw new GeneralFailureException("ShoppingClientFacade: failed to look
up name of customer: userId is not set" );
   }
   try {
       ServiceLocator sl = new ServiceLocator();
       CustomerLocalHome home =(CustomerLocalHome)
           sl.getLocalHome(JNDINames.CUSTOMER_EJBHOME);
       customer = home.findByPrimaryKey(userId);
    } catch (ServiceLocatorException slx) {
       throw new GeneralFailureException("ShoppingClientFacade: failed to look
up name of customer: caught " + slx);
    }
    return customer;
}

CustomerEJB
Entity Bean,它与AccountEJB有一对一关系,AccountEJB也与ContactInfoEJB有一对一关系,三者皆为Entity Bean,直接对应资料库资料表CustomerEJBTable, AccountEJBTable, ContactInfoEJBTable,源码在D:/petstore1.3.1/src/components/customer/src/com/sun/j2ee/blueprints/customer/
目录下,因Entity Bean无特别之处,故不再说明。



24 Entity Beans间的关系(Relation)

所以<c:out value="${customer.account.contactInfo.givenName}"/>即是从

Session
取出CustomerLocal,再透过CMR字段(即前面所提关系)取得最底层givenName字段显示出来。

将程序重新编译及部署,可得如下预期结果:


25 SignOnNotifier取得CustomerLocal

结语


笔者将整个使用者基本数据浏览流程做一总整理,绘制成合作图,帮助读者更容易了解:

25 使用者基本资料浏览合作图

1.
使用者进入首页。
2.
点选右上角Account连结(customer.do),欲进入使用者基本资料浏览画面,
Request
中途被SignOnNotifier拦截。
3.SignOnFilter
读取signon-config.xml设定。
4. SignOnFilter
依据signon-config.xml设定,发现customer.do是受保护的资源,
且使用者尚未进行登入动作,所以转导至signon.screen画面。
5.
使用者输入帐号及密码,按”Sumit”后,Request再度被SignOnFilter拦截,透过SignOnEJB Session Bean进行验证。
6.SignOnEJB Session Bean
透过UserEJB比对密码是否正确。
7.
密码验证无误后,会在Session写入使用者已登入标记,此时会触发SignOnNotifier8.SignOnNotifier透过PetstoreComponentManager
PetstoreComponentManager
PetstoreWeb tier的接口。SignOnNotifier经过
后面913步骤取得CustomerEJB对应CustomerLocal,并存入Session
9.PetstoreComponentManager
透过ShoppingControllerEJBShoppingControllerEJBPetstoreEJB tier的接口。
10.ShoppingControllerEJB
透过ShoppingClientFacadeEJBShoppingClientFacadeEJBPetstoreEJB tier关于前端购物所有功能统一提供接口。
11.ShoppingClientFacadeEJB
取得CustomerEJB Entity BeanCustomerEJB代表
CustomerEJBTable
资料表。
12.CustomerEJB Entity Bean
AccountEJB Entity Bean有一对一关系,AccountEJB代表AccountEJBTable资料表。
13. AccountEJB Entity Bean
ContactInfoEJB Entity Bean有一对一关系,
ContactInfoEJB
代表ContactInfoEJBTable资料表。
14.SignOnFilter
在第7步骤验证无误后,则将Request(customer.do)放行。
15.Request(customer.do)
MainServlet接收,MainServlet负责处理*.doRequest
16.MainServlet
读取mappings.xml相关设定,找出customer.do所对应的web action class(web tier欲执行工作,以本例来说,只有浏览,所以没有对应的工作)screen(结果呈现画面)
17.MainServlet
screen交给ScreenFlowManager,它负责转导工作。
18. screen
对应值为customer.screen,所以转导至customer.screen

    
写到这里笔者已经快昏了!第一次写这么长的文章,不过还是要强打精神
做个Endding,由这三期探讨Petstore架构,一个完整的J2EE framework已然成形,在J2EE所提到的大部份技术几乎都用上了,也见到了各种技术该如何整合
运用,这就是所谓的Design Pattern,也许读者会想这样的架构能不能直接套用
在我们实际要开发的项目上?当然是可以,不过在Web tier的架构已有更完整
、更套装化的framework出现,就是struts,网址在http://jakarta.apache.org/struts/index.html
,它是Apache Jakarta下的一个子项目(Jakarta最有名的子项目就是Tomcat),一个免费且会持续升级的framework,目前是当红炸子鸡,它整个架构与Petstore类似,只是它提的solution只在Web tier,笔者建议可运用struts来开发我们的项目,当我们已了解Petstore的架构,学习struts必能事半功倍,且能补上strutsEJB tier缺乏的framework。若读者有任何问题或意见欢迎与笔者讨论,E-Mail:senshaw@ms4.hinet.net

1Petstore_home代表您的Petstore安装目录。
2deploytool的开启方式及将pestore.ear加载deploytool请参阅本系列第一篇文章。
3DAO产生过程与本系列第二篇CatalogDAO产生方式相似,笔者不再赘述。
4:读者若想观察cloudscape数据库中相关资料表,笔者在此提供一个方法,可利用JBuilderDatabase Pilot来观察:
a.
请开启JBuilder,点选menu bar”Tools” > “Enterprise Setup”,请选择
Database Drivers”
页,按”add”钮,请新增一个librarycloudscape相关jar文件,位置在j2ee安装目录/lib/cloudscape下三个档案选进来后按”ok”钮,此时JBuilder会要求您重新激活JBuiler,将JBuilder关闭。

b.
开启Database Pilot,点选menu bar”File” > “New”,会出现一窗口要您输入
Driver
URL,请依下列值输入:
Driver:COM.cloudscape.core.RmiJdbcDriver
URL:jdbc:cloudscape:rmi:PetStoreDB;create=true
c.
点选左半边增加出来的URL,点选两下会要求您输入usernamepassword
直接略过按”ok”,会发现所有资料表都出现啦!

d.Database Pilot
详细操作请参考JBuiler相关文件。

与作者联络: senshaw@ms4.hinet.net

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jiangtao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值