综述
maven使用描述性语言来表达程序编译打包过程,make、ant使用过程性语言来表达程序编译打包过程,maven把过程都封装到了插件里面,这就使得maven很清晰。
maven构建过程中包括三种事物:生命周期、阶段、目标,这三种事物是过程抽象成的三种粒度。
maven内置三种生命周期:clean,default,site(很少用到)。mvn clean
命令可以直接执行clean生命周期,但是不能使用mvn default
来执行default生命周期。default生命周期是最重要、最复杂的生命周期,它由若干阶段(phase)组成,可以使用命令mvn DefaultPhase
来执行default生命周期中的各个阶段。构建目标是Maven构建过程中最细化的步骤,一个目标可以与一个或多个构建阶段绑定,也可以不绑定。如果一个目标没有与任何构建阶段绑定,你只能将该目标的名称作为参数传递给mvn命令来执行它。如果一个目标绑定到多个构建阶段,该目标在绑定的构建阶段执行的同时被执行。
- validate:验证项目的正确性,以及所有必需的信息都是否都存在。同时也会确认项目的依赖是否都下载完毕。
- initialize
- generate-sources
- process-sources
- generate-resources
- process-resources
- compile:编译项目的源代码
- process-classes
- generate-test-sources
- process-test-sources
- generate-test-resources
- process-test-resources
- test-compile
- process-test-classes
- test:选择合适的单元测试框架,对编译后的源码执行测试;这些测试不需要代码被打包或者部署。
- prepare-package
- package:将编译后的代码以可分配的形式打包,如Jar包。
- pre-integration-test
- integration-test
- post-integration-test
- verify
- install:将项目打包后安装到本地仓库,可以作为其它项目的本地依赖。
- deploy:将最终的包复制到远程仓库,与其它开发者和项目共享。
maven的default生命周期各个phase绑定的插件:
phase | 插件:goal |
---|---|
generate-resources | plugin:descriptor |
process-resources | resources:resources |
compile | compiler:compile |
process-test-resources | resources:testResources |
test-compile | compiler:testCompile |
test | surefire:test |
package | jar:jar and plugin:addPluginArtifactMetadata |
install | install:install |
deploy | deploy:deploy |
可以通过命令查看插件的详细信息:
mvn help:describe -Dplugin=org.apache.maven.plugins:maven-compile-plugin –Ddetail
编译插件maven-compiler
maven的编译插件可以用来设置源码的JDK版本、目标平台JDK版本、源码编码。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>utf-8</encoding>
</configuration>
</plugin>
指定Java版本的另一种方式:通过属性指明版本。
<profile>
<id>jdk1.7</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>1.7</jdk>
</activation>
<properties>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
<maven.compiler.compilerVersion>1.7</maven.compiler.compilerVersion>
</properties>
</profile>
也可以不必指明profile,直接指明properties
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
compiler-plugin可以指定编译时参数
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<source>1.8</source> <!-- 源代码使用jdk1.8支持的特性 -->
<target>1.8</target> <!-- 使用jvm1.8编译目标代码 -->
<compilerArgs> <!-- 传递参数 -->
<arg>-parameters</arg>
<arg>-Xlint:unchecked</arg>
<arg>-Xlint:deprecation </arg>
</compilerArgs>
</configuration>
</plugin>
maven-shade
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<configuration>
<!-- put your configurations here -->
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<minimizeJar>true</minimizeJar>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
maven-assembly
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>cnblog.Cmd</mainClass>
</manifest>
<manifestEntries>
<Class-Path>.</Class-Path>
</manifestEntries>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
打包插件maven-jar
maven-jar插件可以将源码打成包含依赖的jar包,不过maven-jar如果包含依赖需要使用maven-dependency插件,两个插件配合才能将依赖+源码打成一个jar包。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.6</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>MavenJarExtractor</mainClass>
</manifest>
</archive>
<finalName>mavenjar</finalName>
</configuration>
</plugin>
依赖打包插件maven-dependency
maven-dependency的作用是把依赖打包到lib里面去。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.10</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
并行运行测试的插件:maven-surefire
maven-surefire插件用于指定测试用例的一些规则。这个插件是maven自带的插件,可以不必在pom.xml中指明。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipAfterFailureCount>1</skipAfterFailureCount> <!-- 只要有一个用例测试失败,就立即停止。默认情况下会跑完所有测试用例 -->
<rerunFailingTestsCount>2</rerunFailingTestsCount> <!-- 失败重试次数 -->
<parallel>methods</parallel> <!-- 并发执行测试用例 -->
<threadCount>10</threadCount> <!-- 并发执行时的线程数量 -->
</configuration>
</plugin>
常用命令
// 跳过测试用例
mvn test -Dmaven.test.skip=true
// 或
mvn test -DskipTests=true
// 运行指定的测试用例
mvn test -Dtest=*2Test
mvn test -Dtest=App2Test,AppTest
mvn test -Dtest=???2Test
mvn test -Dtest=*Test#testAdd
springboot-maven-plugin
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
如果指定了spring-boot作为parent,可以不指定版本和repackage goal:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.8.RELEASE</version>
<configuration>
<mainClass>${start-class}</mainClass>
</configuration>
</parent>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
spring-boot查找main文件的流程是:
首先查看是否有值,如果有,直接拿标签内的类名作为入口。
<properties> <start-class>com.example.Application</start-class> </properties>
如果没找到
<start-class>
标签,会遍历所有文件,找到注解了@SpringBootApplication
并含有main方法的类,将其作为入口。
实践证明,如果没有定义<start-class>
,查找入口类的方法也是非常快的。
在实际开发中,推荐手动定义<start-class>
。这样在一个项目工程中可以有多个@SpringBootApplication
注解的类,修改一下pom里的配置就能灵活切换入口了。
maven打包并通过windows脚本运行
创建一个文件夹haha,里面包含三项
- Haha.java
- Main.java
- pom.xml
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>wyf</groupId>
<artifactId>CloneSite</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<sourceDirectory>.</sourceDirectory>
<finalName>haha</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
<encoding>utf-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
maven遵守约定大于配置的原则,它有一套默认的目录结构,但是也可以在build中指定源代码目录和资源文件目录。
Main.java
public class Main{
public static void main(String[] args) {
System.out.println("天下大势为我所控");
}
}
Haha.java
public class Haha {
public static void main(String[] args) {
System.out.println("hello world");
}
}
在此文件夹下打开cmd,mvn package,打包即可生成target目录。
cd到target,执行java -cp haha.jar Main
就会运行Main类,执行java -cp haha.jar Haha
就会运行Haha类。
我认为这种运行jar包的方式相对于直接打包成可运行的jar包更好,因为这样打包更简单。
可执行jar包其实也可以使用java -cp haha.jar Something
的方式运行其它类,只不过它有默认的主类。
有时会遇到“错误: 找不到或无法加载主类”这样的错误,一个很可能的原因是类加载异常。比如class Haha继承了某个类A,而这个类A却又在classpath中找不到,那么就会报错:Haha找不到或者无法加载。实际上是无法加载。
要想使所有的依赖都能够找到,需要maven打包时导出所有依赖包:
<build>
<finalName>clonesite</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>utf-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
而运行的时候,java -cp lib/one.jar;lib/two.jar;mine.jar MyClass
,lib中的所有jar包都要出现在cp参数中,无法使用文件夹+通配符的方式。