(接上期) 
Web tier 当使用者输入http://localhost:8080/petstore/customer.do,MainServlet接收到 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,ShoppingClientFacade是Petstore在EJB tier的代理接口,Web tier所 有Request均需透过它来存取所需资料,这个Design Pattern的好处是可以降低EJB tier与Web 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写入使用者已登入标记,此时会触发SignOnNotifier。8.SignOnNotifier透过PetstoreComponentManager, PetstoreComponentManager是Petstore在Web tier的接口。SignOnNotifier经过 后面9至13步骤取得CustomerEJB对应CustomerLocal,并存入Session。 9.PetstoreComponentManager透过ShoppingControllerEJB,ShoppingControllerEJB是Petstore在EJB tier的接口。 10.ShoppingControllerEJB透过ShoppingClientFacadeEJB,ShoppingClientFacadeEJB是Petstore在EJB tier关于前端购物所有功能统一提供接口。 11.ShoppingClientFacadeEJB取得CustomerEJB Entity Bean,CustomerEJB代表 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负责处理*.do的Request。 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必能事半功倍,且能补上struts在EJB tier缺乏的framework。若读者有任何问题或意见欢迎与笔者讨论,E-Mail:[email protected]。
  注1:Petstore_home代表您的Petstore安装目录。 注2:deploytool的开启方式及将pestore.ear加载deploytool请参阅本系列第一篇文章。 注3:DAO产生过程与本系列第二篇CatalogDAO产生方式相似,笔者不再赘述。 注4:读者若想观察cloudscape数据库中相关资料表,笔者在此提供一个方法,可利用JBuilder之Database Pilot来观察: a.请开启JBuilder,点选menu bar之”Tools” > “Enterprise Setup”,请选择” Database Drivers”页,按”add”钮,请新增一个library将cloudscape相关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,点选两下会要求您输入username及password, 直接略过按”ok”,会发现所有资料表都出现啦!
  d.Database Pilot详细操作请参考JBuiler相关文件。
  与作者联络: [email protected]  
 
  |