商讯信箱
用户名: @
密  码:   注册|忘记密码
登录
个人用户经销商
您的位置:首页 > 技术频道 > 正文

一个由Tapestry和Spring构建的典型分层的J2EE应用包括一个上层的Tapestry表现层和许多底部层次构成,它们存在于一个或多个Spring应用上下文中。

用户界面层:

- 主要关注用户界面的内容

- 包含某些应用逻辑

- 由Tapestry提供

- 除了通过Tapestry提供的用户界面,这一层的代码访问实现业务层接口的对象。实现对象由Spring应用上下文提供。

业务层:

- 应用相关的“业务”代码

- 访问域对象,并且使用Mapper API从某种数据存储(数据库)中存取域对象

- 存在在一个或多个Spring上下文中

- 这层的代码以一种应用相关的方式操作域模型中的对象。它通过这层中的其它代码和Mapper API工作。这层中的对象由某个特定的mapper实现通过应用上下文提供。

- 既然这层中的代码存在在Spring上下文中,它由Spring上下文提供事务处理,而不自己管理事务。

域模型:

- 问题域相关的对象层次,这些对象处理和问题域相关的数据和逻辑

- 虽然域对象层次被创建时考虑到它会被某种方式持久化,并且为此定义一些通用的约束(例如,双向关联),它通常并不知道其它层次的情况。因此,它可以被独立地测试,并且在产品和测试这两种不同的mapping实现中使用。

- 这些对象可以是独立的,也可以关联Spring应用上下文以发挥它的优势,例如隔离,反向控制,不同的策略实现,等等。

数据源层:

- Mapper API(也称为Data Access Objects):是一种将域模型持久化到某种数据存储(一般是数据库,但是也可以是文件系统,内存,等等)的API。

- Mapper API实现:是指Mapper API的一个或多个特定实现,例如,Hibernate的mapper,JDO的mapper,JDBC的mapper,或者内存mapper。

- mapper实现存在在一个或多个Spring应用上下文中。一个业务层对象需要应用上下文的mapper对象才能工作。

数据库,文件系统,或其它形式的数据存储:

- 在域模型中的对象根据一个或多个mapper实现可以存放在不止一个数据存储中

- 数据存储的方式可以是简单的(例如,文件系统),或者有它域模型自己的数据表达(例如,一个数据库中的schema)。但是它不知道其它层次的情况。

实现
真正的问题(本节所需要回答的),是Tapestry页面是如何访问业务实现的,业务实现仅仅是定义在Spring应用上下文实例中的bean。

应用上下文示例
假设我们以xml格式定义的下面的应用上下文:

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <!-- ========================= GENERAL DEFINITIONS ========================= --> <!-- ========================= PERSISTENCE DEFINITIONS ========================= --> <!-- the DataSource --> <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName"><value>java:DefaultDS</value></property> <property name="resourceRef"><value>false</value></property> </bean> <!-- define a Hibernate Session factory via a Spring LocalSessionFactoryBean --> <bean id="hibSessionFactory" class="org.springframework.orm.hibernate.LocalSessionFactoryBean"> <property name="dataSource"><ref bean="dataSource"/></property> </bean> <!-- - Defines a transaction manager for usage in business or data access objects. - No special treatment by the context, just a bean instance available as reference - for business objects that want to handle transactions, e.g. via TransactionTemplate. --> <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"> </bean> <bean id="mapper" class="com.whatever.dataaccess.mapper.hibernate.MapperImpl"> <property name="sessionFactory"><ref bean="hibSessionFactory"/></property> </bean> <!-- ========================= BUSINESS DEFINITIONS ========================= --> <!-- AuthenticationService, including tx interceptor --> <bean id="authenticationServiceTarget" class="com.whatever.services.service.user.AuthenticationServiceImpl"> <property name="mapper"><ref bean="mapper"/></property> </bean> <bean id="authenticationService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="transactionManager"><ref bean="transactionManager"/></property> <property name="target"><ref bean="authenticationServiceTarget"/></property> <property name="proxyInterfacesOnly"><value>true</value></property> <property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean> <!-- UserService, including tx interceptor --> <bean id="userServiceTarget" class="com.whatever.services.service.user.UserServiceImpl"> <property name="mapper"><ref bean="mapper"/></property> </bean> <bean id="userService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="transactionManager"><ref bean="transactionManager"/></property> <property name="target"><ref bean="userServiceTarget"/></property> <property name="proxyInterfacesOnly"><value>true</value></property> <property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean> </beans>

在Tapestry应用中,我们需要载入这个应用上下文,并允许Tapestry页面访问authenticationService和userService这两个bean,它们分别实现了AuthenticationService接口和UserService接口。

在Tapestry页面中获取bean
在这点上,web应用可以调用Spring的静态工具方法WebApplicationContextUtils.getApplicationContext(servletContext)来获取应用上下文,参数servletContext是J2EE Servlet规范定义的标准ServletContext。因此,页面获取例如UserService实例的一个简单方法就象下面的代码:

WebApplicationContext appContext = WebApplicationContextUtils.getApplicationContext( getRequestCycle().getRequestContext().getServlet().getServletContext()); UserService userService = appContext.getBean("userService"); ... some code which uses UserService

这个方法可以工作。将大部分逻辑封装在页面或组件基类的一个方法中可以减少很多冗余。然而,这在某些方面违背了Spring所倡导的反向控制方法,而应用中其它层次恰恰在使用反向控制,因为你希望页面不必向上下文要求某个名字的bean,事实上,页面也的确对上下文一无所知。

幸运的是,有一个方法可以做到这一点。这是因为Tapestry已经提供一种方法给页面添加声明属性,事实上,以声明方式管理一个页面上的所有属性是首选的方法,这样Tapestry能够将属性的生命周期作为页面和组件生命周期的一部分加以管理。

向Tapestry暴露应用上下文
首先我们需要Tapestry页面组件在没有ServletContext的情况下访问ApplicationContext;这是因为在页面/组件生命周期里,当我们需要访问ApplicationContext时,ServletContext并不能被页面很方便地访问到,所以我们不能直接使用WebApplicationContextUtils.getApplicationContext(servletContext)。一个方法就是实现一个特定的Tapestry的IEngine来暴露它:

package com.whatever.web.xportal; ... import ... ... public class MyEngine extends org.apache.tapestry.engine.BaseEngine { public static final String APPLICATION_CONTEXT_KEY = "appContext"; /** * @see org.apache.tapestry.engine.AbstractEngine#setupForRequest(org.apache.tapestry.request.RequestContext) */ protected void setupForRequest(RequestContext context) { super.setupForRequest(context); // insert ApplicationContext in global, if not there Map global = (Map) getGlobal(); ApplicationContext ac = (ApplicationContext) global.get(APPLICATION_CONTEXT_KEY); if (ac == null) { ac = WebApplicationContextUtils.getWebApplicationContext( context.getServlet().getServletContext() ); global.put(APPLICATION_CONTEXT_KEY, ac); } } }

 这个engine类将Spring应用上下文作为“appContext”属性存放在Tapestry应用的“Global”对象中。在Tapestry应用定义文件中必须保证这个特殊的IEngine实例在这个Tapestry应用中被使用。例如,

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

组件定义文件
现在在我们的页面或组件定义文件(*.page或*.jwc)中,我们仅仅添加property-specification元素从ApplicatonContext中获取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表达式使用上下文中的bean来指定属性的初始值。整个页面定义文件如下: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE page-specification PUBLIC "-//Apache Software Foundation//Tapestry Specification 3.0//EN" "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd"> <page-specification class="com.whatever.web.xportal.pages.Login"> <property-specification name="username" type="java.lang.String"/> <property-specification name="password" type="java.lang.String"/> <property-specification name="error" type="java.lang.String"/> <property-specification name="callback" type="org.apache.tapestry.callback.ICallback" persistent="yes"/> <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> <bean name="delegate" class="com.whatever.web.xportal.PortalValidationDelegate"/> <bean name="validator" class="org.apache.tapestry.valid.StringValidator" lifecycle="page"> <set-property name="required" expression="true"/> <set-property name="clientScriptingEnabled" expression="true"/> </bean> <component id="inputUsername" type="ValidField"> <static-binding name="displayName" value="Username"/> <binding name="value" expression="username"/> <binding name="validator" expression="beans.validator"/> </component> <component id="inputPassword" type="ValidField"> <binding name="value" expression="password"/> <binding name="validator" expression="beans.validator"/> <static-binding name="displayName" value="Password"/> <binding name="hidden" expression="true"/> </component> </page-specification>

添加抽象访问方法
现在在页面或组件本身的Java类定义中,我们所需要做的是为我们定义的属性添加抽象getter方法。当Tapestry真正载入页面或组件时,Tepestry会对类文件作一些运行时的代码处理,添加已定义的属性,挂接抽象getter方法到新创建的域上。例如:

// our UserService implementation; will come from page definition public abstract UserService getUserService(); // our AuthenticationService implementation; will come from page definition public abstract AuthenticationService getAuthenticationService(); 这个例子的login页面的完整Java类如下: package com.whatever.web.xportal.pages; /** * Allows the user to login, by providing username and password. * After succesfully logging in, a cookie is placed on the client browser * that provides the default username for future logins (the cookie * persists for a week). */ public abstract class Login extends BasePage implements ErrorProperty, PageRenderListener { /** the key under which the authenticated user object is stored in the visit as */ public static final String USER_KEY = "user"; /** * The name of a cookie to store on the user's machine that will identify * them next time they log in. **/ private static final String COOKIE_NAME = Login.class.getName() + ".username"; private final static int ONE_WEEK = 7 * 24 * 60 * 60; // --- attributes public abstract String getUsername(); public abstract void setUsername(String username); public abstract String getPassword(); public abstract void setPassword(String password); public abstract ICallback getCallback(); public abstract void setCallback(ICallback value); public abstract UserService getUserService(); public abstract AuthenticationService getAuthenticationService(); // --- methods protected IValidationDelegate getValidationDelegate() { return (IValidationDelegate) getBeans().getBean("delegate"); } protected void setErrorField(String componentId, String message) { IFormComponent field = (IFormComponent) getComponent(componentId); IValidationDelegate delegate = getValidationDelegate(); delegate.setFormComponent(field); delegate.record(new ValidatorException(message)); } /** * Attempts to login. * * <p>If the user name is not known, or the password is invalid, then an error * message is displayed. * **/ public void attemptLogin(IRequestCycle cycle) { String password = getPassword(); // Do a little extra work to clear out the password. setPassword(null); IValidationDelegate delegate = getValidationDelegate(); delegate.setFormComponent((IFormComponent) getComponent("inputPassword")); delegate.recordFieldInputValue(null); // An error, from a validation field, may already have occured. if (delegate.getHasErrors()) return; try { User user = getAuthenticationService().login(getUsername(), getPassword()); loginUser(user, cycle); } catch (FailedLoginException ex) { this.setError("Login failed: " + ex.getMessage()); return; } } /** * Sets up the {@link User} as the logged in user, creates * a cookie for their username (for subsequent logins), * and redirects to the appropriate page, or * a specified page). * **/ public void loginUser(User user, IRequestCycle cycle) { String username = user.getUsername(); // Get the visit object; this will likely force the // creation of the visit object and an HttpSession. Map visit = (Map) getVisit(); visit.put(USER_KEY, user); // After logging in, go to the MyLibrary page, unless otherwise // specified. ICallback callback = getCallback(); if (callback == null) cycle.activate("Home"); else callback.performCallback(cycle); // I've found that failing to set a maximum age and a path means that // the browser (IE 5.0 anyway) quietly drops the cookie. IEngine engine = getEngine(); Cookie cookie = new Cookie(COOKIE_NAME, username); cookie.setPath(engine.getServletPath()); cookie.setMaxAge(ONE_WEEK); // Record the user's username in a cookie cycle.getRequestContext().addCookie(cookie); engine.forgetPage(getPageName()); } public void pageBeginRender(PageEvent event) { if (getUsername() == null) setUsername(getRequestCycle().getRequestContext().getCookieValue(COOKIE_NAME)); } }




13.7.3. 小结
在这个例子中,我们用声明的方式将定义在Spring的ApplicationContext中业务bean能够被页面访问。页面类并不知道业务实现从哪里来,事实上,也很容易转移到另一个实现,例如为了测试。这样的反向控制是Spring框架的主要目标和优点,在这个Tapestry应用中,我们在J2EE栈上自始至终使用反向控制。


--------------------------------------------------------------------------------

1 2 3 4 5 6
【内容导航】
第1页: 和JSP & JSTL一起使用Spring 第2页: Tiles的使用
第3页: Velocity 第4页: XSLT视图
第5页: 文档视图 (PDF/Excel) 第6页: 架构
©版权所有。未经许可,不得转载。
[责任编辑:JavaWorker]
[an error occurred while processing this directive]