也许你所在组织的一些项目都使用同样的框架和项目结构,为一个个项目重复同样的配置及同样的目录结构显然是难以让人接受的。更好的做法是创建一个属于自己的Archetype,这个Archetype包含了一些通用的POM配置、目录结构,甚至是Java类及资源文件,然后在创建项目的时候,就可以直接使用该Archetype,并提供一些基本参数,如groupId、artifactId、version,maven-archetype-plugin会处理其他原本需要手工处理的劳动。这样不仅节省了时间,也降低了错误配置发生的概率。
下面就介绍一个创建Archetype的样例。首先读者需要了解的是,一个典型的Archetype Maven项目主要包括如下几个部分:
·pom.xml:Archetype自身的POM。
·src/main/resources/archetype-resources/pom.xml:基于该Archetype生成的项目的POM原型。
·src/main/resources/META-INF/maven/archetype-metadata.xml:Archetype的描述符文件。
·src/main/resources/archetype-resources/**:其他需要包含在Archetype中的内容。
下面结合样例对上述内容一一详细解释。
首先,和任何其他Maven项目一样,Archetype项目自身也需要有一个POM。这个POM主要包含该Archetype的坐标信息,这样Maven才能定位并使用它。读者还要留意,不要混淆Archetype的坐标和使用该Archetype生成的项目的坐标。需要注意的是,虽然Archetype可以说是一种特殊的Maven项目,但maven-archetype-plugin并没有要求Archetype项目使用特殊的打包类型。因此,一般来说,Archetype的打包类型就是默认值jar。代码清单18-1展示了一个很简单的Archetype的POM。
代码清单18-1 样例Archetype的POM
接下来要关注的就是Archetype所包含的项目骨架的信息。从本质上来说,在编写Archetype的时候预先定义好其要包含的目录结构和文件,同时在必要的地方使用可配置的属性声明替代硬编码。例如,项目的坐标信息一般都是可配置的。代码清单18-2就是一个简单的POM原型,它位于Archetype项目资源目录下的archetype-resources/子目录中。
代码清单18-2 样例Archetype所包含的POM原型
上述代码片段中的groupId、artifactId和version等信息并没有直接声明,而是使用了属性声明。回顾18.1.2节,使用Archetype生成项目的时候,用户一般都需要提供groupId、artifactId、version、package等参数,在那个时候,这些属性声明就会由那些参数值填充。
上述POM原型中还包含了一个JUnit依赖声明和两个插件配置。事实上,我们可以根据自己的实际需要在这里提供任何合法的POM配置,在使用该Archetype生成项目的时候,这些配置就是现成的了。
一个Archetype最核心的部分是archetype-metadata.xml描述符文件,它位于Archetype项目资源目录的META-INF/maven/子目录下。它主要用来控制两件事情:一是声明哪些目录及文件应该包含在Archetype中;二是这个Archetype使用哪些属性参数。代码清单18-3展示了一个Archetype描述符文件。
代码清单18-3 样例Archetype描述符文件
该例中的Archetype描述符定义了名称为sample。它主要包含fileSets和requireProperties两个部分。其中,fileSets可以包含一个或者多个fileSet子元素,每个fileSet定义一个目录,以及与该目录相关的包含或排除规则。
上述代码片段中的第一个fileSet指向的目录是src/main/java,该目录对应于Archetype项目资源目录的archetype-resources/src/main/java/子目录。该fileSet有两个属性,filtered表示是否对该文件集合应用属性替换。例如,像${x}这样的内容是否替换为命令行输入的x参数的值;packaged表示是否将该目录下的内容放到生成项目的包路径下。18.1.2节提到使用Archetype必须提供的参数之一就是package,即项目包名。如果读者暂时无法理解这两个属性的作用,不必着急,稍后通过实例来解释。
该fileSet还包含了includes子元素,并且声明了一个值为**/*.java的include规则,表示包含src/main/java/中任意路径下的java文件。这里两个星号**表示匹配任意目录,一个星号*表示匹配除路径分隔符外的任意0个或者多个字符。这种匹配声明的方式在Maven的很多插件中都被用到,如10.5节中的maven-surefire-plugin。除了includes,用户还可以使用excludes声明要排除的文件。配置方法与includes类似,这里不再赘述。
为了能够说明问题,笔者在src/main/resources/archetype-resources/src/main/java/目录下创建了一些文件,假设使用该Archetype创建项目的时候,package参数的值为com.juvenxu.mvnbook。表18-1表示了Archetype中文件与生成项目文件的对应关系。
表18-1 Archetype资源文件与所生成项目文件的对应关系
如果fileSet的packaged属性值为true,directory的值为X,那么archetype-resources下的X目录就会对应地在生成的项目中被创建,在生成项目的该X目录下还会生成一个包目录,如上例中的com/juvenxu/mvnbook/,最后Archetype中X目录的子目录及文件被复制到生成项目X目录的包目录下。如果packaged的属性值为false,那么Archetype中X目录下的内容会被直接复制到生成项目的X目录下。一般来说,Java代码都需要放到包路径下,而项目资源文件则不需要。因此,在代码清单18-3中,第一、第二个对应Java文件的fileSet的packaged的属性为true,而第三个对应资源文件的fileSet的packaged属性为false。
还有一点需要解释的是fileSet的filtered属性,它表示使用参数值替换属性声明,这是个非常有用的特性。例如,表18-1中涉及的几个Java类都需要有package声明,而且其值是在项目生成的时候确定的。这时就可以在Java代码中使用属性声明,如App.java的内容应该如代码清单18-4所示。
代码清单18-4 Archetype中的App.java
在使用包名com.juvenxu.mvnbook创建项目后,上述代码中的第一行会变成package com.juvenxu.mvnbook;。
类似地,Dao.java和Service.java的包声明应该如代码清单18-5所示。
代码清单18-5 Archetype中的Dao.java和Service.java
对应地,项目生成后Dao.java的第一行会成为“package com.juvenxu.mvnbook.dao;”,而Service.java的第一行会成为“package com.juvenxu.mvnbook.service;”。使用这样的技巧,就可以在Archetype中创建多层次的Java代码。
默认情况下,maven-archetype-plugin要求用户在使用Archetype生成项目的时候必须提供4个参数:groupId、artifactId、version和package。除此之外,用户在编写Archetype的时候可以要求额外的参数。例如,代码清单18-3就使用了requireProperties配置要求额外的port参数,这样,Archetype中所有开启filtered的文件中就可以使用${port}属性声明,然后在项目生成的时候用命令行输入的值填充。
此外,在编写Archetype的时候还可以为预置的4个参数提供默认值。例如,代码清单18-3中就为groupId参数提供了默认值com.juvenxu.mvnbook。在组织内部,可能很多项目的groupId是确定的,这时就可以为Archetype提供默认的groupId。
Archetype编写完成之后,使用mvn clean install将其安装到本地仓库。接着用户就可以通过指定该Archetype的坐标用它生成项目了:
该例使用了交互式的方式生成项目,由于该Archetype为groupId定义了默认值,用户就不再需要输入groupId的值了。此外,用户还不得不输入该Archetype额外定义的port参数的值。