ZK/操作指南/整合其他框架
1. 将 Spring 框架 jar 和其所有依赖项添加到服务器的类路径中。 (有关详细信息,请参阅 http://www.springframework.org 上的文档)。
2. 将以下内容添加到您的 web.xml 中
<context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
3. 将以下 applicationContext.xml 文件放入您的 WEB-INF 目录中
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="myBusinessServiceSpringBean" class="test.BusinessServiceImpl"/> </beans>
4. test.BusinessServiceImpl 和 test.BusinessService 的示例
package test; import java.util.*; public class BusinessServiceImpl implements BusinessService { public List getElementsList() { // some logic here return Arrays.asList(new String[] {"abc", "def"}); } }
package test; public interface BusinessService { java.util.List getElementsList(); }
5. 编译并部署。
6. 最后,一个调用 Spring 管理的 BusinessService 方法的 ZUL 文件示例
<?xml version="1.0" encoding="windows-1251"?> <?variable-resolver class="org.zkoss.zkplus.spring.DelegatingVariableResolver"?> <window> <grid> <rows> <row forEach="${myBusinessServiceSpringBean.elementsList}"> <label value="${each}"/> </row> </rows> </grid> </window>
其中 forEach 循环遍历由对 Spring bean 方法的调用返回的集合,而 ${each} 属性会导致对集合中每个对象调用 toString。 您也可以使用 zscript 直接操作您的 Spring bean。 此代码与上述代码执行相同的操作
<?xml version="1.0" encoding="windows-1251"?> <?variable-resolver class="org.zkoss.zkplus.spring.DelegatingVariableResolver"?> <zscript> List someData = myBusinessServiceSpringBean.getElementsList(); </zscript <window> <grid> <rows> <row forEach="${someData}"> <label value="${each}"/> </row> </rows> </grid> </window>
如果您选择使用 MVC 模式(请参阅此维基中的 Composer 和 GenericAutowireComposer 示例),那么您可以让 Spring 创建您的模型和控制器 bean。 考虑以下 applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd"> <beans> <bean id="myModel" scope="session" class="com.acme.MyModelImpl" /> <bean id="myController" scope="prototype" class="com.acme.MyControllerImpl"> <constructor-arg ref="myModel" /> </bean> </beans>
在这里,我们告诉 Spring 为给定的用户 HttpSession 创建一个 com.acme.MyModelImpl 对象,但为每个应用程序查找创建新的 com.acme.MyControllerImpl bean。 每个“myController” bean 都是使用一个构造函数创建的,该构造函数传递了范围为 HttpSession 的“myModel” bean。 我们需要在 web.xml 中激活 RequestContextListener 才能使会话范围生效
<listener> <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class> </listener>
现在在任何给定页面内,我们都可以让 ZK 要求 Spring 为一个给定的窗口对象使用一个新的 myController bean 作为控制器
<?xml version="1.0" encoding="UTF-8"?> <zk xmlns="http://www.zkoss.org/2005/zul"> <?variable-resolver class="org.zkoss.zkplus.spring.DelegatingVariableResolver"?> <window id="mainWindow" apply="${myController}"> <textbox forward="onChange=mainWindow.onUserEntersDiscountCouponNumber"/> </window>
上面的代码使用了 DelegatingVariableResolver。 它通过 Spring 解析“apply”属性中的 myController 引用(请参阅 MVC 和“apply”属性文档)。 Spring 按需创建与会话范围的 myModel bean 连接的新对象。“forward”属性将委托文本框 onChange 事件到 mainWindow 的自定义“onUserEntersDiscountCouponNumber” 事件处理程序。 Window 组件类没有这样的事件处理程序,但是“apply”属性中指定的对象可以向它应用到的窗口对象添加一个事件处理程序。 最简单的实现方法是让 com.acme.MyControllerImpl 控制器类子类化 GenericAutowireComposer 并提供一个事件处理程序“public void onUserEntersDiscountCouponNumber(Event event) throws Exception”。 然后,超类中的逻辑将自动向 mainWindow 对象添加一个事件处理程序,该处理程序调用您提供的事件处理程序。 请参阅此维基中有关 MVC 的示例,以了解详细信息以及在 http://www.zkoss.org 上发布的 MVC 小话题
注意 比以下方法更轻量级的方法是打开 ZKFilter 并让它对您操作的 URL 进行后处理。 这只有在您的 JSP 输出纯 XHTML 时才有效(使用 jTidy -asxml 将 html 转换为 xhtml,然后将 CDATA 部分放在页面中的任何脚本源代码周围)。
注意 使用 ZK3.0 时,请考虑使用 ZK 自定义标签库,该标签库允许您使用标签库将 ZK 组件写入页面。
ZK 提供了非常丰富的功能集。 您可以使用它构建整个单页 Ajax 驱动的富 Web 应用程序,而无需使用任何 Web 编程。 然而,许多公司在现有的 J2EE Web 技术(如 Struts、Tiles 和 JSP)方面投入了大量资金。 这些公司可能希望通过在那些从 XUL 组件中受益的页面上的中心磁贴中开始使用 ZK XUL 来利用其现有投资。 本节概述了一种实现此目的的方法。 您需要熟悉 Spring 中对 Struts 的支持才能遵循此示例。
由于此操作指南涵盖了 ZK、Struts 和 Spring,因此您需要在 web.xml 中指定它们(请注意,zkFilter 没有加载,因为 Tiles 会导致 zkLoader 渲染纯 zul)
<!-- SECTION FOR SPRING --> <listener> <display-name>Spring Context Loader</display-name> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- SECTION FOR ZK --> <listener> <description>Used to cleanup when a session isdestroyed</description> <display-name>ZK Session Cleaner</display-name> <listener-class>org.zkoss.zk.ui.http.HttpSessionListener</listener-class> </listener> <servlet> <description>ZK loader for evaluating ZK pages</description> <servlet-name>zkLoader</servlet-name> <servlet-class>org.zkoss.zk.ui.http.DHtmlLayoutServlet</servlet-class> <!-- Specifies URI of the update engine(DHtmlUpdateServlet). --> <init-param> <param-name>update-uri</param-name> <param-value>/zkau</param-value> </init-param> <load-on-startup>1</load-on-startup><!-- MUST --> </servlet> <servlet-mapping> <servlet-name>zkLoader</servlet-name> <url-pattern>*.zul</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>zkLoader</servlet-name> <url-pattern>*.zhtml</url-pattern> </servlet-mapping> <servlet> <description>The asynchronous update engine for ZK</description> <servlet-name>auEngine</servlet-name> <servlet-class>org.zkoss.zk.au.http.DHtmlUpdateServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>auEngine</servlet-name> <url-pattern>/zkau/*</url-pattern> </servlet-mapping> <!-- SECTION FOR STRUTS --> <servlet> <servlet-name>The Struts Engine</servlet-name> <servlet-class>org.apache.struts.action.ActionServlet</servlet-class> <init-param> <param-name>config</param-name> <param-value>/WEB-INF/struts/struts-config.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>action</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping>
创建您的 struts 配置文件 struts-config.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd"> <struts-config> <form-beans> <!-- This Form Bean will hold the HTTP POST data sent from our /search.jsp page --> <form-bean name="SearchStuff" type="com.yourdomain.web.StuffSearchForm"></form-bean> </form-beans> <action-mappings> <!-- This Action Mapping delegates to Spring to find a Bean called SearchStuff --> <action path="/SearchStuff" name="SearchStuff" type="org.springframework.web.struts.DelegatingActionProxy" validate="false" scope="request"> <forward name="success" path="/search-results-tile.jsp"/> <forward name="errors" path="/search-errors-tile.jsp"/> </action> </action-mappings> <controller processorClass="org.apache.struts.action.RequestProcessor"></controller> <!-- Message Resources --> <message-resources parameter="ApplicationResources" /> <!-- Spring support for Struts --> <plug-in className="org.springframework.web.struts.ContextLoaderPlugIn"> <set-property property="contextConfigLocation" value="/WEB-INF/spring/applicationContext.xml" /> </plug-in> </struts-config>
然后在您的 Spring 配置文件 applicationContext.xml 中
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <!-- Struts will delegate onto this custom struts Action Class --> <bean name="/SearchStuff" class="com.yourdomain.web.StuffSearchAction"> <property name="searchDelegate"> <ref bean="searchDelegate" /> </property> </bean> <!-- The Action Class needs a Business Delegate --> <bean id="searchDelegate" class="com.yourdomain.business.SearchDelegateImpl"> <constructor-arg><ref bean="pojoSessionFacade"/></constructor-arg> </bean> <!-- The Business Delegate needs a Pojo Facade that implements a package of use cases --> <bean id="pojoSessionFacade" class="com.yourdomain.business.PojoSessionFacadeImpl"> <constructor-arg>...</constructor-arg> <constructor-arg>...</constructor-arg> </bean> <!-- The pojo sesion facade should be wrapped in a spring transaction interceptor. Working open source code examples are at this link http://www.manning.com/crichardson/. The code is Apache licienced and will compile, build and unit test a working pojo Session Facade using either JDO, Hibernate or iBATIS sql maps. See his applicationContext.xml for details. --> ... </beans>
请注意,在 Struts 配置中,我们有两个操作映射,分别对应“成功”或“错误”。 成功映射到 search-results-tile.jsp,其中包含
<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %> <tiles:insert page="site-layout.jsp" flush="true"> <tiles:put name="body-content" value="/search-results.zul" /> </tiles:insert>
这基本上表示此页面看起来像 site-layout,但在页面中间有 search-results.zul。 这是因为在 site-layout.jsp 中存在
<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles"%> <html> <head>...</head> <body> <div class="main"> <div class="banner"> <!-- lots of html --> .... </div> <div class="left-menu"> <!-- The site menu could be a JSP using JSTL and custom tags --> <tiles:insert attribute="site-menu" /> </div> <div class="main-panel"> <!-- The main panel could be either a .jsp or a .zul--> <tiles:insert attribute="body-content" /> </div> ... </div> </body> </html>
有了这些信息,我们看到在特定的 search-results-tile.jsp 中,我们声明它基于 site-layout,但在 body-content 位置使用 search-results.zul。
接下来,我们需要确保 Struts Action 通过 HTTP Session 将结果传递到 search-results.zul。 为此,Struts Action 可以执行以下操作
public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { StuffSearchForm stuffSearchForm = (StuffSearchForm)form; ActionErrors errors = stuffSearchForm.validate(mapping, request); if(!errors.isEmpty()){ saveErrors(request, errors); return mapping.findForward("errors"); } // Business logic. The protected field this.searchDelegate is an interface type. The concrete // instance was configured by Spring and injected when this object was constructed. List searchResultsList = this.searchDelegate.searchStuff(stuffSearchForm.getStuffSearchCriteria()); // Put the list where ZUL can get at it in the web session request.getSession().setAttribute("searchResults", searchResultsList); return mapping.findForward("success"); }
请注意,this.searchDelegate 将由 Spring 设置,如 applicationContext.xml 中定义的那样
因此,我们现在知道我们的 Struts 类将搜索结果列表放入 HTTP Session 中,我们的 search-results.zul 可以从中找到它
<window> <zscript><![CDATA[ /** * Here we pick up the search results that were set by our Struts Action class. */ List iterateOverMe = sessionScope.get("searchResults"); ]]></zscript> <tabbox> <tabs> <!-- Iterate over our List once to draw the tabpanel tabs. --> <tab forEach="${iterateOverMe}" label="${each.searchResultLabel}"/> </tabs> <tabpanels> <!-- Iterate over our Collection again to draw the tabpanel panels. --> <tabpanel forEach="${iterateOverMe}"> <!-- Here you put whatever ZUL that you need to render your search result items using ${each}. See other examples in this wiki. --> ... </tabpanel> </tabpanels> </tabbox> </window>
但是,您应该意识到,让 Struts 为我们执行搜索会使我们的服务器进行比必要更多的繁重的页面重建工作。 更加以 AJAX 为中心的做法是编写一个单一的 ZUL 页面,首先向用户呈现一个搜索输入表单。 然后,在 zscript 事件处理程序中运行搜索并使用搜索结果更新 ZK 桌面。 只有搜索结果的 html 才会在 AJAX 响应中发送回浏览器。 实现此目的的一种模式是将最初位于 Struts Action 子类中的代码移动到 org.zkoss.zul.Window 的子类中,然后在 zscript 事件处理程序中呈现搜索结果
<?page id="main-page"?> <!-- NOTE THAT WE SPECIFY OUR OWN CUSTOM BUSINESS DELEGATE SUBCLASS OF WINDOW HERE! --> <window use="com.yourdomain.zul.SearchWindow" id="SearchFacadeDelegate"> <zscript> void doSearch(){ businessFacadeDelegate = Path.getComponent('/SearchFacadeDelegate'); businessFacadeDelegate.setSearchNameCriteria(name_criteria.value); searchResultsPlaceHolder.src = "render-search-results.zul"; // The file render-search-results.zul should have the delegate run // the actual search and render the results } </zscript> <label value="Search By Name:"/><textbox name="name_criteria"/> <button label="Search"> <attribute name="onClick"> doSearch(); </attribute> </button> <include id="searchResultsPlaceHolder" src=""/> </window>
注意 - 使用 Tiles 时,ZUL 页面可能不会注意到它被包含,并且它可能会将 <html> 和 <head> 标签写入页面。 为了避免这种情况,而不是让磁贴包含原始 zul 页面,请包含一个 jsp 页面,该页面仅 JSP 包含 zul 页面,这将表现良好。
有关介绍,请参阅此 小话题。
没有 Spring 的 ZK Hibernate 支持:Hibernate 会话处理。 初步!
ZK 团队从 2.1.1 版开始提供了一组类来支持没有 Spring 的 Hibernate。 Henri Chen 在他的 Hibernate + ZK 文章中解释了详细信息。
有关介绍,请参阅此 小话题
Hari Gangadharan 还写了另一个小话题 - 让 Acegi 与 ZK 一起工作
作者:Marcos de Sousa (马普托 - 莫桑比克)
01/06/2007
此条目背后的动机是为整合 ZK 框架 2.x.x + Spring 框架 2.x.x + Hibernate 3.x 提供一个简单的分步指南。 我将向您展示如何在真实的 J2EE 世界应用程序中完成操作。
A. 初始设置
1. 下载 ZK 框架 JAR 并添加到您的类路径中:zk.jar, zul.jar, zhtml.jar, zkplus.jar, zweb.jar, zcommon.jar, bsh.jar, jcommon.jar, commons-el.jar, commons-fileupload.jar, commons-io.jar, Filters.jar
2. 下载 Spring 框架并添加到您的类路径中:spring.jar, spring-mock.jar
3. 下载 Hibernate 3.x.x 和 Hibernate Annotation 3.x.x,然后将其添加到您的类路径中:antlr-2.x.x.jar, asm.jar, asm-attrs.jar, cglib-2.x.x.jar, commons-collections 2.x.x.jar, commons-logging-1.x.x.jar, dom4j-1.x.x.jar, ejb3-persistence.jar, hibernate3.jar, hibernate-annotations.jar, jdbc2_0-stdext.jar, jta.jar, xerces-2.x.x, xml-apis.jar
4. 添加包含您的数据库驱动程序的 JAR(我在示例中使用 SQL Server sqljdbc.jar,但只需要进行少量更改即可适应其他数据库)。
5. 添加 JAR junit-3.x.x.jar 用于单元测试。
B. 代码 - 域模型
本示例将基于一个简单的域模型,仅包含两个类。请注意注解的使用。你可以选择使用注解或 XML 文件来指定对象关系映射元数据,甚至可以组合使用两种方法。这里我选择仅使用注解,并在域模型代码列表之后提供简要说明。
首先是 **Employee** 类
package com.wikibook.persistence; import java.io.Serializable; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; @SuppressWarnings("serial") @Entity public class Employee implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String fullname; private String email; private String username; private String password; @ManyToOne @JoinColumn(name = "role") private Role role; public Employee() { } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getFullname() { return fullname; } public void setFullname(String fullname) { this.fullname = fullname; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Role getRole() { return role; } public void setRole(Role role) { this.role = role; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } }
其次是 **Role** 类
package com.wikibook.persistence; import java.io.Serializable; import java.util.HashSet; import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.OneToMany; @SuppressWarnings("serial") @Entity public class Role implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToMany(cascade = CascadeType.ALL, mappedBy = "role") private Set<Employee> employees = new HashSet<Employee>(); public Role() { } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Set<Employee> getEmployees() { return employees; } public void setEmployees(Set<Employee> employees) { this.employees = employees; } public void addEmployee(Employee employee) { this.employees.add(employee); } }
在 *Employee* 类中,有一个 *@ManyToOne* 注解来描述与 *Role* 的关系。请查看 *cascade* 规则。例如,当一个 *Role* 被删除时,与其关联的 *Employees* 也会被删除。
C. 代码 - 数据访问对象层
为了访问域模型的实例,最好创建一个通用的接口,隐藏底层持久化机制的所有细节。这样,如果以后切换到除 Hibernate Annotation 之外的其他东西,就不会对架构造成影响,只需进行少量更改即可适应迁移到 JPA。这也有助于更轻松地测试服务层,因为它允许创建此数据访问接口的存根实现,甚至动态模拟实现。
以下是该接口。请注意,这里没有任何对 Hibernate Annotation 或 Spring 类的依赖。实际上,这里唯一不是核心 Java 类的依赖项是我的域模型的类。
package com.wikibook.persistence.dao; import java.util.List; import com.wikibook.persistence.Employee; public interface EmployeeDao { void save(Employee employee); void update(Employee employee); void delete(Employee employee); Employee get(Long id); List<Employee> getAll(); Employee getByUserName(String username); List<Employee> getAllByRole(Integer role); }
package com.wikibook.persistence.dao; import java.util.List; import com.wikibook.persistence.Role; public interface RoleDao { void save(Role role); void update(Role role); void delete(Role role); Role get(Long id); List<Role> getAll(); Role getByName(String name); }
为了实现此接口,我将扩展 Spring 的 *HibernateDaoSupport* 类。这提供了一个获取 *HibernateTemplate* 的便捷方法。如果你以前使用过带有 JDBC 或其他 ORM 技术的 Spring,那么你可能对这种方法相当熟悉。
需要注意的是,使用 HibernateDaoSupport 是可选的。
以下是实现
package com.wikibook.persistence.dao; import java.util.List; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; import com.wikibook.persistence.Employee; public class EmployeeDaoImpl extends HibernateDaoSupport implements EmployeeDao { public void save(Employee employee) { getHibernateTemplate().save(employee); } public void update(Employee employee) { getHibernateTemplate().update(employee); } public void delete(Employee employee) { getHibernateTemplate().delete(employee); } public Employee get(Long id) { return (Employee)getHibernateTemplate().get(Employee.class, id); } @SuppressWarnings("unchecked") public List<Employee> getAll() { return getHibernateTemplate().loadAll(Employee.class); } @SuppressWarnings("unchecked") public Employee getByUserName(String username) { List<Employee> lista = getHibernateTemplate().find("FROM Employee WHERE username = ?", username); if (!lista.isEmpty()) { return lista.get(0); } return null; } @SuppressWarnings("unchecked") public List<Employee> getAllByRole(Integer role) { return getHibernateTemplate().find( "FROM Employee WHERE role = ?", role); } }
package com.wikibook.persistence.dao; import java.util.List; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; import com.wikibook.persistence.Role; public class RoleDaoImpl extends HibernateDaoSupport implements RoleDao { public void save(Role role) { getHibernateTemplate().save(role); } public void update(Role role) { getHibernateTemplate().update(role); } public void delete(Role role) { getHibernateTemplate().delete(role); } public Role get(Long id) { return (Role)getHibernateTemplate().get(Role.class, id); } @SuppressWarnings("unchecked") public List<Role> getAll() { return getHibernateTemplate().loadAll(Role.class); } @SuppressWarnings("unchecked") public Role getByName(String name) { List<Role> lista = getHibernateTemplate().find("FROM Role WHERE name = ?", name); if (!lista.isEmpty()) { return lista.get(0); } return null; } }
D. 服务层
服务层在系统架构中起着至关重要的作用。它将是事务分界点所在,通常,事务将在 Spring 配置中声明式地分界。在下一步中,当你查看配置时,你会注意到我已经提供了一个 *transactionManager* bean。它将使用事务包装服务层方法。关键点是数据访问层中没有与事务相关的代码。使用 Spring *HibernateTemplate* 可确保在所有 DAO 中共享相同的 *EntityManager*。因此,事务传播会自动发生,就像在 Spring 框架内配置的其他持久化机制一样。
以下是接口和实现
package com.wikibook.persistence.manager; import java.util.List; import com.wikibook.persistence.Employee; public interface EmployeeManager { void save(Employee employee); void update(Employee employee); void delete(Employee employee); Employee get(Long id); List<Employee> getAll(); Employee getByUserName(String username); List<Employee> getAllByRole(Integer role); }
package com.wikibook.persistence.manager; import java.util.List; import com.wikibook.persistence.Role; public interface RoleManager { void save(Role role); void update(Role role); void delete(Role role); Role get(Long id); List<Role> getAll(); Role getByName(String name); }
package com.wikibook.persistence.manager; import java.util.List; import com.wikibook.persistence.Employee; import com.wikibook.persistence.dao.EmployeeDao; public class EmployeeManagerImpl implements EmployeeManager { private EmployeeDao employeeDao; public void setEmployeeDao(EmployeeDao employeeDao) { this.employeeDao = employeeDao; } public void save(Employee employee) { this.employeeDao.save(employee); } public void update(Employee employee) { this.employeeDao.update(employee); } public void delete(Employee employee) { this.employeeDao.delete(employee); } public Employee get(Long id) { return this.employeeDao.get(id); } public List<Employee> getAll() { return this.employeeDao.getAll(); } public Employee getByUserName(String username) { return this.employeeDao.getByUserName(username); } public List<Employee> getAllByRole(Integer role) { return this.employeeDao.getAllByRole(role); } }
package com.wikibook.persistence.manager; import java.util.List; import com.wikibook.persistence.Role; import com.wikibook.persistence.dao.RoleDao; public class RoleManagerImpl implements RoleManager { private RoleDao roleDao; public void setRoleDao(RoleDao roleDao) { this.roleDao = roleDao; } public void save(Role role) { this.roleDao.save(role); } public void update(Role role) { this.roleDao.update(role); } public void delete(Role role) { this.roleDao.delete(role); } public Role get(Long id) { return this.roleDao.get(id); } @SuppressWarnings("unchecked") public List<Role> getAll() { return this.roleDao.getAll(); } public Role getByName(String name) { return this.roleDao.getByName(name); } }
E. 配置
由于我选择了基于注解的映射,因此你实际上已经看到了大部分 Hibernate Annotation 特定的配置,这些配置是在展示域类时提供的。如上所述,也可以通过 XML 配置这些映射。唯一其他必需的配置是在 *META-INF/context.xml*、Spring 应用程序上下文 *spring-sql.xml*(必须位于类路径或包开始的位置)和 *web.xml* 中。
以下是 **context.xml**
<?xml version="1.0" encoding="UTF-8"?> <Context crossContext="true" reloadable="true"> <Resource name="DefaultDS" auth="Container" type="javax.sql.DataSource" factory="org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory" driverClassName="com.microsoft.sqlserver.jdbc.SQLServerDriver" url="jdbc:sqlserver://COMPUTERNAME:1433;DatabaseName=WIKIBOOK" username="USERNAME" password="PASSWORD" maxActive="10" maxIdle="10" maxWait="-1" /> </Context>
以下是 **spring-sql.xml**(必须位于类路径根目录)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:jee="http://www.springframework.org/schema/jee" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.0.xsd"> <!-- JNDI DataSource for J2EE environments --> <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/DefaultDS" /> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="annotatedClasses"> <list> <value>com.wikibook.persistence.Role</value> <value>com.wikibook.persistence.Employee</value> </list> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.show_sql">true</prop> <prop key="hibernate.dialect"> org.hibernate.dialect.SQLServerDialect </prop> </props> </property> </bean> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <bean id="daoTxTemplate" abstract="true" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="transactionManager" ref="transactionManager" /> <property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean> <bean id="roleDao" class="com.wikibook.persistence.dao.RoleDaoImpl"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <bean id="roleManager" parent="daoTxTemplate"> <property name="proxyInterfaces"> <list> <value> com.wikibook.persistence.manager.RoleManager </value> </list> </property> <property name="target"> <bean class="com.wikibook.persistence.manager.RoleManagerImpl"> <property name="roleDao" ref="roleDao" /> </bean> </property> </bean> <bean id="employeeDao" class="com.wikibook.persistence.dao.EmployeeDaoImpl"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <bean id="employeeManager" parent="daoTxTemplate"> <property name="proxyInterfaces"> <list> <value> com.wikibook.persistence.manager.EmployeeManager </value> </list> </property> <property name="target"> <bean class="com.wikibook.persistence.manager.EmployeeManagerImpl"> <property name="employeeDao" ref="employeeDao" /> </bean> </property> </bean> </beans>
以下是 **web.xml**
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <description> <![CDATA[WikiBook]]> </description> <display-name>WikiBook</display-name> <!-- Spring ApplicationContext --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/classes/spring-*.xml</param-value> </context-param> <!-- INI ZK --> <!-- INI DSP --> <servlet> <description> <![CDATA[The servlet loads the DSP pages.]]> </description> <servlet-name>dspLoader</servlet-name> <servlet-class> org.zkoss.web.servlet.dsp.InterpreterServlet </servlet-class> </servlet> <servlet-mapping> <servlet-name>dspLoader</servlet-name> <url-pattern>*.dsp</url-pattern> </servlet-mapping> <!-- END DSP --> <servlet> <description>ZK loader for ZUML pages</description> <servlet-name>zkLoader</servlet-name> <servlet-class> org.zkoss.zk.ui.http.DHtmlLayoutServlet </servlet-class> <init-param> <param-name>update-uri</param-name> <param-value>/zkau</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>zkLoader</servlet-name> <url-pattern>*.zul</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>zkLoader</servlet-name> <url-pattern>*.zhtml</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>zkLoader</servlet-name> <url-pattern>/zk/*</url-pattern> </servlet-mapping> <servlet> <description>The asynchronous update engine for ZK</description> <servlet-name>auEngine</servlet-name> <servlet-class> org.zkoss.zk.au.http.DHtmlUpdateServlet </servlet-class> </servlet> <servlet-mapping> <servlet-name>auEngine</servlet-name> <url-pattern>/zkau/*</url-pattern> </servlet-mapping> <!-- END ZK --> <listener> <description> Used to cleanup when a session is destroyed </description> <display-name>ZK Session Cleaner</display-name> <listener-class> org.zkoss.zk.ui.http.HttpSessionListener </listener-class> </listener> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <listener> <listener-class> org.springframework.web.context.request.RequestContextListener </listener-class> </listener> <!-- INI filter --> <!-- Hibernate OpenSession Filter --> <filter> <filter-name>lazyLoadingFilter</filter-name> <filter-class> org.springframework.orm.hibernate3.support.OpenSessionInViewFilter </filter-class> </filter> <filter-mapping> <filter-name>lazyLoadingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- END filter --> <!-- Miscellaneous --> <session-config> <session-timeout>10</session-timeout> </session-config> <welcome-file-list> <welcome-file>index.zul</welcome-file> </welcome-file-list> <!-- INI resource-ref --> <resource-ref> <description>DefaultDS</description> <res-ref-name>jdbc/DefaultDS</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref> <!-- END resource-ref --> <!-- INI MIME mapping --> <!-- MIME mapping OMMITED--> <!-- END MIME mapping --> </web-app>
F. 集成测试
也许学习新 API 的最佳方法是编写大量测试用例。*TestEmployeeManager* 类提供了一些基本测试。为了进一步了解 Hibernate Annotation,请修改代码和/或配置,并观察对这些测试的影响。
以下是 **TestManager** 类
package com.wikibook.test; import javax.sql.DataSource; import org.springframework.jdbc.datasource.DriverManagerDataSource; import org.springframework.mock.jndi.SimpleNamingContextBuilder; import org.springframework.test.AbstractTransactionalSpringContextTests; public class TestManager extends AbstractTransactionalSpringContextTests { public TestManager() { try { if (SimpleNamingContextBuilder.getCurrentContextBuilder() == null) { SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder(); DataSource ds = new DriverManagerDataSource("com.microsoft.sqlserver.jdbc.SQLServerDriver", "jdbc:sqlserver://COMPUTERNAME:1433;DatabaseName=WIKIBOOK", "USERNAME", "PASSWORD"); builder.bind("java:comp/env/DefaultDS", ds); builder.activate(); } } catch (Exception e) { e.printStackTrace(); } } @Override protected String[] getConfigLocations() { return new String[] { "spring-sql.xml" }; } }
以下是 **TestEmployeeManager** 类
package com.wikibook.test; import com.wikibook.persistence.Employee; import com.wikibook.persistence.manager.EmployeeManager; public class TestEmployeeManager extends TestManager { private EmployeeManager employeeManager; public TestEmployeeManager() { } @Override protected void onSetUpBeforeTransaction() throws Exception { employeeManager = (EmployeeManager) applicationContext.getBean("employeeManager"); } public void testInsert() { Employee employee = new Employee(); employee.setFullname("Marcos de Sousa"); employee.setUsername("marcos.sousa"); employee.setPassword("password1"); employeeManager.save(employee); employee = new Employee(); employee.setFullname("Tom Yeh"); employee.setUsername("tom.yeh1"); employee.setPassword("password2"); employeeManager.save(employee); employee = new Employee(); employee.setFullname("Henri Chen"); employee.setUsername("henri.chen"); employee.setPassword("password3"); employeeManager.save(employee); setComplete(); // Commit the transactions, and don't rollback } public void testUpdate() { Employee employee = employeeManager.get(Long.valueOf(2)); assertEquals("Must be tom.yeh1", "tom.yeh1", employee.getUsername()); employee.setUsername("tom.yeh"); employeeManager.update(employee); employee = employeeManager.get(Long.valueOf(2)); assertEquals("Must be tom.yeh", "tom.yeh", employee.getUsername()); setComplete(); } public void testDelete() { assertEquals("Before must be 3", 3, employeeManager.getAll().size()); Employee employee = employeeManager.get(Long.valueOf(3)); employeeManager.delete(employee); assertEquals("After must be 2", 2, employeeManager.getAll().size()); setComplete(); } }
以下是 **AllTests** 类
package com.wikibook.test; import junit.framework.Test; import junit.framework.TestSuite; public class AllTests { public static Test suite() { TestSuite suite = new TestSuite("Test for com.wikibook.test"); //$JUnit-BEGIN$ suite.addTestSuite(TestEmployeeManager.class); // TODO: ADD MORE TEST SUITE HERE. For Example TestRoleManager //$JUnit-END$ return suite; } }
到这里为止,如果你运行你的单元测试,一切都会正常工作。
// TODO: 明天我会继续...
// TODO: 创建数据库、表,展示与 ZK 框架的集成作为前端、结论和技巧,等等。
// TODO: 也许展示 Acegi Security?OSWORKFLOW?IBM AS400?Ant?Maven 2?SSL/HTTPS?CSS?Eclipse?在实际应用中。