Using Maven in J2EE Projects (在J2EE项目中使用Maven)
译注:由于原文的source code是基于maven 1.0编写的,部分代码存在着一定的问题。此外,本文的部分内容所提到的问题在最新的maven 1.02中已经不存在了。
In the previous section you saw how to use Maven to set up a project template, compile source files, create a jar and publish the artifact into the repository. There is much more to Maven than that. We will cover inheritance in Maven POM, setting up a Sample J2EE project, creating WARs, EJB-JARs, dependency jars and ultimately creating EARs. However let's first look at another important concept in Maven.
从前文中,你已经学会了如何使用Maven使用项目模板、编译源文件、创建jar包、将制品发布到仓库中,Maven的基本功能都在这里了。下面我们将会介绍Maven POM的基础、设置J2EE 演示项目、创建WAR、EJB-JAR、依赖包、以及最终的EAR。但在这之前,我们先看一下Maven中的另一个重要概念。
In Maven, a project can produce only one artifact. The top-level project can have multiple sub projects and they in turn can have siblings too. But at the end, each of them can only produce one artifact. This makes a lot of sense if you consider how the Ant scripts are used to create multiple artifacts from a project - which gets confusing over time - to say the least. Typically, projects have a monolithic (or maybe a couple) source tree(s), from which all the dependency jars, WARs and EARs are created using different build scripts. At the end, the message of application partitioning is lost and classes are created in different packages without consideration to the classloader mechanism, making it even harder to keep build scripts up to date. Maven discourages this practice at the outset. Of course Maven gives you flexibility to override this practice. There are ways to produce multiple artifacts but that goes against the very spirit of Maven and has to be avoided unless absolutely essential.
在Maven中,一个项目只能生成一个制品。最顶层的项目可以含有多个子项目、也就是说,每个项目可以有兄弟项目。但无论如何,每个项目只能有一个制品。将Maven和Ant比较,“Ant可以为一个项目创建多个制品”,这确实是个需要花一段时间才能想通的问题。在典型情况下,每个项目都拥有一个(或多个)源代码树,从中可以build出各种jar、WAR、EAR。最后,项目中的所有分割信息都被隐藏了起来,根本没有考虑classloader机制,直接编译各包的class,很难保持构建脚本及时更新。Maven则不鼓励这样的工作方式,当然Maven灵活的提供了其他的方式实现此功能。在Maven中,你可以在一个项目中创建多个制品,但这本身就违反了Maven的精神,除非必要,我们都应该尽量去避免它。
Customizing Maven with maven.xml (使用maven.xml对Maven进行定制)
Until now you saw how to compile java classes in a maven project and create jar files out of it. What if you wanted to customize Maven behavior? You can create a file called maven.xml in the same folder where project.xml resides. In this file, you can customize goals, define and pre and post goals.
到现在为止,你已经知道在Maven项目中如何编译java文件并将其打包,那么如何定制Maven的行为呢?你可以在Maven的项目中,创建一个maven.xml文件,其位置和project.xml相同。在此文件中,你可以定制goal,定义pre、post goal。
Listing 6 shows a sample maven.xml. We are achieving quite a bit in this file. Look at the default goal for the project. The default goal is set to foobar-dist. A goal is equivalent of an Ant target. The default goal is the same as the default target in Ant. What this means is that the default goal is executed when you go to the directory containing this file and simply type maven. The foobar-dist goal is simply a wrapper on top of another goal called war:install. The "install" goal in the war plugin creates a war and publishes it into the repository. The "attainGoal" instructs maven to achieve the goal described by the name attribute.
表6 中给出了maven.xml的例子,从中我们可以学到很多东西。请看其中定义的该项目的默认goal,它被设为了foobar-dist。Maven中的goal和Ant中的target是相同的概念,Maven中的默认goal和Ant中的默认target相同。Maven的默认goal意味着:在该项目的目录下,直接敲入“maven”命令,这时将会执行默认goal。这里例子中的foobar-dist是对“war:install”的封装。在插件war中的“install”goal的作用是创建一个war,并将其发布到仓库中。“attainGoal”则是告诉Maven去执行“name”属性所描述的任务。
The preGoal element instructs Maven to execute the defined tasks in the preGoal before achieving the goal specified in its name attribute. This can get confusing at times. Note that the preGoal name (java:compile) is different from the default goal (foobar-dist). The default goal for this project installs a war file. However the war plugin has to compile the java files in the project before creating the actual WAR archive. The preGoal specified is linked to this java:compile goal rather than the foobar-dist goal. In this case, the pre goal executes a Xdoclet maven plug in. We will use Xdoclet Maven plugin later in this article to generate tlds (tag library definitions) from the actual tag classes.
PreGoal元素则告诉Maven在执行goal之前,执行在其“name”属性中所描述的任务。有时候这也许会让你犯糊涂,注意这里的preGoal 的name(java:compile)与默认goal(foobar-dist)的name是不同的。本项目中的默认goal是安装一个war文件,但是在正式创建WAR文件之前,必须首先要编译java文件,(译者注:但是在编译java之前,通过pregoal的定义,在java:compile调用之前,添加了新的调用xdoclet:webdoclet)。这里preGoal链接指向java:compile,而不是foobar-dist, 在本例中,pre goal将会执行Xdoclet插件的xdoclet:webdoclet。在这篇文章的后面,我们还会使用Xdoclet插件,从tag class中生成tld(tag library definitions)文件。(译者注:表6中的preGoal调用了xdoclet:webdoclet,而此xdoclet插件可能是Maven没有预先安装的,需要执行“maven plugin:download -DartifactId=maven-xdoclet-plugin -DgroupId=xdoclet -Dversion=1.2”进行下载。此外,由于xdoclet目前的并不是在一个jar文件中的,所以还需要在project.xml中加载一些其他jar文件。)
A postGoal can be specified for any of the implicit goals achieved as part of the default goal or for the default goal itself. In Listing 6, a postGoal is specified for the war:war goal, which gets executed just before war:install goal. The postGoal specifies that a certain directory have to be created after the WAR is built. Notice the use of standard ant tasks within the postGoal. In fact any of the ant tasks can be embedded within the goal, preGoal or postGoal. This is what makes Maven flexible. It is also very easy to abuse this flexibility by compiling and creating multiple artifacts using Ant tasks within Maven. In other words, you would have written Ant scripts under the hoods of Maven. That practice is discouraged and should be used if there is no other option. If you find yourself repeating the steps of putting ant tasks into Maven goals, what you need is a custom plugin.
同样的是,postGoal 可以做为任何goal的一部分,在表6中,postGoal则是专门为“war:war”定义的,这将在war:install之前被执行。(译者注:因为表6中,为war:war定义了添加了postGoal扩展,而war:war将由war:install调用,因此文中说“这将在war:install之前被执行”。读者不妨可以将postGoal这一段注释掉执行以观察执行结果,以进行比较。)在本例中,postGoal的定义是:在创建了WAR之后,创建一个目录。注意,postGoal中使用了Ant中的task,事实上,Maven中可以嵌入任何Ant的task,无论是在goal, pregoal, postgoal中。它给Maven带来了极大的灵活性,但是也很容易带来Maven的误用-“使用Ant的task进行代码编译、创建制品”,也就是说,你只是在Maven的帽子下面仍然依从Ant的方法编写Ant脚本,如果有可能,请别使用这种方式完成任务。如果你发现你不断的往Maven种添加Ant的task,那么你就该停下来去找一个适合的Maven插件。
Listing 6 Sample maven.xml
<project default="foobar-dist" xmlns:m="jelly:maven">
<preGoal name="java:compile">
<attainGoal name="xdoclet:webdoclet"/>
</preGoal>
<goal name="foobar-dist">
<attainGoal name="war:install" />
</goal>
<postGoal name="war:war">
<mkdir dir="${test.result.dir}"/>
<echo>Creating directories</echo>
</postGoal>
</project>
A custom plugin is written in a file called plugin.jelly using the Jelly scripting language and bundled along with project.xml and other files into a jar and copied into the C:/Documents And Settings/<login-id>/.maven/plugins directory. This plugin is then available to all of your processes using Maven. We will customize the war plugin later in this article since it lacks some of the features we need.
Maven的插件存放于C:/Documents And Settings/<login-id>/.maven/plugins目录中,每个插件是一个jar文件,其中包括:使用Jelly脚本语言编写的plugin.jell文件、project.xml 以及其他文件。因为war插件不能满足一些我们的要求,后面我们将对war插件进行定制(译者注:由于本篇文章写于2003,它所使用的是Maven 1.0,其问题已经解决)。
Figure 3 Inter-Project relationships for Foobar Travels
图3 Foobar Travels中的内部项目关系图
Let us now jump to applying Maven to J2EE projects. The most common J2EE artifact generated and deployed is the EAR. The EAR itself consists of dependency jars, ejb jars, wars and third party libraries. In other words, EAR is an aggregate artifact. For Foobar Travels, we will use the same project template as shown in Figure 2. Figure 3 shows the inter-project relationship and hierarchy. The master project template is defined at the top project level. A simplified master template is shown in Listing 7 only with the essential elements. A real one will have most of the items described in the section Maven basics. The key thing to note is that the dependencies are not declared in this template. This is because the master project.xml is just that - a template for other sub projects to extend. The only thing we have not touched upon from Listing 7 is the ${pom.artifactId}. I will explain this shortly in the context of extending the template. Figure 4 shows the structure of the EAR file generated by this project. You will find that the EAR content organization closely resembles the Maven project shown in Figure 3. In most of the J2EE project you would not deviate from a deployment artifact as in Figure 4. And when you stick to the usual route, Maven "out-of-the-box" build scripts work as is.
现在让我开始将Maven使用到J2EE项目中。J2EE项目中最经常生成发布的制品是EAR。EAR制品中包含的内容有:各种依赖包、EJB JAR、WAR以及第三方库文件,EAR是一个聚合包。对于Foobar Travels,我们将使用图2中的项目模板。图3给出了其内部项目的依赖关系。项目的主模板将在项目的最顶层进行定义,表7中只给出了主模板中的关键元素信息。在Maven basics部分我们已经给出了实际中的绝大部分元素的信息。需要注意的是:在此模板中,并没用给出依赖关系,这是因为主模板project.xml只是一个给子项目进行扩展的模板。在表7中我们唯一以前未曾接触到的是${pom.artifactId},这将在讲述如何扩展模板的时候对它进行解释。图4给出了本项目所生成的EAR文件的结构,你会发现这里EAR的内容和图3中的Maven项目的内容紧紧的结合在一起。在绝大部分J2EE项目中,最后所部署的制品基本都和图4所显示项目的内容基本一致。如果你采用常规方法使用Maven的,它“out-of-the-box”构造脚本就可以如上使用了(译者注:“out-of-the-box”是以前的一个开源项目,参考http://www.javaworld.com/javaworld/jw-04-2003/jw-0404-outofbox.html )。
Figure 4 The EAR structure for Foobar Travels
图4 FooBar Travels的EAR结构图
Listing 7 Master Project Definition Template
表7主项目定义模板
<project>
<pomVersion>3</pomVersion>
<id>foobar-online</id>
<groupId>Foobar-Travels</groupId>
<currentVersion>2.0</currentVersion>
<name>Foobar Online Project</name>
<organization>
<name>Foobar Travels</name>
</organization>
<package>foobar.*</package>
<distributionDirectory>
/foobar/dist/${pom.artifactId}/
</distributionDirectory>
<dependencies/>
<build>
<sourceDirectory>${basedir}/src/java</sourceDirectory>
<resources/>
</build>
</project>
To summarize Figure 4, we have to build the following artifacts from the project shown in Figure 3.
- foobar-services-2.0.jar - The dependency jar
- reservationejb-2.0.jar - The Reservation EJB and create its Manifest file with Manifest Class-path pointing to the services jar file.
- personejb-2.0.jar - The Person EJB and create its Manifest file with Manifest Class-path pointing to the services jar file.
- foobar-web-2.0.jar - The Web application for Foobar Travels and create its Manifest file with Manifest Class-path pointing to the personejb, reservationejb and service jars
- application.xml - the J2EE application deployment descriptor.
- Build the EAR by including all of the above artifacts and also including the castor jar file since the services jar depends on it at runtime.
总结图4,我们将从图3 的项目中创建出以下的制品:
1、 foobar-services-2.0.jar -其所依赖的包
2、 reservationejb-2.0.jar -Reservation EJB,需要创建Manifest文件,其中声明class-path,指向services jar文件。
3、 personejb-2.0.jar-Person EJB,需要创建Manifest文件,其中声明class-path,指向services jar文件。
4、 foobar-web-2.0.jar-Foobar Travels 的Web程序,需要创建Manifest文件,其中声明class-path,指向personejb、reservationejb、services jar文件。
5、 application.xml -J2EE应用的部署描述符。
6、 创建EAR,其中包含上面的所有的制品、另外还需要包含castor.jar,因为service jar会在运行的时候使用它。
Building the dependency jars (创建依赖包)
Listing 8 shows the project definition for Services - a subproject in Maven. This subproject creates the foobar-services-2.0.jar file - a dependency library used by both ejbs and the web tier. Note that the definition extends from the master template in Listing 7. By extending from the parent, it inherits all the properties. It defines the dependency elements to indicate the dependencies on the J2EE APIs (not defined in the parent) and overrides the id, name and description defined in the parent.
表8中给出了子项目Services的定义,该项目将会创建foobar-services-2.0.jar,在EJB和Web程序中都会使用到该文件。注意,这里的定义侍从表7中的模板扩展而来的。通过扩展父模板,它继承了所有的属性。其中它定义了它将会依赖于J2EE API(这并没有在父模板中定义),并且覆盖(override)了父模板中的id, name and description。
Listing 8 Services Sub-Project Definition
表8 子项目Service的定义
<project>
<extend>${basedir}/../project.xml</extend>
<id>foobar-services</id>
<name>Foobar Services Framework</name>
<package>foobar.service.*</package>
<description>Services JAR project.</description>
<dependencies>
<dependency>
<groupId>j2ee</groupId>
<artifactId>j2ee</artifactId>
<version>1.3.1</version>
</dependency>
</dependencies>
</project>
Now let us get back to the ${pom.artifactId} mentioned earlier (Listing 7). This lets you introduce polymorphism into Maven. At runtime, the value of ${pom.artifactId} for the Services sub-project is its id - foobar-services. Hence the value of the distribution directory for the Services project is /foobar/dist/foobar-services. In other words the pom can capture the variables and abstract them in a standard manner and establish a procedural approach instead of the chaotic one.
现在让我讲解前面提到的${pom.artifactId}(表7中),它允许你将多态(polymorphism)引入到Maven中。在运行的时候, ${pom.artifactId}的具体值是子项目Services的id:foobar-services.因此Services项目的distributionDirectory值是:/foobar/dist/foobar-services.换句话说,POM能够捕捉变量的值,并且在运行的时候通过一种标准方式,及时用确切值替换原变量。
Now let us introduce you to the maven.xml for the Services Project. Technically speaking, maven.xml is not needed since you can always go to this sub project and execute maven jar:install. However as we will see later, having a maven.xml will help us eliminate manual execution of Maven from every sub-project. The maven.xml for the Services project looks as follows.
下面介绍Services项目中的maven.xml。从技术角度来看,maven.xml并不是必需的,你可以到该子项目的目录下执行命令“jar:install”。但是我们后面就可以看到,拥有这样的一个maven.xml文件将会极大的帮助我们,不再需要人工到每个子目录下面执行maven命令。Services子项目的maven.xml文件内容如下:
<project>
<extend>${basedir}/../project.xml</extend>
<id>foobar-services</id>
<name>Foobar Services Framework</name>
<package>foobar.service.*</package>
<description>Services JAR project.</description>
<dependencies>
<dependency>
<groupId>j2ee</groupId>
<artifactId>j2ee</artifactId>
<version>1.3.1</version>
</dependency>
</dependencies>
</project>
Building the EJB jars (构建EJB Jar)
After having built the dependency jars let us turn our focus to building the ejb jars. This is no different, since there is already a ejb plugin and you can use the ejb:install goal to create and publish the ejb jars. However we would also like to remind you that the ejb jar (loaded by the EJB class loader) depends on the Services built in the previous step (See Figure 4). What this means for you is that the ejb manifest classpath should point to the foobar-services-2.0 jar. Listing 9 shows the project.xml for the Reservation EJB jar. Other ejb project definitions are similar.
在创建了依赖包后,我们开始创建ejb jar。毫无区别的是,Maven中已经有了ejb插件 ,你可以使用ejb:install创建和发布ejb jar。但是我必须提醒你,ejb jar(由EJB class loader载入)依赖于前一步图4中刚创建的Services包。也就是说,你必须在ejb manifest中声明classpath,将它指向foobar-services-2.0 jar。表9给出了Reservation EJB jar的project.xml。其他EJB项目的定义与此类似。
NOTE: In the downloadable example supplied with this article, no EJBs are included. The reason is to have even non-ejb developers using Tomcat to see Maven in action. However this section describes what it takes to build EJBs with Maven.
注意:在供下载的例子中,并没有包含EJB程序。因为保证非EJB开发者可以在Tomcat中使用,了解Maven的使用方法。但是本段所讲的内容则是如何使用Maven构建EJB。 |
As usual the ejb project depends on the J2EE jars. However since it also depends on the foobar-services-2.0.jar (loaded by the EAR class loader), it has to add that jar file name to its manifest classpath. You can achieve this by setting the property ejb.manifest.classpath to be true. The Maven variable, pom.currentVersion stands for the current version of the project. In our case, it is 2.0. This setting indicates that the version of services project on which the ejb project depends is the same as the current version of the ejb project. This version is set on the parent project template. For future releases, the version number need not be changed for every dependency in every project. Just change it in the Master project template and it takes effect everywhere. Ah, the beauty of inheritance!
和平常一样,EJB项目会依赖J2EE包,但是他还依赖foobar-services-2.0.jar(由EAR Class loader载入)。因此不得不将此jar的文件名机阿然到manifest的classpath中。你可以将ejb.manifest.classpath的值设为true。pom.currentVersion代表项目的当前版本,在我们的例子中是2.0。这里的设置意味着EJB项目和其所依赖的Services项目两者的版本号是一样的。这里的版本号被设置为父项目模板中的版本。在将来发布的时候,则无需一一修改各个子项目的版本号,直接修改父项目的模板即可。这就是继承的作用。
Listing 9 Reservation EJB Project Definition 表9 Reservation EJB项目的定义
<project>
<extend>${basedir}/../project.xml</extend>
<id>reservationejb</id>
<name>Foobar Reservation Components</name>
<package>foobar.reservation.*</package>
<description>Reservation Components project</description>
<dependencies>
<dependency>
<groupId>j2ee</groupId>
<artifactId>j2ee</artifactId>
<version>1.3</version>
</dependency>
<dependency>
<groupId>Foobar-Travels</groupId>
<artifactId>foobar-services</artifactId>
<version>${pom.currentVersion}</version>
<properties>
<ejb.manifest.classpath>true</ejb.manifest.classpath>
</properties>
</dependency>
</dependencies>
</project>
The discussion of building the ejb jars is incomplete without mentioning the maven.xml file. This file is not straight forward as in the services project. Listing 10 shows the maven.xml for the Reservation EJB project. There are a few things happening here. First, the foobar-dist goal is used as a wrapper for attaining the goal ejb:install. One of the first goals to be achieved during the install is init, when the file system and other resources are initialized and then the ejb classes are compiled. The compiler expects to find the home, local and remote interfaces, failing which it will throw a compiler error. This is where XDoclet comes into play.
在讨论构建ejb jar的时候,必须要谈到其maven.xml。这里的maven.xml则不像Services项目中那样加纳队那了。表10给出了其定义,有以下几点内容:首先,foobar-dist goal封装了ejb:install;在“install”刚初始化的之前,文件系统和其他资源必须首先被初始化,随后编译ejb的class文件。编译器将尝试寻找home、local、interface,如果失败,则编译报错。这就是XDolet所搬演的角色。
Listing 10 maven.xml for the Reservation EJB Project
表10 Reservation EJB项目的maven.xml
<project default="foobar-dist" xmlns:m="jelly:maven"
xmlns:ant="jelly:ant">
<goal name="foobar-dist">
<attainGoal name="ejb:install" />
</goal>
<preGoal name="ejb:init">
<attainGoal name="xdoclet:ejbdoclet"/>
</preGoal>
<postGoal name="ejb:install">
<ant:property name="maven.ejb.install.dir"
value="${maven.repo.local}/${pom.artifactDirectory}/ejbs"/>
<ant:mkdir dir="${maven.ejb.install.dir}"/>
<ant:copy file="${maven.build.dir}/${maven.final.name}.jar"
tofile="${maven.ejb.install.dir}/../
jars/${maven.final.name}.jar"/>
</postGoal>
</project>
XDoclet is an open source project hosted on the SourceForge. By using EJB Doclet specific tags in the Bean class (the implementation class), you signal the XDoclet to generate the home, local and remote interfaces. You will also provide the jndi names, and other information using similar tags. On parsing the file, the XDoclet will also generate appropriate deployment descriptors. Now let us get back to see how it fits in here.
XDoclet是sourceforge中的一个开源项目,通过在Bean class文件(EJB 实现文件)中的EJB Doclet 描述tag,告诉Xdoclet产生home, local interface和remote interface。你还可以使用类似tag,写入jndi name以及其他信息。Xdoclet经过分析源文件之后,将会生成相应的部署描述符。现在让我们回头看它是怎么运行的。
XDoclet provides Maven plugins for generating the above-mentioned artifacts. By specifying the ejbdoclet as the preGoal for ejb:init, you will generate home, local and remote interfaces and xml deployment descriptors just in time for the compilation.
XDoclet为Maven提供了插件,可以生成上述的制品。通过将ejbdoclet设置为ejb:init的pregoal,它将会在编译期间帮你生成home, local interface, remote interface和部署描述符xml文件。
Now let us look at the postGoal for ejb:install. As you know, the ejb:install generates the ejb jar as the artifact and puts it in the repository in the location "C:/Documents And Settings/<login-id>/.maven/repository/Foobar-Online/jars". Suppose that you want the ejbs to be in a separate directory called ejbs instead of the jars, you have to do it after the ejb:install is finished with its business. That's why it is a postGoal. For those familiar with Ant, the postGoal is nothing but a collection of Ant tasks. Again, a couple of Maven specific properties are used. Don't try to remember all of the Maven defined properties at once. Over time, these will become second nature.
现在让我们看一下ejb:install的postGoal吧。正如你所知道的,ejb:install将会生成ejb jar制品,并将其发布到仓库C:/Documents And Settings/<login-id>/.maven/repository/Foobar-Online/jars目录。假设你想将ejb部署到名为“ejbs”的目录中,而不是目录“jars”,这必须在ejb:install完成之后才能做。这就是为何这里使用postGoal的原因。熟悉Ant的读者就能知道,这里的postGoal是Ant的task的集合,这里使用了Maven的许多特有属性。并没有必要一次性的记住Maven所定义的所有属性,过一段时间,这些就变成你的第二本能啦。
You will notice there are two distinct categories of properties - those with their name beginning with pom and the rest of them. The properties with their names beginning with pom are the individual elements in the project.xml. For instance, the currentVersion element defined in project.xml has a property with the name pom.currentVersion and so on. The project.xml is loaded to create the POM model by Maven. POM model attribute values are evaluated by using the $ sign before the attribute names. As more plugins are added new properties emerge. This is going to be a challenge. You may not find adequate documentation of these properties. The only way to find out is by opening the scripts for each of these plugins in plugin.jelly file. Luckily there aren't very many properties that you will have to know.
注意到这里有两类属性:名字以pom开头算第一类,其余的是第二类。第一类的属性是在project.xml中定义的元素。例如project.xml定义的currentVersion元素可以使用pom.currentversion进行访问,其他也是如此。Maven将会载入project.xml形成POM模型。可以在属性名前面加$开头访问其属性值。随着插件的增多,这确实会带来一定的问题。你根本无法找到这些属性的文档,唯一能做的就是打开每个插件的pligin.jelly,查看其内容。幸好,你并不需要了解太多的属性。
In your project, if you find yourself writing the same pregoals and postgoals for every ejb subproject for example, it is time to roll your own plugin.
在你的项目中,如果你为每个ejb都写了相同的pregoal、postgoal,那么你就该写自己的插件啦。
Building the WAR (构建war)
After the ejb jar, it is war time. We will accomplish quite a bit in this section. First we will introduce you to the project.xml. Then we will add a minor missing functionality into the war plugin. Then we will show you how to use XDoclet to generate tlds for a sample tag. Then we will end the section with maven.xml.
在创建了Ejb jar后,该创建war包了。我们将在后面对此进行讲述。首先我们介绍project.xml,然后将给war插件添加一些未曾有的功能(译注:在maven 1.02版本中,问题已经解决),接着将演示如何使用XDoclet生成为tag例子生成tld文件,最后介绍maven.xml。
Listing 11 shows the project.xml for the Foobar web application. The actual project definition has much more dependencies and is downloadable. Here we are showing only the relevant portions to illustrate the concepts. The important part is the dependencies section. The web application depends on the J2EE APIs provided by the container at runtime as well as compile time. However there is no need to bundle the J2EE API with the WAR since it is the servlet container's responsibility. In addition the web application depends on Struts 1.1 jar file. The property setting <war.bundle>true</war.bundle> indicates that struts.jar has to be bundled with the WAR. Whenever the war plugin is instructed to bundle the jars, it puts them under the WEB-INF/lib directory of the WAR. Compare this to manually copying the jars into the WEB-INF/lib directory in Ant. The WAR also depends on the Foobar Services jar file, foobar-services-2.0.jar. However we do not want to copy this jar into the WEB-INF/lib since it is a dependency library and shared by both the web tier and the ejbs. (Recall that any jars and classes residing in WEB-INF/lib and WEB-INF/classes are loaded by the WAR class loader.) Hence we set the war.bundle property as false for foobar-services jar file. You get all this functionality out of the box with Maven war plugin.
表11给出了Foobar Web程序的project.xml,实际的project.xml文件的内容要比这里演示的内容多许多。这里我们仅介绍相关部分,讲解其概念。最关键的部分就是depencies部分,该Web程序依赖于J2EE API,它们将由容器在编译器和运行时提供。因此无需在WAR中加入J2EE的包,这时Servlet容器的责任。另外,该程序依赖于Structs 1.1 jar文件,<war.bundle>true</war.bundle>表示这struts.jar将会打入到WAR包中。war插件将会把jar文件加入到WAR的WEB-INF/lib目录下,而Ant中则需要将jar文件手工拷贝到WEB-INF/lib目录。WAR还依赖于Foobar Services的foobar-services-2.0.jar文件,但是我们并不需要将它拷贝到WEB-INF/lib目录,因为该包是依赖包,EJB、WEB都需要使用此包(记住所有在WEB-INF/lib和WEB-INF/classes 的jar或class都是由WAR class loader所加载的) 。因此将foobar-services jar文件的war.bundle 属性设为false。所有的这一切都可以通过Maven的war插件完成。
Listing 11 maven.xml for the Web Project
<project>
<extend>${basedir}/../project.xml</extend>
<id>foobar-web</id>
<name>Foobar web application</name>
<package>foobar.webapp.*</package>
<description>Foobar Web project.</description>
<dependencies>
<dependency>
<groupId>j2ee</groupId>
<artifactId>j2ee</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>jakarta-struts</groupId>
<artifactId>jakarta-struts</artifactId>
<version>1.0.2</version>
<properties>
<war.bundle>true</war.bundle>
</properties>
</dependency>
<dependency>
<groupId>Foobar-Travels</groupId>
<artifactId>foobar-services</artifactId>
<version>${pom.currentVersion}</version>
<properties>
<war.bundle>false</war.bundle>
<war.manifest.classpath>true</war.manifest.classpath>
</properties>
</dependency>
</dependencies>
</project>
However the Maven war plugin lacks one feature. It does not have the capability to set the manifest classpath. Without this feature you cannot expect to use dependency libraries. Hence I decided to add this feature to the war plugin. Only two simple changes were required to achieve this. I decided to illustrate this change to show how intuitive and easy it is to write plugins in Jelly or customize the ones that already exist. Open the plugin.jelly for the war plugin from C:/Documents And Settings/<login-id>/.maven/plugins/maven-war-plugin-<version>-[SNAPSHOT]. These changes have to be done in the goal named war (The actual WAR - deployment archive, is built in a goal named as war in the war plugin). Add the following code in that goal.
但是目前的Maven的war插件还存在着一定的缺陷,它无法设置manifest的classpath(译者注:此功能已经在Maven 1.02中完成),你无法正常的使用依赖库。因此我决定给插件添加以下功能,只需要做两处改变即可。下面讲述如何使用Jelly简单的修改现有插件,或者自己写一个插件。在C:/Documents And Settings/<login-id>/.maven/plugins/maven-war-plugin-<version>-[SNAPSHOT]目录中打开war插件的plugin.helly,找到名为war的goal(事实上WAR发布包就是使用war插件中的名为war的构建的),添加下面的内容。
<j:forEach var="dep" items="${pom.dependencies}">
<j:if test="${dep.getProperty('war.manifest.classpath')=='true'}">
<j:set var="maven.war.classpath"
value="${maven.war.classpath} ${dep.artifact}"/>
</j:if>
</j:forEach>
This code iterates over each of the dependencies listed in the project.xml (identified by pom.dependencies) and checks if the war.manifest.classpath property is set to true. If so, then it appends that artifact name to a property called maven.war.classpath.
这里的代码将会遍历project.xml中的所有依赖包(由pom.dependencies可以辨别),然后检查war.manifest.classpath是否被设为true。如果真,则将此制品的名字加入到属性maven.war.classpath中。
The second change is needed when the manifest file is written out. The manifest file is created by the ant plugin by executing a goal named manifest. The manifest creation code is shown below. The line in bold is the line I inserted to set the manifest classpath attribute. This uses the previously set maven.war.classpath.
第二处改变在manifest文件生成的时候。在执行名为manifest的goal的时候,Maven将调用Ant的插件,创建生成manifest文件。Manifest的创建代码如下显示。黑体部分是我写的设置manifest的classpath属性的代码,它将使用到前面设置的maven.war.classpath属性。
<ant:manifest>
<ant:attribute name="Built-By" value="${user.name}" />
<ant:attribute name="Class-Path" value="${maven.war.classpath}"/>
<ant:section name="${pom.package}">
<ant:attribute name="Specification-Title"
value="${pom.artifactId}" />
<ant:attribute name="Specification-Version"
value="${pom.currentVersion}" />
<ant:attribute name="Specification-Vendor"
value="${pom.organization.name}" />
</ant:section>
</ant:manifest>
That was simple wasn't it! Hopefully this will inspire you to write your own plugins when needed, instead of repetitively adding tasks into preGoals and postGoals.
非常简单,不是么?也许这会激发你在必要的时候写自己的插件。而不是象先前那样不断的在任务中插入preGoal和postGoal。
Written by Srikanth Shenoy November 2003
Translated by jinfeng wang 2005年三月
原文地址:http://www.theserverside.com/articles/article.tss?l=MavenMagic
本文的翻译已经原作者同意,转载请保持原文。如有问题和意见可以和原作者或我联系。
Part 1: http://www.blogjava.net/jinfeng_wang/archive/2005/03/11/1956.html
Part 3: http://www.blogjava.net/jinfeng_wang/archive/2005/03/15/2089.html