17.2 案例:编写一个用于代码行统计的Maven插件
为了便于大家实践,下面将详细演示如何实际编写一个简单的用于代码行统计的Maven插件。使用该插件,用户可以了解到Maven项目中各个源代码目录下文件的数量,以及它们加起来共有多少代码行。不过,笔者强烈反对使用代码行来考核程序员,因为大家都知道,代码的数量并不能真正反映一个程序员的价值。
要创建一个Maven插件项目,首先使用maven-archetype-plugin命令:
$mvn archetype:generate
然后选择:
maven-archetype-plugin(An archetype which contains a sample Maven plugin.)
输入Maven坐标等信息之后,一个Maven插件项目就创建好了。打开项目的pom.xml可以看到如代码清单17-1所示的内容。
代码清单17-1 代码行统计插件的POM
<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.juvenxu.mvnbook</groupId>
<artifactId>maven-loc-plugin</artifactId>
<packaging>maven-plugin</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>Maven LOC Plugin</name>
<url>http://www.juvenxu.com/</url>
<properties>
<maven.version>3.0</maven.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>${maven.version}</version>
</dependency>
</dependencies>
</project>
Maven插件项目的POM有两个特殊的地方:
1)它的packaging必须为maven-plugin,这种特殊的打包类型能控制Maven为其在生命周期阶段绑定插件处理相关的目标,例如在compile阶段,Maven需要为插件项目构建一个特殊插件描述符文件。
2)从上述代码中可以看到一个artifactId为maven-plugin-api的依赖,该依赖中包含了插件开发所必需的类,例如稍后会看到的AbstractMojo。需要注意的是,代码清单17-1中并没有使用默认Archetype生成的maven-plugin-api版本,而是升级到了3.0,这样做的目的是与Maven的版本保持一致。
插件项目创建好之后,下一步是为插件编写目标。使用Archetype生成的插件项目包含了一个名为MyMojo的Java文件,我们将其删除,然后自己创建一个CountMojo,如代码清单17-2所示。
代码清单17-2 CountMojo的主要代码
/**
*Goal which counts lines of code of a project
*
*@goal count
*/
public class CountMojo
extends AbstractMojo
{
private static final String[]INCLUDES_DEFAULT={"java","xml","properties"
};
/**
*@parameter expression="${project.basedir}"
*@required
*@readonly
*/
private File basedir;
/**
*@parameter expression="${project.build.sourceDirectory}"
*@required
*@readonly
*/
private File sourceDirectory;
/**
*@parameter expression="${project.build.testSourceDirectory}"
*@required
*@readonly
*/
private File testSourceDirectory;
/**
*@parameter expression="${project.build.resources}"
*@required
*@readonly
*/
private List<Resource>resources;
/**
*@parameter expression="${project.build.testResources}"
*@required
*@readonly
*/
private List<Resource>testResources;
/**
*The file types which will be included for counting
*
*@parameter
*/
private String[]includes;
public void execute()
throws MojoExecutionException
{
if(includes==null| |includes.length==0)
{
includes=INCLUDES_DEFAULT;
}
try
{
countDir(sourceDirectory);
countDir(testSourceDirectory);
for(Resource resource:resources)
{
countDir(new File(resource.getDirectory()));
}
for(Resource resource:testResources)
{
countDir(new File(resource.getDirectory()));
}
}
catch(IOException e)
{
throw new MojoExecutionException("Unable to count lines of code.",e);
}
}
}
首先,每个插件目标类,或者说Mojo,都必须继承AbstractMojo并实现execute()方法,只有这样Maven才能识别该插件目标,并执行execute()方法中的行为。其次,由于历史原因,上述CountMojo类使用了Java 1.4风格的标注(将标注写在注释中),这里要关注的是@goal,任何一个Mojo都必须使用该标注写明自己的目标名称,有了目标定义之后,我们才能在项目中配置该插件目标,或者在命令行调用之。例如:
$mvn com.juvenxu.mvnbook:maven-loc-plugin:0.0.1-SNAPSHOT:count
创建一个Mojo所必要的工作就是这三项:继承AbstractMojo、实现execute()方法、提供@goal标注。
下一步是为插件提供配置点。我们希望该插件默认统计所有Java、XML,以及properties文件,但是允许用户配置包含哪些类型的文件。代码清单17-2中的includes字段就是用来为用户提供该配置点的,它的类型为String数组,并且使用了@parameter参数表示用户可以在使用该插件的时候在POM中配置该字段,如代码清单17-3所示。
代码清单17-3 配置CountMojo的includes参数
<plugin>
<groupId>com.juvenxu.mvnbook</groupId>
<artifactId>maven-loc-plugin</artifactId>
<version>0.0.1-SNAPSHOT</version>
<configuration>
<includes>
<include>java</include>
<include>sql</include>
</includes>
</configuration>
</executions>
</plugin>
代码清单17-3配置了CountMojo统计Java和SQL文件,而不是默认的Java、XML和Properties。
代码清单17-2中还包含了basedir、sourceDirectory、testSourceDirectory等字段,它们都使用了@parameter标注,但同时关键字expression表示从系统属性读取这几个字段的值。${project.basedir}、${project.build.sourceDirectory}、${project.build.testSourceDirectory}
等表达式读者应该已经熟悉,它们分别表示了项目的基础目录、主代码目录和测试代码目录。@readonly标注表示不允许用户对其进行配置,因为对于一个项目来说,这几个目录位置都是固定的。
了解这些简单的配置点之后,下一步就该实现插件的具体行为了。从代码清单17-2的execute()方法中大家能看到这样一些信息:如果用户没有配置includes则就是用默认的统计包含配置,然后再分别统计项目主代码目录、测试代码目录、主资源目录,以及测试资源目录。这里涉及一个countDir()方法,其具体实现如代码清单17-4所示。
代码清单17-4 CountMojo的具体行为实现
private void countDir(File dir)
throws IOException
{
if(!dir.exists())
{
return;
}
List<File>collected=new ArrayList<File>();
collectFiles(collected,dir);
int lines=0;
for(File sourceFile:collected)
{
lines+=countLine(sourceFile);
}
String path=dir.getAbsolutePath().substring(basedir.getAbsolutePath()
.length());
getLog().info(path+":"+lines+"lines of code in"+collected.size()+
"files");
}
private void collectFiles(List<File>collected,File file)
{
if(file.isFile())
{
for(String include:includes)
{
if(file.getName().endsWith("."+include))
{
collected.add(file);
break;
}
}
}
else
{
for(File sub:file.listFiles())
{
collectFiles(collected,sub);
}
}
}
private int countLine(File file)
throws IOException
{
BufferedReader reader=new BufferedReader(new FileReader(file));
int line=0;
try
{
while(reader.ready())
{
reader.readLine();
line++;
}
}
finally
{
reader.close();
}
return line;
}
这里简单解释一下上述三个方法:collectFiles()方法用来递归地收集一个目录下所有应当被统计的文件,countLine()方法用来统计单个文件的行数,而countDir()则借助上述两个方法统计某一目录下共有多少文件被统计,以及这些文件共包含了多少代码行。
代码清单17-2中的execute()方法包含了简单的异常处理,代码行统计的时候由于涉及了文件操作,因此可能会抛出IOException。当捕获到IOException的时候,使用MojoExecutationException对其简单包装后再抛出,Maven执行插件目标的时候如果遇到MojoExecutationException,就会在命令行显示“BUILD ERROR”信息。
代码清单17-4中的countDir()方法的最后一行使用了AbstractMojo的getLog()方法,该方法返回一个类似于Log4j的日志对象,可以用来将输出日志到Maven命令行。这里使用了info级别的日志告诉用户某个路径下有多少文件被统计,共包含了多少代码行,因此在使用该插件的时候可以看到如下的Maven输出:
[INFO]——maven-loc-plugin:0.0.1-SNAPSHOT:count(default)@app——
[INFO]\src\main\java:13 lines of code in 1 files
[INFO]\src\test\java:38 lines of code in 1 files
使用mvn clean install命令将该插件项目构建并安装到本地仓库后,就能使用它统计Maven项目的代码行了。如下所示:
$ mvn com.juvenxu.mvnbook:maven - loc - plugin:0.0.1 - SNAPSHOT:count
[INFO] Scanning for projects…
[INFO]
[INFO] ----------------------------------------------------------------------
[INFO] Building Account Captcha 1.0.0 - SNAPSHOT
[INFO] ----------------------------------------------------------------------
[INFO]
[INFO] ---maven-loc - plugin:0.0.1-SNAPSHOT:count (default-cli) @ account-captcha
---
[INFO] \src\main\java: 179 lines of code in 4 files
[INFO] \src\test\java: 112 lines of code in 2 files
[INFO] \src\main\resources: 11 lines of code in 1 files
[INFO] \src\test\resources: 0 lines of code in 0 files
[INFO] ----------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ----------------------------------------------------------------------
[INFO] Total time: 0.423s
[INFO] Finished at: Sat Jun 05 16:28:35 CST 2010
[INFO] Final Memory: 1M/4M
[INFO] ----------------------------------------------------------------------
如果嫌命令行太长太复杂,可以将该插件的groupId添加到settings.xml中。如下所示:
<settings>
<pluginGroups>
<pluginGroup>com.juvenxu.mvnbook</pluginGroup>
</pluginGroups>
</settings>
现在Maven命令行就可以简化成:
$mvn loc:count
这里面的具体原理可参考7.8.4节。