seam-gen默认会将Seam(以及其他一些)的依赖项复制到生成的项目的lib目录中。该目录中的JAR文件随后被放置到项目的构建路径上。虽然这种方法可以快速启动项目,但可能不是最佳的长远策略。难以确定项目实际依赖哪些库以及这些库的版本。您需要某种形式的正式依赖管理。
管理依赖关系的一种方法是将Maven 2用作构建工具,这是许多人要求seam-gen支持的。然而,转向Maven 2与当前的基于Ant的构建有很大的不同。在这篇文章中,我将解释如何将Ivy引入Ant构建中,以跟踪项目使用的依赖项及其版本,并从远程仓库中获取它们。 Ivy 是Ant的一个子项目,提供了一组专门用于为Java库提供依赖管理器的Ant任务。
关于本解决方案
我在本文中提出的设置独特之处在于,它将依赖项的获取与构建本身分开,因此一旦JAR文件就位,构建就可以像以前一样工作。这意味着Ivy的使用不会影响seam-gen项目提供的开发效率。您还可以创建包含所有依赖JAR文件的项目分发,这是确保可重复性的关键方面。但还有其他一些区别本解决方案的特点。
在某个时候,人们似乎将共享仓库与传递依赖项结合在一起。您会发现,可以从远程仓库中提取工件,而无需使用所有Maven 2项目共有的传递依赖机制(Ivy中的可选功能)。
为Ivy让路
将Ivy集成到项目的第一步是创建一个独立的Ant构建文件,我们将其命名为ivy.build.xml,以存放与Ivy相关的任务。将这些任务隔离到单独的文件中,使它们更易于重用,并保持主构建文件整洁。使用Ant导入任务在主构建文件顶部附近导入Ivy构建文件。
<import file="${basedir}/ivy.build.xml"/>
在Ivy构建文件中,您将创建的第一个任务是获取Ivy本身,以某种自我更新的方式。我们将Ivy JAR文件存储在项目根目录下的build-lib目录中,以将其与应用程序的依赖项隔离开来。为了提高效率,在尝试获取之前,我们还将检查Ivy JAR是否已经下载。以下是到目前为止我们讨论过的ivy.build.xml的内容
<?xml version="1.0"?> <project basedir="." xmlns:ivy="antlib:org.apache.ivy.ant" name="myproject-ivy"> <property name="ivy.install.version" value="2.0.0-beta2"/> <property name="ivy.jar.dir" value="${basedir}/build-lib"/> <property name="ivy.jar.file" value="${ivy.jar.dir}/ivy.jar"/> <property name="central.repo" value="http://repo1.maven.org/maven2"/> <property name="jboss.repo" value="http://repository.jboss.org/maven2"/> <target name="init-ivy"> <available property="ivy.installed" value="true" file="${ivy.jar.file}" type="file"/> </target> <target name="download-ivy" depends="init-ivy" unless="ivy.installed"> <mkdir dir="${ivy.jar.dir}"/> <echo message="Installing Ivy..."/> <get src="${central.repo}/org/apache/ivy/ivy/${ivy.install.version}/ivy-${ivy.install.version}.jar" dest="${ivy.jar.file}"/> </target> <project>
注意 我使用的是Ivy 2.0.0-beta2,因为2.0.0-rc2(撰写本文时的最新版本)中存在一个错误。
在您可以使用Ivy之前,您需要配置Ivy配置文件。我们将首先查看的主要配置文件是主设置文件,它包含对工件解析器的定义。
链接一组解析器
Ivy通过从Maven 2仓库中获取资源来工作。然而,与Maven不同,它可以容纳任何托管结构。因此,我们需要告诉Ivy在哪里查找工件(即依赖项),以及它应该使用什么模式在远程仓库和本地缓存中定位工件。这种配置定义在一个名为ivy.settings.xml的Ivy设置文件中。我们将定义三个解析器:本地、中央和jboss,我们将它们串联成一个解析器链,并将其设置为默认策略。
<?xml version="1.0" encoding="UTF-8"?> <ivysettings> <settings defaultResolver="default"/> <caches artifactPattern="[organisation]/[module]/[type]s/[artifact]-[revision](-[classifier]).[ext]" checkUpToDate="true"/> <resolvers> <filesystem name="local"> <ivy pattern="${ivy.cache.dir}/[module]/ivy-[revision].xml"/> <artifact pattern="${ivy.cache.dir}/[module]/[artifact]-[revision](-[classifier]).[ext]"/> </filesystem> <ibiblio name="central" m2compatible="true" usepoms="false" root="${central.repo}" pattern="[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]"/> <ibiblio name="jboss" m2compatible="true" usepoms="false" root="${jboss.repo}" pattern="[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]"/> <chain name="default" returnFirst="true"> <resolver ref="local"/> <resolver ref="central"/> <resolver ref="jboss"/> </chain> </resolvers> </ivysettings>
如我之前提到的,Ivy可以从几乎任何仓库中获取JAR文件,但这同时也意味着您必须告诉它正在处理哪种类型的仓库。Ivy设置文件中的<ibiblio>标签应用了一个内置模式,允许Ivy将仓库视为标准Maven 2仓库。然而,该模式中存在一个错误,使其无法处理非二进制工件,因此我们必须显式定义该模式。
您将在此XML配置中注意到两种不同风格的占位符变量。第一种是常规的Ant属性引用。您可以使用在调用Ivy任务之前定义的任何属性,或者在Ivy中定义的任何隐式属性(这些属性没有很好地记录)。如您所见,我们正在使用内置的ivy.cache.dir属性引用来定义JAR文件在本地缓存的存储位置,这恰好解析为${user.home}/.ivy2/cache。
第二种类型的占位符变量是方括号锚定的特殊Ivy替换令牌,它们解析为工件的各个部分。以下表格显示了这些令牌如何映射到Maven 2术语
Ivy | Maven 2 |
---|---|
organisation | group ID |
module, artifact | artifact ID |
revision | version |
ext | type |
classifier | classifier |
分类器花费了我很长时间才发现,并且需要特殊的设置才能在Ivy中使用。稍后我会详细介绍。
一旦Ivy解析了一个依赖项,它就会将工件复制到本地缓存目录中,类似于Maven 2的工作方式。然而,您可以在配置级别上对Ivy处理更新的方式有更多的控制。您还可以通过调整<filesystem>节点中的模式,对缓存进行项目分段。这允许您防止无关项目之间相互干扰。在此基础上,Ivy缓存比Maven 2本地仓库更容易管理,因为它不会用其自己的插件污染缓存。
配置就绪后,Ivy将搜索本地缓存,然后是中央仓库,最后是JBoss仓库以定位工件。现在的问题是,您如何定义Ivy需要获取的内容?这就是Ivy模块文件的作用所在。
向Ivy介绍你的依赖关系
Ivy模块文件,通常命名为ivy.xml(尽管可以通过使用ivy.dep.file配置属性来更改其位置),类似于Maven 2 POM文件的依赖项部分。这是你定义项目所依赖的库的地方。幸运的是,Ivy团队确实相信XML属性,与Maven 2的激进分子不同,因此你定义依赖项时需要输入的文本要少得多。但Ivy最绝对的关键功能,以及它比Maven 2更有用的事实,是你可以禁用传递依赖。
我对传递依赖的看法很明确。我认为它是邪恶的,并且是一个为新手(以及闲得无聊的人)设计的愚蠢工具。这使得你的构建不可重复和不稳定,最终让你比试图通过转向Maven 2来消除的工作还要多。这个所谓的“功能”确实是Maven的沉重负担。请相信我,定义你的应用程序所依赖的库非常简单。实际上并不多!而且你还可以放下关于排除的担忧。好吧,抱怨够了,现在是时候回到手头的任务了。我可以整天讨论这个话题!
下面是一个针对纯seam-gen 2.0.3.CR1 WAR项目的Ivy模块文件,其中包含一些额外的功能(目前我已删除了Drools的依赖项)。尽管Ivy具有对依赖范围(例如,编译、运行时、测试)的认识,但我们不关心这个功能,因为我们只是想将JAR文件填充到项目lib目录中,以便使用Ant构建。
<?xml version="1.0" encoding="UTF-8"?> <ivy-module version="1.0"> <info organisation="org.example" module="myproject"/> <configurations> <conf name="default" transitive="false"/> </configurations> <dependencies defaultconf="default"> <dependency org="com.sun.facelets" name="jsf-facelets" rev="1.1.15.B1"/> <dependency org="commons-beanutils" name="commons-beanutils" rev="1.7.0"/> <dependency org="commons-digester" name="commons-digester" rev="1.7"/> <dependency org="javax.el" name="el-api" rev="1.0"/> <dependency org="javax.faces" name="jsf-api" rev="1.2_04-p02"/> <dependency org="javax.faces" name="jsf-impl" rev="1.2_04-p02"/> <dependency org="javax.persistence" name="persistence-api" rev="1.0"/> <dependency org="javax.servlet" name="servlet-api" rev="2.5"/> <dependency org="javax.transaction" name="jta" rev="1.0.1B"/> <dependency org="org.codehaus.groovy" name="groovy-all" rev="1.5.4"/> <dependency org="org.hibernate" name="hibernate-validator" rev="3.0.0.GA"/> <dependency org="org.jboss.el" name="jboss-el" rev="1.0_02.CR2"/> <dependency org="org.jboss.seam" name="jboss-seam" rev="2.0.3.CR1"/> <dependency org="org.jboss.seam" name="jboss-seam-debug" rev="2.0.3.CR1"/> <dependency org="org.jboss.seam" name="jboss-seam-ioc" rev="2.0.3.CR1"/> <dependency org="org.jboss.seam" name="jboss-seam-mail" rev="2.0.3.CR1"/> <dependency org="org.jboss.seam" name="jboss-seam-pdf" rev="2.0.3.CR1"/> <dependency org="org.jboss.seam" name="jboss-seam-remoting" rev="2.0.3.CR1"/> <dependency org="org.jboss.seam" name="jboss-seam-ui" rev="2.0.3.CR1"/> <dependency org="org.jbpm" name="jbpm-jpdl" rev="3.2.2"/> <dependency org="org.richfaces.framework" name="richfaces-api" rev="3.2.2.GA"/> <dependency org="org.richfaces.framework" name="richfaces-impl" rev="3.2.2.GA"/> <dependency org="org.richfaces.ui" name="richfaces-ui" rev="3.2.2.GA"/> <dependency org="org.testng" name="testng" rev="5.6"/> </dependencies> </ivy-module>
你可能想知道是否可以消除Seam、JSF和RichFaces库(以及其他可能)的版本号重复。我有个好消息要告诉你。Ivy模块文件理解Ant属性引用。因此,你可以在ivy.build.xml文件中声明这些版本,从而有一个中央位置来控制它们。
<property name="seam.version" value="2.0.3.CR1"/> <property name="jsf.version" value="1.2_04-p02"/> <property name="richfaces.version" value="3.2.2.GA"/>
你现在可以更新你的Ivy模块文件,使用这些属性引用。以下是现在使用Ant属性引用来管理版本的主要Seam工件
<dependency org="org.jboss.seam" name="jboss-seam" rev="${seam.version}"/>
现在我们只需要告诉Ivy获取依赖项并将它们复制到lib目录。为此,我们切换回与Ivy相关的目标的Ant构建文件。
填充项目
我们将创建一个任务来对膨胀
lib文件中Ivy模块文件中定义的工件。这个任务需要依赖于一个加载Ivy Ant任务的任务,而这个任务反过来又需要依赖于一个获取Ivy的任务。以下是这些目标
<target name="load-ivy" depends="init-ivy,download-ivy"> <path id="ivy.lib.path"> <fileset dir="${ivy.jar.dir}" includes="*.jar"/> </path> <taskdef resource="org/apache/ivy/ant/antlib.xml" uri="antlib:org.apache.ivy.ant" classpathref="ivy.lib.path"/> <ivy:settings file="${basedir}/ivy.settings.xml"/> </target> <target name="inflate-core" depends="load-ivy"> <ivy:retrieve pattern="${lib.dir}/[artifact].[ext]" type="jar"/> </target>
Theload-ivy目标首先使用<taskdef>元素定义了Ivy提供的Ant任务。Ivy任务在ivy命名空间下可用。最后,<ivy:settings>任务启动了Ivy配置。在inflate-core目标中,<ivy:retrieve>任务解析了从一系列存储库中链式依赖的工件,并将它们复制到项目lib目录中(lib.dir属性在主Ant构建文件中定义)。
再次强调,我们使用了Ant属性引用和Ivy替换令牌来定义JAR文件最终将去哪里。我们通过将编译所需的JAR文件放入lib目录并去除版本号来复制标准seam-gen设置。当然,你可以通过引用[revision]在模式中的令牌,但随后您必须更新 deployed-jars.war 文件以确保构建包含可部署存档中的工件。以下是运行任务时的输出片段inflate-core。
Buildfile: build.xml init-ivy: download-ivy: load-ivy: [ivy:settings] :: Ivy 2.0.0-beta2 - 20080225093827 :: https://ant.apache.ac.cn/ivy/ :: [ivy:settings] :: loading settings :: file = /home/dallen/projects/myproject/ivy.settings.xml inflate-core: [ivy:retrieve] :: resolving dependencies :: org.example#myproject;working@sandstone [ivy:retrieve] confs: [default] [ivy:retrieve] found com.sun.facelets#jsf-facelets;1.1.15.B1 in jboss [ivy:retrieve] found commons-beanutils#commons-beanutils;1.7.0 in central ... [ivy:retrieve] :: resolution report :: resolve 1622ms :: artifacts dl 121ms --------------------------------------------------------------------- | | modules || artifacts | | conf | number| search|dwnlded|evicted|| number|dwnlded| --------------------------------------------------------------------- | default | 24 | 0 | 0 | 0 || 24 | 0 | --------------------------------------------------------------------- [ivy:retrieve] :: retrieving :: org.example#myproject [ivy:retrieve] confs: [default] [ivy:retrieve] 0 artifacts copied, 24 already retrieved (0kB/34ms) BUILD SUCCESSFUL Total time: 3 seconds
到目前为止,一切顺利,但我们目前缺少引入 Ivy 之前存在的功能。首先,我们需要 JBoss 内嵌 JAR 文件及其依赖项才能运行测试。项目构建期望这些工件位于 lib/test 目录中。此外,seam-gen 将 Seam 的源工件包含在 lib/src 目录中,因此我们还需要从存储库中下载这些工件(以及您想抓取的任何其他源)。问题是,这些工件放在哪里?
获取辅助工件
是时候扩展我们的依赖列表,详细说明不同类型的依赖了。我们刚刚确定了三种类型
- jar
- 源
- 测试-jar
然而,<dependency>元素本身无法进行这些区分。这就是嵌套<artifact>元素的目的。此元素允许我们为依赖定义一个或多个类型。此外,我们还可以接受一些额外的属性。其中最重要的是classifier.
Theclassifier属性与之前我提到的一个讨论相联系。在 Maven 2 存储库中,源文件通过在文件名末尾附加后缀 -sources 来命名,但在文件扩展名之前。问题是这引入了模式中的新段。(如果 Maven 2 存储库中的二进制工件使用后缀 -binary,那就太好了,但这只是美好的愿望)。
幸运的是,Ivy 理解可选段的概念。(我真心希望这个技巧能为您节省时间,因为追踪它花费了我大部分一天的时间)。可选段在 Ivy 模式中通过括号包围来定义。如果令牌没有值,括号之间的任何文本都将被忽略。回到之前,您可能会记得在 ivy.settings.xml 中使用以下模式
[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]
如您所见,如果定义了分类器,它将被附加到 URL 上。我们现在需要定义给定依赖项的分类器是什么。我们将展开 Ivy 模块文件中的<dependency>元素,并插入一个或多个<artifact>元素,其中之一将包含classifier属性。这里再次是主要 Seam 工件的依赖声明,现在配对了其源
<dependency org="org.jboss.seam" name="jboss-seam" rev="${seam.version}"> <artifact name="jboss-seam" type="jar"/> <artifact name="jboss-seam" type="source" ext="jar" m:classifier="sources"/> </dependency>
如您所见,classifier属性在其自己的命名空间中定义。为了防止 Ivy 抱怨使用无效的 XML 语法,您需要在 Ivy 模块文件的根元素中声明此命名空间
<ivy-module version="1.0" xmlns:m="https://ant.apache.ac.cn/ivy/maven"> ... </ivy-module>
当 Ivy 解析依赖项时,它会获取所有类型的工件。但类型判别器允许我们根据类型将工件复制到不同的位置。我们将在 Ant 构建中引入一个新的目标,将源工件复制到 lib/src,使用文件名中的类型与 seam-gen 铺设的项目文件保持一致
<target name="inflate-source" depends="load-ivy"> <ivy:retrieve pattern="${lib.dir}/src/[artifact]-[type]s.[ext]" type="source"/> </target>
我们剩下的只是抓取运行时测试 JAR 文件。我必须承认,使用 Ivy 依赖配置可能有一种更优雅的方法来完成此任务,但我无法理解该功能,而我即将提出的这种方法非常完美(有时我们会忘记,有用的东西比应该有用的事物更好)。以下是那些测试 JAR 文件的依赖定义
<dependency org="org.jboss.seam.embedded" name="hibernate-all" rev="${jboss-embedded.version}"> <artifact name="hibernate-all" type="test-jar" ext="jar"/> </dependency> <dependency org="org.jboss.seam.embedded" name="thirdparty-all" rev="${jboss-embedded.version}"> <artifact name="thirdparty-all" type="test-jar" ext="jar"/> </dependency> <dependency org="org.jboss.seam.embedded" name="jboss-embedded-all" rev="${jboss-embedded.version}"> <artifact name="jboss-embedded-all" type="test-jar" ext="jar"/> </dependency>
最后,这里是有检索测试 JAR 文件的 Ant 目标,以及另一个 Ant 目标,用于在单个命令中检索所有三种类型
<target name="inflate-test" depends="load-ivy"> <ivy:retrieve pattern="${lib.dir}/test/[artifact].[ext]" type="test-jar"/> </target> <target name="inflate" depends="inflate-core,inflate-source,inflate-test"/>
虽然这里没有显示,您可能还想创建一个目标来清除 lib 文件夹,以及一个目标使用<ivy:report>任务生成依赖项报告。
一旦您按照本文中描述的配置完成所有Ivy配置,您可以清除lib目录,并通过以下两个步骤构建项目:
ant inflate ant explode
您现在已启用Ivy,您的lib目录不再拥挤。
总结
我们旨在解决seam-gen项目中lib目录的混乱状态,并建立一个依赖关系管理系统。拥有依赖关系管理解决方案可以轻松添加新库并升级现有库。本文采用的方法是使用Ivy定义依赖关系,并从远程仓库检索它们,而无需以任何方式更改项目构建(除了导入与Ivy相关的构建文件)。我有意不尝试将Ivy集成到构建过程中,因为在开发应用程序时,您根本不需要不断检索工件。相反,您需要的是一种设置后无需再关心
的方法。这种策略的副作用是使您的构建可重复且稳定。
如果我写另一篇文章,下一步是删除deployed-jars.list文件,并找出如何从Ivy模块文件中提取此信息。但这需要我更好地理解Ivy依赖关系配置。
至于您可能想知道的,是的,我正在考虑将Ivy支持集成到seam-gen核心中。对我来说这似乎是理所当然的。我对Maven 2仍抱有希望,但到目前为止,我发现使用Ivy配置可以获得更高的生产率。
获取完整的Ivy配置和构建文件[1]
更新:抓取更新后的Ivy配置和构建文件[1],以获取更简洁的ivy.settings.xml文件版本。