jojo's blog--快乐忧伤都与你同在
为梦想而来,为自由而生。 性情若水,风起水兴,风息水止,故时而激荡,时又清平……
posts - 11,  comments - 30,  trackbacks - 0

15.5. Tapestry
摘自 Tapestry 主页...

“ Tapestry 是用来创建动态、健壮、高伸缩性 Web 应用的一个 Java 开源框架。 Tapestry 组件构建于标准的Java Servlet API 之上,所以它可以工作在任何 Servlet 容器或者应用服务器之上。 ”
尽管 Spring 有自己的 强有力的 Web 层,但是使用 Tapestry 作为 Web 用户界面,并且结合 Spring 容器管理其他层次,在构建 J2EE 应用上具有一些独到的优势。 这一节将尝试介绍集成这两种框架的最佳实践。

一个使用 Tapestry 和 Spring 构建的 典型的 J2EE 应用通常由 Tapestry 构建一系列的用户界面(UI)层,然后通过一个或多个 Spring容器来连接底层设施。 Tapestry 的 参考手册 包含了这些最佳实践的片断。(下面引用中的 [] 部分是本章的作者所加。)

“ Tapestry 中一个非常成功的设计模式是保持简单的页面和组件,尽可能多的将任务 委派(delegate) 给 HiveMind [或者 Spring,以及其他容器] 服务。 Listener 方法应该仅仅关心如何组合成正确的信息并且将它传递给一个服务。 ”
那么关键问题就是...如何将协作的服务提供给 Tapestry 页面?答案是,在理想情况下,应该将这些服务直接 注入到 Tapestry 页面中。在 Tapestry 中,你可以使用几种不同的方法 来实现依赖注入。这一节只讨论Spring 提供的依赖注入的方法。Spring-Tapestry 集成真正具有魅力的地方是 Tapestry 优雅又不失灵活的设计,它使得注入 Spring 托管的 bean 简直就像把马鞍搭在马背上一样简单。(另一个好消息是 Spring-Tapestry 集成代码的编写和维护都是由 Tapestry 的创建者 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 容器中, 并且任何相关的 Tapestry 页面都需要提供(被注入) authenticationService 和 userService 这两个 bean, 它们分别实现了 AuthenticationService 和 UserService 接口。

现在,Web 应用可以通过调用 Spring 静态工具函数 WebApplicationContextUtils.getApplicationContext(servletContext) 来得到application context。这个函数的参数servletContext 是J2EE Servlet 规范所定义的 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 已经提供了一种给页面声明属性的方法, 事实上,以声明的方式管理一个页面上的所有属性是首选的方法,这样 Tapestry 能够将属性的生命周期 作为页面和组件生命周期的一部分加以管理。

注意
下一节应用于 Tapestry 版本 < 4.0 的情况下。如果你正在使用 Tapestry 4.0+,请参考标有 第 15.5.1.4 节 “将 Spring Beans 注入到 Tapestry 页面中 - Tapestry 4.0+ 风格” 的小节。

15.5.1.1. 将 Spring Beans 注入到 Tapestry 页面中
首先我们需要 Tapestry 页面组件在没有 ServletContext 的情况下访问 ApplicationContext;这是因为在页面/组件生命周期里面,当我们需要访问 ApplicationContext 时,ServletContext 并不能被页面很方便的访问到,所以我们不能直接使用 WebApplicationContextUtils.getApplicationContext(servletContext)。 一种解决方法就是实现一个自定义的 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. 组件定义文件
现在,我们在页面或组件定义文件(*.page 或者 *.jwc)中添加 property-specification 元素就可以 从 ApplicationContext 中获取 bean,并为这些 bean 创建页面或 组件属性。例如:

 

    <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. 添加抽象访问方法
现在在页面或组件本身的 Java 类定义中,我们需要为刚刚定义的属性添加抽象的 getter 方法。 (这样才可以访问那些属性)。


下面这个例子总结了前面讲述的方法。这是个完整的 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 页面是 非常 简单的。 你只需要一个 附加函数库, 和一些(少量)的配置。 你可以将这个库和Web 应用其他的库一起部署。(一般情况下是放在 WEB-INF/lib 目录下。)

你需要使用 前面介绍的方法 来创建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 页面; 你可以使用传统风格的 XML 来声明依赖注入;例如,在 Login 页面(或组件)的 .page 或 .jwc 文件中:

<inject property="userService" object="spring:userService"/>
<inject property="authenticationService" object="spring:authenticationService"/>
在这个例子中,我们尝试使用声明的方式将定义在 Spring 容器里的 bean 提供给 Tapestry 页面。 页面类并不知道服务实现来自哪里,事实上,你也可以很容易地转换到另一个实现。这在测试中是很有用的。 这样的反向控制是 Spring 框架的主要目标和优点,我们将它拓展到了Tapestry 应用的整个 J2EE 堆栈上。

posted on 2008-10-04 23:24 Blog of JoJo 阅读(656) 评论(0)  编辑  收藏 所属分类: Programming 相关

只有注册用户登录后才能发表评论。


网站导航:
 

<2024年4月>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

常用链接

留言簿(6)

随笔档案

文章分类

文章档案

新闻分类

新闻档案

相册

收藏夹

搜索

  •  

最新评论

阅读排行榜

评论排行榜