我终于完成了添加 GlassFish 支持到 seam-gen 的修改(文档见这里和这里),并将其整合到 Seam 项目中(JBSEAM-1619)。在集成更改的过程中,我设法关闭了剩余的一些差距,并为 JBoss AS 5 添加了支持!
让我告诉你,调整配置和构建以支持所有十二种部署变体(GlassFish V2/V3 的 EAR 和 WAR 存档、JBoss AS 4.2.x 和 JBoss AS 5.0.x 的爆炸式和打包格式)是一项相当大的任务;(如果你考虑我为 RichFaces 和 ICEfaces 项目添加了支持,则这个数字要翻倍)。但令人惊讶的是,现在它们都能直接工作(除了部署 EAR 项目到 GlassFish 时需要进行的少量配置更改)。
持久性悖论
最大的挑战是持久性单元配置。这源于持久性单元的打包要求在 EAR 和 WAR 之间有所不同。更复杂的是,应用程序服务器在解释和强制执行这些要求方面存在差异。我想花一些时间解释这种情况以及我是如何找到平衡的,因为这对理解项目构建现在的工作方式很重要。
让我们先回顾一下事情原来的设置。JPA 规范中有一个期望,即在 Java EE 环境中,应用程序服务器将引导在 META-INF/persistence.xml 中定义的持久性单元。当然,如果这是使用 JPA 的唯一方式,那就相当有限了。因此,提供了一个 API,允许应用程序在 Java SE 环境中(或在容器不支持此期望的情况下)手动加载持久性单元。可以理解的是,随着供应商努力使他们的应用程序服务器符合 Java EE 5(第一个包含 JPA 的规范),这一期望需要一些时间才能得到满足。
对于不支持加载持久化单元的容器,无论是特定存档类型(EAR或WAR)还是一般情况,Seam可以介入并接管这项任务。这就是以下组件定义的目的,该定义在Seam组件描述符(即components.xml)中定义。
<persistence:entity-manager-factory name="entityManagerFactory" persistence-unit-name="example"/>
以下属性值persistence-unit-name属性与在META-INF/persistence.xml中定义的持久化单元之一名称匹配。
在JBoss AS 5之前的版本中,JBoss AS不支持在WAR中加载持久化单元,因此需要将此任务委托给Seam。对于任何版本的Tomcat也是如此。GlassFish更符合规范。它将加载持久化单元,但前提是它已按如下方式在WEB-INF/web.xml中的持久化单元引用中声明:
<persistence-unit-ref> <persistence-unit-ref-name>example/pu</persistence-unit-ref-name> <persistence-unit-name>example</persistence-unit-name> </persistence-unit-ref>
持久化单元引用还有另一个目的。在持久化单元加载后,EntityManagerFactory实例将使用以下属性的值绑定到JNDIpersistence-unit-ref-name在java:comp/env命名空间(在此示例中,完整的JNDI名称为java:comp/env/example/pu)。在版本5之前,JBoss AS不支持这种JNDI绑定。为了提供一个临时的解决方案,Hibernate可以提供在私有JBoss AS命名空间下将EntityManagerFactory绑定到JNDI的功能java:/。如果META-INF/persistence.xml中存在以下属性,则此功能被激活
<property name="jboss.entity.manager.factory.jndi.name" value="java:/exampleEntityManagerFactory"/>
JBoss AS 5支持将EntityManagerFactory绑定到JNDI的合规方法,但有一个副作用。它会自动加载在META-INF/persistence.xml中定义的所有持久化单元,无论它们是否在持久化单元引用中声明。这个事实让很多人感到困惑,并且是现在在JBoss AS 5上大多数Seam部署失败的根本原因。总之,最好只定义你打算加载的持久化单元,然后让容器加载它们。
到目前为止,讨论仅适用于WAR部署。对于EAR来说,事情变得更加复杂。技术上,持久化单元(描述符和类)应打包在其自己的JAR文件中,然后将其打包在WAR的WEB-INF/lib目录下,或者直接打包在EAR中。然而,持久化单元也可以打包在EJB JAR中。无论哪种方式,你都必须非常清楚自己在做什么,才能正确设置所有引用,以便正确加载持久化单元,并将其绑定到JNDI,以便WAR可以访问它。当应用服务器按预期工作,以下持久化单元配置就足够了
<persistence-unit-ref> <persistence-unit-ref-name>example/pu</persistence-unit-ref-name> <persistence-unit-name>../example.jar#example</persistence-unit-name> </persistence-unit-ref>
注意持久化单元名称现在包括包含持久化单元描述符和类的JAR文件在EAR中的相对位置。由于某种原因,GlassFish不理解此配置,因此我已包含一个临时的解决方案(请参阅seam-gen EAR项目的resources/WEB-INF/web.xml中的说明)。
为了总结各种变体,我已整理了一个矩阵,说明根据存档类型和环境处理持久化单元以及它们在seam-gen项目中如何加载。
WAR | EAR | |||||
---|---|---|---|---|---|---|
JBoss AS 4.2 | JBoss AS 5.0 | GlassFish | JBoss AS 4.2 | JBoss AS 5.0 | GlassFish | |
由 | Seam | 容器启动 | 容器启动 | 容器启动 | 容器启动 | 容器启动 |
依赖于持久化单元引用 | 否 | 否 | 是 | 否 | 否 | 是 |
JNDI绑定机制 | 不适用 | 两者 | 标准 | 专有 | 两者 | 标准 |
自动检测实体 | 是 | 是 | 是 | 是 | 是 | 否 |
正如您所想象的,处理所有这些变化需要来自构建的大量支持。幸运的是,我能够利用Seam的一个特性来满足这些需求。您可以在Seam组件描述符中使用Ant风格的占位符,Seam将使用类路径根目录下的components.properties文件中定义的替换值来替换它们。但是,替换值也必须是动态的,以支持各种环境。因此,我在该文件中也使用了Ant风格的占位符,这些占位符由Ant构建替换。因此,这里发生了一个两阶段替换。下面是components-dev.properties文件的内容
jndiPattern=@ejbJndiPattern@ debug=true seamBootstrapsPu=@seamBootstrapsPu@ seamEmfRef=@seamEmfRef@ puJndiName=@puJndiName@
下面是从这个模板生成的components.properties文件,用于针对名为example的EAR项目中的JBoss AS 5构建
jndiPattern=example/#{ejbName}/local debug=true seamBootstrapsPu=false seamEmfRef=#{null} puJndiName=java:comp/env/example/pu
下面是为针对名为example的WAR项目中的JBoss AS 4构建准备的components.properties文件
jndiPattern=example/#{ejbName}/local debug=true seamBootstrapsPu=true seamEmfRef=#{entityManagerFactory} puJndiName=#{null}
这些占位符被应用于Seam组件描述符中的以下两个组件定义
<persistence:managed-persistence-context name="entityManager" auto-create="true" entity-manager-factory="@seamEmfRef@" persistence-unit-jndi-name="@puJndiName@"/> <persistence:entity-manager-factory name="entityManagerFactory" persistence-unit-name="@projectName@" installed="@seamBootstrapsPu@"/>
嗯!这涵盖了JPA配置。有关Java EE和Seam中持久化单元配置的更多详细信息,请查看《Seam in Action》第9章。现在我想简要提及支持部署到JBoss AS 5和GlassFish的新目标。
JBoss AS 5和GlassFish目标
与之前一样,seam-gen项目的构建默认支持JBoss AS。但现在,我可以通过检查JBoss AS 5安装目录(从jboss.home解析)来支持JBoss 4和JBoss 5,以确定目标JBoss AS的版本。我只是检查JAR文件的存在。
GlassFish确实有一套自己的目标。所有与GlassFish相关的目标都位于项目根目录下的glassfish-build.xml文件中。此文件被导入到主Ant构建中。所有GlassFish目标都以前缀gf-开头gf-并紧密地反映了JBoss AS的目标。还有控制GlassFish服务器的目标。以下是新目标的简要总结,这些目标在glassfish-readme.txt文件中有详细文档
gf-start - Starts GlassFish gf-debug - Starts GlassFish in debug mode gf-stop - Stops GlassFish gf-reboot - Restarts GlassFish gf-deploy-datasource - Deploys the datasource and connection pool to GlassFish gf-explode - Deploys the exploded archive to GlassFish (restarts application if already deployed) gf-hotdeploy - Hot deploys Java classes, Seam components, and web resources gf-deploy - Deploys the packaged archive to GlassFish gf-undeploy - Undeploys the exploded or packaged archive from GlassFish gf-stage - Prepares an exploded archive targeting GlassFish gf-archive - Prepares a packaged archive targeting GlassFish gf-prepare - Prepares GlassFish for a seam-gen project deployment (calls gf-deploy-hibernate) gf-deploy-hibernate - Deploys Hibernate as a JPA provider to GlassFish
您不需要做任何事情就可以启用对GlassFish的支持。它与对JBoss AS的支持一起提供。我在工作于GlassFish支持的过程中终于关闭了一个差距,那就是explode EAR的部署。GlassFish一直因为包含EJBs的jboss-seam.jar而窒息,因此被认为是EJB模块。事实证明,GlassFish不支持打包和展开存档的混合。因此,当您部署展开的EAR时,还需要展开jboss-seam.jar文件,构建会为您处理。
我必须做出的唯一权衡是,在GlassFish中期望Hibernate是JPA提供程序(注意部署Hibernate的目标)。切换到另一个JPA提供程序并不难,但可以说,Seam与Hibernate配合最好。
增长的Ivy
Seam项目在库管理方面以其比较老式而闻名。所有JAR文件都只是被丢弃到lib目录中,并在需要时从中挑选。这使得共享seam-gen项目非常困难,因为它太大。我在这篇条目中解释了如何使用Ivy来管理seam-gen项目的依赖项。seam-gen现在将该配置作为扩展提供。要启用它,您只需运行以下seam-gen目标
seam add-ivy
目前,仅支持RichFaces项目,但支持ICEfaces项目只需对依赖项进行一些更改。
总结
现在您可以看到,seam-gen不再局限于JBoss世界。对于早期采用者来说,JBoss AS 5——JBoss的骄傲和喜悦——现在得到了支持。将Ivy添加到项目中有助于使其更加轻量级,提供了一种轻松下载源JAR的方式,并有助于记录正在使用的JAR版本。只需从SVN检查Seam,构建并享受即可!