jojo's blog--快樂憂傷都與你同在
          為夢想而來,為自由而生。 性情若水,風起水興,風息水止,故時而激蕩,時又清平……
          posts - 11,  comments - 30,  trackbacks - 0

          15.5. Tapestry
          摘自 Tapestry 主頁...

          “ Tapestry 是用來創(chuàng)建動態(tài)、健壯、高伸縮性 Web 應用的一個 Java 開源框架。 Tapestry 組件構(gòu)建于標準的Java Servlet API 之上,所以它可以工作在任何 Servlet 容器或者應用服務器之上。 ”
          盡管 Spring 有自己的 強有力的 Web 層,但是使用 Tapestry 作為 Web 用戶界面,并且結(jié)合 Spring 容器管理其他層次,在構(gòu)建 J2EE 應用上具有一些獨到的優(yōu)勢。 這一節(jié)將嘗試介紹集成這兩種框架的最佳實踐。

          一個使用 Tapestry 和 Spring 構(gòu)建的 典型的 J2EE 應用通常由 Tapestry 構(gòu)建一系列的用戶界面(UI)層,然后通過一個或多個 Spring容器來連接底層設施。 Tapestry 的 參考手冊 包含了這些最佳實踐的片斷。(下面引用中的 [] 部分是本章的作者所加。)

          “ Tapestry 中一個非常成功的設計模式是保持簡單的頁面和組件,盡可能多的將任務 委派(delegate) 給 HiveMind [或者 Spring,以及其他容器] 服務。 Listener 方法應該僅僅關(guān)心如何組合成正確的信息并且將它傳遞給一個服務。 ”
          那么關(guān)鍵問題就是...如何將協(xié)作的服務提供給 Tapestry 頁面?答案是,在理想情況下,應該將這些服務直接 注入到 Tapestry 頁面中。在 Tapestry 中,你可以使用幾種不同的方法 來實現(xiàn)依賴注入。這一節(jié)只討論Spring 提供的依賴注入的方法。Spring-Tapestry 集成真正具有魅力的地方是 Tapestry 優(yōu)雅又不失靈活的設計,它使得注入 Spring 托管的 bean 簡直就像把馬鞍搭在馬背上一樣簡單。(另一個好消息是 Spring-Tapestry 集成代碼的編寫和維護都是由 Tapestry 的創(chuàng)建者 Howard M. Lewis Ship 一手操辦, 所以我們應該為了這個如絲般順暢的集成方案向他致敬。)

          15.5.1. 注入 Spring 托管的 beans
          假設我們有下面這樣一個 Spring 容器定義(使用 XML 格式):
           

           1<?xml version="1.0" encoding="UTF-8"?>
           2<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" 
           3        "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
           4 
           5<beans>
           6    <!-- the DataSource -->
           7    <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
           8        <property name="jndiName" value="java:DefaultDS"/>
           9    </bean>
          10
          11    <bean id="hibSessionFactory" 
          12          class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
          13        <property name="dataSource" ref="dataSource"/>
          14    </bean>
          15
          16    <bean id="transactionManager" 
          17          class="org.springframework.transaction.jta.JtaTransactionManager"/>
          18
          19    <bean id="mapper" 
          20          class="com.whatever.dataaccess.mapper.hibernate.MapperImpl">
          21        <property name="sessionFactory" ref="hibSessionFactory"/>
          22    </bean>
          23
          24    <!-- (transactional) AuthenticationService -->
          25    <bean id="authenticationService" 
          26          class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
          27        <property name="transactionManager" ref="transactionManager"/>
          28        <property name="target">
          29            <bean class="com.whatever.services.service.user.AuthenticationServiceImpl">
          30                <property name="mapper" ref="mapper"/>
          31            </bean>
          32        </property>
          33        <property name="proxyInterfacesOnly" value="true"/>
          34        <property name="transactionAttributes">
          35            <value>
          36                *=PROPAGATION_REQUIRED
          37            </value>
          38        </property>
          39    </bean>  
          40 
          41    <!-- (transactional) UserService -->
          42    <bean id="userService" 
          43          class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
          44        <property name="transactionManager" ref="transactionManager"/>
          45        <property name="target">
          46             <bean class="com.whatever.services.service.user.UserServiceImpl">
          47                 <property name="mapper" ref="mapper"/>
          48             </bean>
          49        </property>
          50        <property name="proxyInterfacesOnly" value="true"/>
          51        <property name="transactionAttributes">
          52            <value>
          53                *=PROPAGATION_REQUIRED
          54            </value>
          55        </property>
          56    </bean>  
          57 
          58 </beans>
          59
          60


          在 Tapestry 應用中,上面的 bean 定義需要 加載到 Spring 容器中, 并且任何相關(guān)的 Tapestry 頁面都需要提供(被注入) authenticationService 和 userService 這兩個 bean, 它們分別實現(xiàn)了 AuthenticationService 和 UserService 接口。

          現(xiàn)在,Web 應用可以通過調(diào)用 Spring 靜態(tài)工具函數(shù) WebApplicationContextUtils.getApplicationContext(servletContext) 來得到application context。這個函數(shù)的參數(shù)servletContext 是J2EE Servlet 規(guī)范所定義的 ServletContext。 這樣一來,頁面可以很容易地得到 UserService 的實例, 就像下面的這個例子:

          WebApplicationContext appContext = WebApplicationContextUtils.getApplicationContext(
              getRequestCycle().getRequestContext().getServlet().getServletContext());
          UserService userService = (UserService) appContext.getBean("userService");
          ... some code which uses UserService
          這種機制可以工作...如果想進一步改進的話,我們可以將大部分的邏輯封裝在頁面或組件基類的一個方法中。 然而,這個機制在某些方面違背了 Spring 所倡導的反向控制方法(Inversion of Control)。在理想情況下,頁面 不必在context中尋找某個名字的 bean。事實上,頁面最好是對context一無所知。

          幸運的是,有一種機制可以做到這一點。這是因為 Tapestry 已經(jīng)提供了一種給頁面聲明屬性的方法, 事實上,以聲明的方式管理一個頁面上的所有屬性是首選的方法,這樣 Tapestry 能夠?qū)傩缘纳芷?作為頁面和組件生命周期的一部分加以管理。

          注意
          下一節(jié)應用于 Tapestry 版本 < 4.0 的情況下。如果你正在使用 Tapestry 4.0+,請參考標有 第 15.5.1.4 節(jié) “將 Spring Beans 注入到 Tapestry 頁面中 - Tapestry 4.0+ 風格” 的小節(jié)。

          15.5.1.1. 將 Spring Beans 注入到 Tapestry 頁面中
          首先我們需要 Tapestry 頁面組件在沒有 ServletContext 的情況下訪問 ApplicationContext;這是因為在頁面/組件生命周期里面,當我們需要訪問 ApplicationContext 時,ServletContext 并不能被頁面很方便的訪問到,所以我們不能直接使用 WebApplicationContextUtils.getApplicationContext(servletContext)。 一種解決方法就是實現(xiàn)一個自定義的 Tapestry IEngine 來提供 ApplicationContext:

           

           1 package com.whatever.web.xportal;
           2 
           3 import 
           4 
           5 public class MyEngine extends org.apache.tapestry.engine.BaseEngine {
           6  
           7     public static final String APPLICATION_CONTEXT_KEY = "appContext";
           8  
           9     /**
          10      * @see org.apache.tapestry.engine.AbstractEngine#setupForRequest(org.apache.tapestry.request.RequestContext)
          11      */
          12     protected void setupForRequest(RequestContext context) {
          13         super.setupForRequest(context);
          14      
          15         // insert ApplicationContext in global, if not there
          16         Map global = (Map) getGlobal();
          17         ApplicationContext ac = (ApplicationContext) global.get(APPLICATION_CONTEXT_KEY);
          18         if (ac == null) {
          19             ac = WebApplicationContextUtils.getWebApplicationContext(
          20                 context.getServlet().getServletContext()
          21             );
          22             global.put(APPLICATION_CONTEXT_KEY, ac);
          23         }
          24     }
          25 }
          26 
          27 

          這個引擎類將 Spring application context作為一個名為 “appContext” 的屬性存放在 Tapestry 應用的 “Global” 對象中。在 Tapestry 應用定義文件中必須保證這個特殊的 IEngine 實例在這個 Tapestry 應用中被使用。 舉個例子:

           

           1 file: xportal.application: 
           2 <?xml version="1.0" encoding="UTF-8"?>
           3 <!DOCTYPE application PUBLIC 
           4     "-//Apache Software Foundation//Tapestry Specification 3.0//EN" 
           5     "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
           6 <application
           7     name="Whatever xPortal"
           8     engine-class="com.whatever.web.xportal.MyEngine">
           9 </application>
          10 

          15.5.1.2. 組件定義文件
          現(xiàn)在,我們在頁面或組件定義文件(*.page 或者 *.jwc)中添加 property-specification 元素就可以 從 ApplicationContext 中獲取 bean,并為這些 bean 創(chuàng)建頁面或 組件屬性。例如:

           

              <property-specification name="userService"
                                      type
          ="com.whatever.services.service.user.UserService">
                  global.appContext.getBean(
          "userService")
              
          </property-specification>
              
          <property-specification name="authenticationService"
                                      type
          ="com.whatever.services.service.user.AuthenticationService">
                  global.appContext.getBean(
          "authenticationService")
              
          </property-specification>


          在 property-specification 中定義的 OGNL 表達式使用context中的 bean 來指定屬性的初始值。 整個頁面定義文件如下:

           

           1 <?xml version="1.0" encoding="UTF-8"?>
           2 <!DOCTYPE page-specification PUBLIC 
           3     "-//Apache Software Foundation//Tapestry Specification 3.0//EN" 
           4     "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
           5      
           6 <page-specification class="com.whatever.web.xportal.pages.Login">
           7  
           8     <property-specification name="username" type="java.lang.String"/>
           9     <property-specification name="password" type="java.lang.String"/>
          10     <property-specification name="error" type="java.lang.String"/>
          11     <property-specification name="callback" type="org.apache.tapestry.callback.ICallback" persistent="yes"/>
          12     <property-specification name="userService"
          13                             type="com.whatever.services.service.user.UserService">
          14         global.appContext.getBean("userService")
          15     </property-specification>
          16     <property-specification name="authenticationService"
          17                             type="com.whatever.services.service.user.AuthenticationService">
          18         global.appContext.getBean("authenticationService")
          19     </property-specification>
          20    
          21     <bean name="delegate" class="com.whatever.web.xportal.PortalValidationDelegate"/>
          22  
          23     <bean name="validator" class="org.apache.tapestry.valid.StringValidator" lifecycle="page">
          24         <set-property name="required" expression="true"/>
          25         <set-property name="clientScriptingEnabled" expression="true"/>
          26     </bean>
          27  
          28     <component id="inputUsername" type="ValidField">
          29         <static-binding name="displayName" value="Username"/>
          30         <binding name="value" expression="username"/>
          31         <binding name="validator" expression="beans.validator"/>
          32     </component>
          33    
          34     <component id="inputPassword" type="ValidField">
          35         <binding name="value" expression="password"/>
          36        <binding name="validator" expression="beans.validator"/>
          37        <static-binding name="displayName" value="Password"/>
          38        <binding name="hidden" expression="true"/>
          39     </component>
          40  
          41 </page-specification>
          42 

          15.5.1.3. 添加抽象訪問方法
          現(xiàn)在在頁面或組件本身的 Java 類定義中,我們需要為剛剛定義的屬性添加抽象的 getter 方法。 (這樣才可以訪問那些屬性)。


          下面這個例子總結(jié)了前面講述的方法。這是個完整的 Java 類:

           

            1 
            2 package com.whatever.web.xportal.pages;
            3  
            4 /**
            5  *  Allows the user to login, by providing username and password.
            6  *  After successfully logging in, a cookie is placed on the client browser
            7  *  that provides the default username for future logins (the cookie
            8  *  persists for a week).
            9  */
           10 public abstract class Login extends BasePage implements ErrorProperty, PageRenderListener {
           11  
           12     /** the key under which the authenticated user object is stored in the visit as */
           13     public static final String USER_KEY = "user";
           14    
           15     /** The name of the cookie that identifies a user **/
           16     private static final String COOKIE_NAME = Login.class.getName() + ".username";  
           17     private final static int ONE_WEEK = 7 * 24 * 60 * 60;
           18  
           19     public abstract String getUsername();
           20     public abstract void setUsername(String username);
           21  
           22     public abstract String getPassword();
           23     public abstract void setPassword(String password);
           24  
           25     public abstract ICallback getCallback();
           26     public abstract void setCallback(ICallback value);
           27     
           28     public abstract UserService getUserService();
           29     public abstract AuthenticationService getAuthenticationService();
           30  
           31     protected IValidationDelegate getValidationDelegate() {
           32         return (IValidationDelegate) getBeans().getBean("delegate");
           33     }
           34  
           35     protected void setErrorField(String componentId, String message) {
           36         IFormComponent field = (IFormComponent) getComponent(componentId);
           37         IValidationDelegate delegate = getValidationDelegate();
           38         delegate.setFormComponent(field);
           39         delegate.record(new ValidatorException(message));
           40     }
           41  
           42     /**
           43      *  Attempts to login. 
           44      * <p>
           45      *  If the user name is not known, or the password is invalid, then an error
           46      *  message is displayed.
           47      **/
           48     public void attemptLogin(IRequestCycle cycle) {
           49      
           50         String password = getPassword();
           51  
           52         // Do a little extra work to clear out the password.
           53         setPassword(null);
           54         IValidationDelegate delegate = getValidationDelegate();
           55  
           56         delegate.setFormComponent((IFormComponent) getComponent("inputPassword"));
           57         delegate.recordFieldInputValue(null);
           58  
           59         // An error, from a validation field, may already have occurred.
           60         if (delegate.getHasErrors()) {
           61             return;
           62         }
           63 
           64         try {
           65             User user = getAuthenticationService().login(getUsername(), getPassword());
           66            loginUser(user, cycle);
           67         }
           68         catch (FailedLoginException ex) {
           69             this.setError("Login failed: " + ex.getMessage());
           70             return;
           71         }
           72     }
           73  
           74     /**
           75      *  Sets up the {@link User} as the logged in user, creates
           76      *  a cookie for their username (for subsequent logins),
           77      *  and redirects to the appropriate page, or
           78      *  a specified page).
           79      **/
           80     public void loginUser(User user, IRequestCycle cycle) {
           81      
           82         String username = user.getUsername();
           83  
           84         // Get the visit object; this will likely force the
           85         // creation of the visit object and an HttpSession
           86         Map visit = (Map) getVisit();
           87         visit.put(USER_KEY, user);
           88  
           89         // After logging in, go to the MyLibrary page, unless otherwise specified
           90         ICallback callback = getCallback();
           91  
           92         if (callback == null) {
           93             cycle.activate("Home");
           94         }
           95         else {
           96             callback.performCallback(cycle);
           97         }
           98 
           99         IEngine engine = getEngine();
          100         Cookie cookie = new Cookie(COOKIE_NAME, username);
          101         cookie.setPath(engine.getServletPath());
          102         cookie.setMaxAge(ONE_WEEK);
          103  
          104         // Record the user's username in a cookie
          105         cycle.getRequestContext().addCookie(cookie);
          106         engine.forgetPage(getPageName());
          107     }
          108    
          109     public void pageBeginRender(PageEvent event) {
          110         if (getUsername() == null) {
          111             setUsername(getRequestCycle().getRequestContext().getCookieValue(COOKIE_NAME));
          112         }
          113     }
          114 }

          15.5.1.4. 將 Spring Beans 注入到 Tapestry 頁面中 - Tapestry 4.0+ 風格
          在 Tapestry 4.0+ 版本中,將 Spring 托管 beans 注入到 Tapestry 頁面是 非常 簡單的。 你只需要一個 附加函數(shù)庫, 和一些(少量)的配置。 你可以將這個庫和Web 應用其他的庫一起部署。(一般情況下是放在 WEB-INF/lib 目錄下。)

          你需要使用 前面介紹的方法 來創(chuàng)建Spring 容器。 然后你就可以將 Spring 托管的 beans 非常簡單的注入給 Tapestry;如果我們使用 Java5, 我們只需要簡單地給 getter 方法添加注釋(annotation),就可以將 Spring 管理的 userService 和 authenticationService 對象注入給頁面。 比如下面 Login 的例子:(為了保持簡潔,許多的類定義在這里省略了)

           

           1 package com.whatever.web.xportal.pages;
           2 
           3 public abstract class Login extends BasePage implements ErrorProperty, PageRenderListener {
           4     
           5     @InjectObject("spring:userService")
           6     public abstract UserService getUserService();
           7     
           8     @InjectObject("spring:authenticationService")
           9     public abstract AuthenticationService getAuthenticationService();
          10 
          11 }
          12 


          我們的任務基本上完成了...剩下的工作就是配置HiveMind,將存儲在 ServletContext 中 的 Spring 容器配置為一個 HiveMind 服務:

           

           1<?xml version="1.0"?>
           2<module id="com.javaforge.tapestry.spring" version="0.1.1">
           3
           4    <service-point id="SpringApplicationInitializer"
           5        interface="org.apache.tapestry.services.ApplicationInitializer"
           6        visibility="private">
           7        <invoke-factory>
           8            <construct class="com.javaforge.tapestry.spring.SpringApplicationInitializer">
           9                <set-object property="beanFactoryHolder"
          10                    value="service:hivemind.lib.DefaultSpringBeanFactoryHolder" />
          11            </construct>
          12        </invoke-factory>
          13    </service-point>
          14
          15    <!-- Hook the Spring setup into the overall application initialization. -->
          16    <contribution
          17        configuration-id="tapestry.init.ApplicationInitializers">
          18        <command id="spring-context"
          19            object="service:SpringApplicationInitializer" />
          20    </contribution>
          21
          22</module>
          23
          24

          如果你使用 Java5(這樣就可以使用annotation),那么就是這么簡單。

          如果你不用 Java5,你沒法通過annotation來注釋你的 Tapestry 頁面; 你可以使用傳統(tǒng)風格的 XML 來聲明依賴注入;例如,在 Login 頁面(或組件)的 .page 或 .jwc 文件中:

          <inject property="userService" object="spring:userService"/>
          <inject property="authenticationService" object="spring:authenticationService"/>
          在這個例子中,我們嘗試使用聲明的方式將定義在 Spring 容器里的 bean 提供給 Tapestry 頁面。 頁面類并不知道服務實現(xiàn)來自哪里,事實上,你也可以很容易地轉(zhuǎn)換到另一個實現(xiàn)。這在測試中是很有用的。 這樣的反向控制是 Spring 框架的主要目標和優(yōu)點,我們將它拓展到了Tapestry 應用的整個 J2EE 堆棧上。

          posted on 2008-10-04 23:24 Blog of JoJo 閱讀(684) 評論(0)  編輯  收藏 所屬分類: Programming 相關(guān)

          <2025年6月>
          25262728293031
          1234567
          891011121314
          15161718192021
          22232425262728
          293012345

          常用鏈接

          留言簿(6)

          隨筆檔案

          文章分類

          文章檔案

          新聞分類

          新聞檔案

          相冊

          收藏夾

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 台前县| 盐山县| 苍梧县| 福清市| 云梦县| 宁远县| 达拉特旗| 苏尼特左旗| 岳阳县| 勃利县| 南川市| 海口市| 噶尔县| 文化| 舒兰市| 绍兴县| 灵山县| 莱西市| 襄城县| 巨野县| 怀化市| 高青县| 沧州市| 康马县| 湖北省| 鄂托克旗| 吉木乃县| 万宁市| 蒙城县| 裕民县| 长汀县| 酒泉市| 新泰市| 澄城县| 南城县| 隆昌县| 那曲县| 静乐县| 长兴县| 怀化市| 佛坪县|