【Maven】Maven Plugin示例:自己动手编写Maven插件

需求:

在Maven编译项目的时候,统计代码量,即项目中的文件数目、代码行数,包括java文件和配置文件两种;其中配置文件(sql、xml、properties)代码行数/4 处理。


创建项目:

首先确保已安装m2eclipse插件:http://eclipse.org/m2e/

eclipse - new - Maven Project,选择archetype = maven-archetype-plugin:(或用命令 mvn archetype:generate,再按提示操作)

【Maven】Maven Plugin示例:自己动手编写Maven插件_第1张图片

下一步设置好坐标信息,即可创建一个maven plugin工程。

生成的pom.xml文件如下(有手工改动,参见注释):

  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  2.   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  
  3.   <modelVersion>4.0.0</modelVersion>  
  4.   
  5.   <groupId>com.alpha.wang</groupId>  
  6.   <artifactId>maven-statis-plugin</artifactId>  
  7.   <version>0.0.1-SNAPSHOT</version>  
  8.   <packaging>maven-plugin</packaging>  
  9.   
  10.   <name>alpha-statis-plugin Maven Plugin</name>  
  11.   <url>http://blog.csdn.net/vking_wang</url>  
  12.   
  13.   <properties>  
  14.     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  
  15.   </properties>  
  16.   
  17.   <dependencies>  
  18.     <dependency>  
  19.       <groupId>org.apache.maven</groupId>  
  20.       <artifactId>maven-plugin-api</artifactId>  
  21.       <version>2.0</version>  
  22.     </dependency>  
  23.     <!-- 否则Mojo中的org.apache.maven.model.Resource,无法解析 -->  
  24.     <dependency>  
  25.         <groupId>org.apache.maven</groupId>  
  26.         <artifactId>maven-model</artifactId>  
  27.         <version>2.2.1</version>  
  28.     </dependency>  
  29.     <dependency>  
  30.       <groupId>junit</groupId>  
  31.       <artifactId>junit</artifactId>  
  32.       <version>3.8.1</version>  
  33.       <scope>test</scope>  
  34.     </dependency>  
  35.   </dependencies>  
  36.   
  37.   <build>  
  38.     <plugins>  
  39.       <plugin>  
  40.         <groupId>org.apache.maven.plugins</groupId>  
  41.         <artifactId>maven-plugin-plugin</artifactId>  
  42.         <version>2.5.1</version>  
  43.         <configuration>  
  44.           <!--[WARNING]Goal prefix is specified as: 'maven-statis-plugin'. Maven currently expects it to be 'statis'.-->  
  45.           <!-- goalPrefix>maven-statis-plugin</goalPrefix-->  
  46.           <goalPrefix>statis</goalPrefix>  
  47.         </configuration>  
  48.         <executions>  
  49.           <execution>  
  50.             <id>generated-helpmojo</id>  
  51.             <goals>  
  52.               <goal>helpmojo</goal>  
  53.             </goals>  
  54.           </execution>  
  55.         </executions>  
  56.       </plugin>  
  57.       <!-- generics are not supported in -source 1.3 (use -source 5 or higher to enable generics)-->  
  58.       <plugin>  
  59.         <groupId>org.apache.maven.plugins</groupId>  
  60.         <artifactId>maven-compiler-plugin</artifactId>  
  61.         <version>2.5.1</version>  
  62.         <configuration>  
  63.           <source>1.5</source>  
  64.           <target>1.5</target>  
  65.         </configuration>          
  66.       </plugin>  
  67.     </plugins>  
  68.   </build>  
  69. </project>  

注意packing为maven-plugin;并有对maven-plugin-api的依赖。

还会自动生成一个public class MyMojo    extends AbstractMojo的源文件。

编写Mojo

Mojo = Maven Old Java Object,需要继承AbstractMojo,并实现其execute方法。

上一步已经自动生成了一个Mojo,我们删掉重新创建一个:

  1. import org.apache.maven.model.Resource;  
  2. import org.apache.maven.plugin.AbstractMojo;  
  3. import org.apache.maven.plugin.MojoExecutionException;  
  4. /** 
  5.  * 
  6.  * @goal count 
  7.  *  
  8.  * @phase process-sources 
  9.  */  
  10. public class CountMojo extends AbstractMojo  
  11. {  
  12.     private static final String[] INCLUDES_DEFAULT = {"java""xml""sql""properties"};    
  13.     private static final String[] RATIOS_DEFAULT = {"1.0""0.25""0.25""0.25"};  
  14.     private static final String DOT = ".";  
  15.     /** 
  16.      * @parameter expression="${project.basedir}" 
  17.      * @required 
  18.      * @readonly 
  19.      */  
  20.     private File basedir;  
  21.     /** 
  22.      * @parameter expression="${project.build.sourceDirectory}" 
  23.      * @required 
  24.      * @readonly 
  25.      */  
  26.     private File sourcedir;  
  27.     /** 
  28.      * @parameter expression="${project.build.testSourceDirectory}" 
  29.      * @required 
  30.      * @readonly 
  31.      */  
  32.     private File testSourcedir;  
  33.     /** 
  34.      * @parameter expression="${project.resources}" 
  35.      * @required 
  36.      * @readonly 
  37.      */  
  38.     private List<Resource> resources;      
  39.     //private List<File> resources;  
  40.     /** 
  41.      * @parameter expression="${project.testResources}" 
  42.      * @required 
  43.      * @readonly 
  44.      */  
  45.     private List<Resource> testResources;  
  46.     //private List<File> testResources;  
  47.     /** 
  48.      * @parameter 
  49.      */  
  50.     private String[] includes;  
  51.     /** 
  52.      * @parameter 
  53.      */  
  54.     private String[] ratios;//TODO 定义为double[],从xml读取时提示java.lang.ClassCastException: [D cannot be cast to [Ljava.lang.Object;  
  55.       
  56.     private Map<String, Double> ratioMap = new HashMap<String, Double>();  
  57.     private long realTotal;  
  58.     private long fakeTotal;  
  59.   
  60.     public void execute() throws MojoExecutionException  
  61.     {  
  62.         initRatioMap();  
  63.         try{  
  64.             countDir(sourcedir);  
  65.             countDir(testSourcedir);  
  66.               
  67.             for(Resource res : resources){  
  68.                 countDir(new File(res.getDirectory()));  
  69.             }  
  70.             for(Resource res : testResources){  
  71.                 countDir(new File(res.getDirectory()));  
  72.             }  
  73.               
  74.             getLog().info("TOTAL LINES:"+fakeTotal+ " ("+realTotal+")");  
  75.               
  76.         }catch (IOException e){  
  77.             throw new MojoExecutionException("Unable to count lines of code", e);  
  78.         }  
  79.          
  80.     }  
  81. }  


所调用的工具方法定义如下:

  1. private void initRatioMap() throws MojoExecutionException{  
  2.     if(includes == null || includes.length == 0){  
  3.         includes = INCLUDES_DEFAULT;  
  4.         ratios = RATIOS_DEFAULT;  
  5.     }  
  6.     if(ratios == null || ratios.length == 0){  
  7.         ratios = new String[includes.length];  
  8.         for(int i=0; i<includes.length; i++){  
  9.             ratios[i] = "1.0";  
  10.         }  
  11.     }  
  12.     if(includes.length != ratios.length){  
  13.         throw new MojoExecutionException("pom.xml error: the length of includes is inconsistent with ratios!");  
  14.     }  
  15.     ratioMap.clear();  
  16.     for(int i=0; i<includes.length; i++){  
  17.         ratioMap.put(includes[i].toLowerCase(), Double.parseDouble(ratios[i]));  
  18.     }  
  19. }  
  20.   
  21. private void countDir(File dir) throws IOException {  
  22.     if(! dir.exists()){  
  23.         return;  
  24.     }  
  25.     List<File> collected = new ArrayList<File>();  
  26.     collectFiles(collected, dir);  
  27.       
  28.     int realLine = 0;  
  29.     int fakeLine = 0;  
  30.     for(File file : collected){  
  31.         int[] line =  countLine(file);  
  32.         realLine += line[0];  
  33.         fakeLine += line[1];  
  34.     }  
  35.       
  36.     String path = dir.getAbsolutePath().substring(basedir.getAbsolutePath().length());  
  37.     StringBuilder info = new StringBuilder().append(path).append(" : ").append(fakeLine).append(" ("+realLine+")")  
  38.             .append(" lines of code in ").append(collected.size()).append(" files");  
  39.     getLog().info(info.toString());       
  40.       
  41. }  
  42.   
  43. private void collectFiles(List<File> collected, File file)  
  44.         throws IOException{  
  45.     if(file.isFile()){  
  46.         if(isFileTypeInclude(file)){  
  47.             collected.add(file);  
  48.         }  
  49.     }else{  
  50.         for(File files : file.listFiles()){  
  51.             collectFiles(collected, files);  
  52.         }  
  53.     }  
  54. }  
  55.   
  56. private int[] countLine(File file)  
  57.         throws IOException{  
  58.     BufferedReader reader = new BufferedReader(new FileReader(file));  
  59.     int realLine = 0;  
  60.     try{  
  61.         while(reader.ready()){  
  62.             reader.readLine();  
  63.             realLine ++;  
  64.         }  
  65.     }finally{  
  66.         reader.close();  
  67.     }  
  68.     int fakeLine = (int) (realLine * getRatio(file));  
  69.     realTotal += realLine;  
  70.     fakeTotal += fakeLine;  
  71.       
  72.     StringBuilder info = new StringBuilder().append(file.getName()).append("  : ").append(fakeLine).append(" ("+realLine+")")  
  73.             .append(" lines");  
  74.     getLog().debug(info.toString());  
  75.       
  76.     return new int[]{realLine, fakeLine};  
  77. }  
  78.   
  79. private double getRatio(File file){  
  80.     double ratio = 1.0;  
  81.     String type = getFileType(file);  
  82.     if(ratioMap.containsKey(type)){  
  83.         ratio = ratioMap.get(type);  
  84.     }         
  85.     return ratio;  
  86. }  
  87.   
  88. private boolean isFileTypeInclude(File file){  
  89.     boolean result = false;  
  90.     String fileType = getFileType(file);  
  91.     if(fileType != null && ratioMap.keySet().contains(fileType.toLowerCase())){           
  92.         result = true;            
  93.     }         
  94.     return result;  
  95. }  
  96.   
  97. private String getFileType(File file){  
  98.     String result = null;  
  99.     String fname = file.getName();  
  100.     int index = fname.lastIndexOf(DOT);  
  101.     if(index > 0){  
  102.         String type = fname.substring(index+1);  
  103.         result = type.toLowerCase();  
  104.     }  
  105.     return result;  
  106. }  

注意此处 annotation 的使用,例如@parameter expression="${project.build.sourceDirectory}"就会自动将项目源代码路径赋值给sourcedir变量。

特别注意@goal,每个插件可有多个目标,在用mvn命令时需要用到这个goal。

执行

可使用mvn clean install将自定义的这个插件安装到本地仓库。

  1. mvn clean install  
然后在需要统计的项目目录下,调用如下命令即可统计代码行:

  1. mvn com.alpha.wang:maven-statis-plugin:0.0.1-SNAPSHOT:count  
最后一个冒号后面,就是上面定义的@goal。

显然这个命令太长了,使用很不方便,可在settings.xml中配置如下:

  1. <pluginGroups>  
  2.     <pluginGroup>com.alpha.wang</pluginGroup>  
  3. </pluginGroups>  
这样上述命令可以简写为:

  1. mvn statis:count  

提供配置点

Mojo的includes、ratios变量标记为@parameter,表示用户可在pom.xml中配置该字段;例如可在目标项目的pom.xml中增加如下内容:

  1. <build>  
  2.     <plugins>  
  3.         <plugin>  
  4.             <groupId>com.alpha.wang</groupId>  
  5.             <artifactId>maven-statis-plugin</artifactId>  
  6.             <version>0.0.1-SNAPSHOT</version>  
  7.             <configuration>  
  8.                 <includes>  
  9.                    <!--include>java</include-->  
  10.                    <include>properties</include>  
  11.                 </includes>  
  12.                 <ratios>  
  13.                    <ratio>1.5</ratio>  
  14.                 </ratios>  
  15.             </configuration>  
  16.         </plugin>  
再在该项目上运行mvn statis:count时就只会统计properties文件了,并将代码量*1.5。

D:\>mvn statis:count
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'statis'.
[INFO] ------------------------------------------------------------------------
[INFO] Building com.。。。
[INFO]    task-segment: [statis:count]
[INFO] ------------------------------------------------------------------------
[INFO] [statis:count]
[INFO] \src\main\java : 0 (0) lines of code in 0 files
[INFO] \src\main\resources : 18 (12) lines of code in 1 files
[INFO] TOTAL LINES:18 (12)

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: < 1 second
[INFO] Finished at: Tue Feb 26 17:11:22 CST 2013
[INFO] Final Memory: 6M/215M
[INFO] ------------------------------------------------------------------------


绑定插件

那么每次都必须手工执行命令来统计代码量吗?我们可以绑定插件到生命周期的某个阶段,例如install阶段,那么只要每次install该项目的时候,就会自动统计代码量。

修改目标项目的pom文件,注意<phase>、<goals>标签:

  1. <build>  
  2.     <plugins>  
  3.             <plugin>  
  4.             <groupId>com.alpha.wang</groupId>  
  5.             <artifactId>maven-statis-plugin</artifactId>  
  6.             <version>0.0.1-SNAPSHOT</version>  
  7.             <configuration>  
  8.                 <includes>  
  9.                    <include>java</include>  
  10.                    <include>properties</include>  
  11.                 </includes>  
  12.                 <ratios>  
  13.                    <ratio>1.0</ratio>  
  14.                    <ratio>0.5</ratio>  
  15.                                        </ratios>  
  16.             </configuration>  
  17.             <executions>  
  18.                 <execution>  
  19.                     <id>count line number</id>  
  20.                     <phase>install</phase>  
  21.                     <goals>  
  22.                         <goal>count</goal>                            
  23.                     </goals>  
  24.                 </execution>  
  25.             </executions>  
  26.         </plugin>  

你可能感兴趣的:(【Maven】Maven Plugin示例:自己动手编写Maven插件)