可以看到, 子POM中并未定义模块groupId与version, 这是因为子POM默认会从父POM继承了如下元素:
groupId、version
dependencies
developers and contributors
plugin lists (including reports)
plugin executions with matching ids
plugin configuration
resources
因此所有的springframework都省去了version、junit还省去了scope, 而且插件还省去了executions与configuration配置, 因为完整的声明已经包含在父POM中.
优势: 当依赖、插件的版本、配置等信息在父POM中声明之后, 子模块在使用时就无须声明这些信息, 也就不会出现多个子模块使用的依赖版本不一致的情况, 也就降低了依赖冲突的几率. 另外如果子模块不显式声明依赖与插件的使用, 即使已经在父POM的dependencyManagement、pluginManagement中配置了, 也不会产生实际的效果.
推荐: 模块继承与模块聚合同时进行,这意味着, 你可以为你的所有模块指定一个父工程, 同时父工程中可以指定其余的Maven模块作为它的聚合模块. 但需要遵循以下三条规则:
在所有子POM中指定它们的父POM;
将父POM的packaging值设为pom;
在父POM中指定子模块/子POM的目录.
注: parent元素内还包含一个relativePath元素, 用于指定父POM的相对路径, 默认../pom.xml.
超级pom-约定优先于配置
任何一个Maven项目都隐式地继承自超级POM, 因此超级POM的大量配置都会被所有的Maven项目继承, 这些配置也成为了Maven所提倡的约定.
<!-- START SNIPPET: superpom -->
<project>
<modelVersion>4.0.0</modelVersion>
<!-- 定义了中央仓库以及插件仓库, 均为:https://repo.maven.apache.org/maven2 -->
<repositories>
<repository>
<id>central</id>
<name>Central Repository</name>
<url>https://repo.maven.apache.org/maven2</url>
<layout>default</layout>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>central</id>
<name>Central Repository</name>
<url>https://repo.maven.apache.org/maven2</url>
<layout>default</layout>
<snapshots>
<enabled>false</enabled>
</snapshots>
<releases>
<updatePolicy>never</updatePolicy>
</releases>
</pluginRepository>
</pluginRepositories>
<!-- 依次定义了各类代码、资源、输出目录及最终构件名称格式, 这就是Maven项目结构的约定 -->
<build>
<directory>${project.basedir}/target</directory>
<outputDirectory>${project.build.directory}/classes</outputDirectory>
<finalName>${project.artifactId}-${project.version}</finalName>
<testOutputDirectory>${project.build.directory}/test-classes</testOutputDirectory>
<sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>
<scriptSourceDirectory>${project.basedir}/src/main/scripts</scriptSourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/java</testSourceDirectory>
<resources>
<resource>
<directory>${project.basedir}/src/main/resources</directory>
</resource>
</resources>
<testResources>
<testResource>
<directory>${project.basedir}/src/test/resources</directory>
</testResource>
</testResources>
<!-- 为核心插件设定版本 -->
<pluginManagement>
<!-- NOTE: These plugins will be removed from future versions of the super POM -->
<!-- They are kept for the moment as they are very unlikely to conflict with lifecycle mappings (MNG-4453) -->
<plugins>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.3</version>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2-beta-5</version>
</plugin>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.8</version>
</plugin>
<plugin>
<artifactId>maven-release-plugin</artifactId>
<version>2.3.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
<!-- 定义项目报告输出路径 -->
<reporting>
<outputDirectory>${project.build.directory}/site</outputDirectory>
</reporting>
<!-- 定义release-profile, 为构件附上源码与文档 -->
<profiles>
<!-- NOTE: The release profile will be removed from future versions of the super POM -->
<profile>
<id>release-profile</id>
<activation>
<property>
<name>performRelease</name>
<value>true</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<inherited>true</inherited>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<inherited>true</inherited>
<artifactId>maven-javadoc-plugin</artifactId>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<inherited>true</inherited>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<updateReleaseInfo>true</updateReleaseInfo>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
<!-- END SNIPPET: superpom -->
附: Maven继承与组合的其他信息还可参考: Introduction to the POM.
Maven Plugin 开发
几乎100%的场景都不用我们自己开发Maven插件, 但理解插件开发可以使我们更加深入的理解Maven. 下面我们实际开发一个用于统计代码行数的插件 lc-maven-plugin.
1. 创建plugin项目
mvn archetype:generate
-DgroupId=com.fq.plugins -DartifactId=lc-maven-plugin -Dversion=0.0.1-SNAPSHOT
-DarchetypeArtifactId=maven-archetype-plugin
-DinteractiveMode=false
-DarchetypeCatalog=internal
使用maven-archetype-plugin Archetype可以快速创建一个Maven插件项目(关于Maven Archetype可参考What is an Archetype 、Creating Archetypes[注: 该文档介绍的是Archetype 1.x编写, 2.x内附链接]).
pom.xml
插件本身也是Maven项目, 特殊之处在于packaging方式为maven-plugin:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.fq.plugins</groupId>
<artifactId>lc-maven-plugins</artifactId>
<packaging>maven-plugin</packaging>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>3.3.3</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>3.3</version>
</dependency>
</dependencies>
</project>
maven-plugin 打包方式能控制Maven为其在生命周期阶段绑定插件处理的相关目标.
2. 编写目标Mojo
What is a Mojo? A mojo is a M aven plain O ld J ava O bject. Each mojo is an executable goal in Maven, and a plugin is a distribution of one or more related mojos.
/**
* @author jifang
* @since 16/10/9 上午11:33.
*/
@Mojo(name = "lc", defaultPhase = LifecyclePhase.VERIFY)
public class LCMavenMojo extends AbstractMojo {
private static final List<String> DEFAULT_FILES = Arrays.asList("java", "xml", "properties");
@Parameter(defaultValue = "${project.basedir}", readonly = true)
private File baseDir;
@Parameter(defaultValue = "${project.build.sourceDirectory}", readonly = true)
private File srcDir;
@Parameter(defaultValue = "${project.build.testSourceDirectory}", readonly = true)
private File testSrcDir;
@Parameter(defaultValue = "${project.build.resources}", readonly = true)
private List<Resource> resources;
@Parameter(defaultValue = "${project.build.testResources}", readonly = true)
private List<Resource> testResources;
@Parameter(property = "lc.file.includes")
private Set<String> includes = new HashSet<>();
private Log logger = getLog();
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
if (includes.isEmpty()) {
logger.debug("includes/lc.file.includes is empty!");
includes.addAll(DEFAULT_FILES);
}
logger.info("includes: " + includes);
try {
long lines = 0;
lines += countDir(srcDir);
lines += countDir(testSrcDir);
for (Resource resource : resources) {
lines += countDir(new File(resource.getDirectory()));
}
for (Resource resource : testResources) {
lines += countDir(new File(resource.getDirectory()));
}
logger.info("total lines: " + lines);
} catch (IOException e) {
logger.error("error: ", e);
throw new MojoFailureException("execute failure: ", e);
}
}
private LineProcessor<Long> lp = new LineProcessor<Long>() {
private long line = 0;
@Override
public boolean processLine(String fileLine) throws IOException {
if (!Strings.isNullOrEmpty(fileLine)) {
++this.line;
}
return true;
}
@Override
public Long getResult() {
long result = line;
this.line = 0;
return result;
}
};
private long countDir(File directory) throws IOException {
long lines = 0;
if (directory.exists()) {
Set<File> files = new HashSet<>();
collectFiles(files, directory);
for (File file : files) {
lines += CharStreams.readLines(new FileReader(file), lp);
}
String path = directory.getAbsolutePath().substring(baseDir.getAbsolutePath().length());
logger.info("path: " + path + ", file count: " + files.size() + ", total line: " + lines);
logger.info("\t-> files: " + files.toString());
}
return lines;
}
private void collectFiles(Set<File> files, File file) {
if (file.isFile()) {
String fileName = file.getName();
int index = fileName.lastIndexOf(".");
if (index != -1 && includes.contains(fileName.substring(index + 1))) {
files.add(file);
}
} else {
File[] subFiles = file.listFiles();
for (int i = 0; subFiles != null && i < subFiles.length; ++i) {
collectFiles(files, subFiles[i]);
}
}
}
}
@Parameter: 配置点, 提供Mojo的可配置参数. 大部分Maven插件及其目标都是可配置的, 通过配置点, 用户可以自定义插件行为:
<plugin>
<groupId>com.fq.plugins</groupId>
<artifactId>lc-maven-plugins</artifactId>
<version>0.0.1-SNAPSHOT</version>
<executions>
<execution>
<id>lc</id>
<phase>verify</phase>
<goals>
<goal>lc</goal>
</goals>
<configuration>
<includes>
<include>java</include>
<include>lua</include>
<include>json</include>
<include>xml</include>
<include>properties</include>
</includes>
</configuration>
</execution>
</executions>
</plugin>
execute(): 实际插件功能;
异常: execute()方法可以抛出以下两种异常:
MojoExecutionException: Maven执行目标遇到该异常会显示 BUILD FAILURE 错误信息, 表示在运行期间发生了预期的错误;
MojoFailureException: 表示运行期间遇到了未预期的错误, 显示 BUILD ERROR 信息.
3. 测试&执行
通过mvn clean install将插件安装到仓库后, 就可将其配置到实际Maven项目中, 用于统计项目代码了:
$ mvn com.fq.plugins:lc-maven-plugins:0.0.1-SNAPSHOT:lc [-Dlc.file.includes=js,lua] [-X]
注: 其他关于Maven Plugin开发可参考:Plugin Developers Centre、Maven API Reference.