Maven学习
前言
Maven可以翻译为知识的积累,是一个跨平台的项目管理工具。Maven主要服务于基于Java平台的项目构建、依赖管理和项目信息管理。
它能让项目对象模型最大程度地与实际代码相独立,即解耦。很大程度上避免了Java代码和POM代码的相互影响。比如项目需要升级代码时只需要修改POM,而不需要修改Java代码。
maven的核心有:坐标、依赖、仓库、生命周期、插件
何为构建:
构建即清理、编译、测试、打包、部署
正文
<?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> //POM模型的版本,对于Maven2或者Maven3来说只能是4.0.0 <groupId>com.yang</groupId>//定义项目属于哪个组,往往和组织或者公司存在关联如com.公司名.项目名 <artifactId>spmb</artifactId>// 定义了当前项目在组中的唯一ID <version>1.0-SNAPSHOT</version>//项目当前的版本,SNAPSHOT意为快照,不稳定的版本 <packaging>war</packaging> <name>spmb Maven Webapp</name>//非必需的,声明一个对于用户来说友好的项目名称 <!-- FIXME change it to the project's website --> <url>http://www.example.com</url> </project>
Maven约定项目的主代码位于src/java/main目录下,且Java类的包名应与POM定义的groupId和artifactId相吻合。项目的主代码最终会被打包到最终的构件中,如jar,而测试代码不会被打包。 默认情况下,maven构建的所有输出都在target/目录中。
代码编写完后,可以使用下面命令进行编译:
mvn clean compile
首先执行clean任务,删除target目录。接着执行resources任务,最后执行compile目录,将项目主代码编译到target/classes目录。
调用Maven执行测试:
mvn clean test
打包,Maven打包执行会执行编译,测试等操作,将项目打成jar包或者war包,存放在target/目录下,默认包名为artifact-version.jar
mvn clean package
若想修改打包后的包名,在build元素下添加:
<build>
<finalName>${project.artifactId}-${project.version}-xxx</finalName>
</build>
打包并上传(安装)到Maven本地仓库,这样其他maven项目就能直接通过引用jar包使用它了。
mvn clean install
将项目打成jar包并部署到私服(远程仓库)
mvn clean deploy
打包、上传、部署时忽略test文件使用命令
mvn clean xxx -Dmaven.test.skip=true
目前为止,默认打包生成的jar包是不能直接运行的,因为带有main方法的类信息不会添加到manifest中(打开jar文件中的META_INF/MANIFEST.MF文件,将无法看到Main-Class一行)。运行结果如下:
D:\IdeaProject\spmb\target>java -jar spmb.jar
spmb.jar中没有主清单属性
为了生成可执行的jar文件,需要借助maven-shade-plugin插件,配置插件使用plugin元素,位于<project><build><plugin>下面。如:
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>1.2.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration>// 插件全局配置,所有基于该插件目标的任务都会使用这些配置 <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>HelloWorld</mainClass> </transformer> </transformers> </configuration> </execution> </executions> </plugin> </plugins> </build>
使用mvn clean package命令后,在target目录下会有 original-spmb-1.0-SNAPSHOT.jar 和 spmb-1.0-SNAPSHOT.jar 两个jar包,后者是带有Main-Class信息的可运行jar包,前者是原始
的jar包,打开spmb-1.0-SNAPSHOT.jar包的META_INF/MANIFEST.MF文件,包含了Main-Class: com.yang.HelloWorld这样一行信息,直接运行此jar包
D:\IdeaProject\spmb\target>java -jar spmb-1.0-SNAPSHOT.jar
hello
依赖的配置:
<dependencies> <dependency> <groupId></groupId> <artifactId></artifactId> <version></version> <type></type>// 依赖的类型,对应于项目坐标定义的packaging,大部分情况下不必声明,默认为jar <scope></scope>//依赖的范围 <optional></optional>//标记依赖是否可选 <exclusions>// 用来排除传递性依赖 <exclusion></exclusion> </exclusions> </dependency> </dependencies>
依赖范围scope
依赖范围用来控制依赖与三种classpath(编译classpath、测试classpath、运行classpath)的关系,Maven有以下几种依赖范围:
compile:编译依赖范围,默认范围。使用此依赖范围对编译、测试、运行三种classpath都有效。
test:测试依赖范围,只对测试classpath有效,在编译主代码或者运行项目的使用时将无法使用此类依赖
provided:已提供依赖范围。使用此范围的Maven依赖,对于编译和测试classpath有效,但在运行时无效。典型的例子如servlet-api,运行的时候容器提供,所以不需要Maven引入
runtime:运行时范围依赖。使用此依赖范围的Maven依赖,对于测试和运行classpath有效,但在编译主代码时无效。典型的例子如JDBC驱动实现
system:系统依赖范围,和provided依赖范围完全一致。但是使用此范围的依赖时必须通过systemPath元素显示地指定依赖文件的路径。由于此类依赖不是通过Maven仓库解析的,而且往往与本机系统绑定,可能造成构建的不可移植,因此应谨慎使用。
import:导入依赖范围,不会对三种classpath产生实际的影响。
传递性依赖:依赖一个jar包,这个jar包依赖了其他jar包,那么依赖的依赖也会称为自己的一个依赖,这就叫做传递性依赖。
传递性依赖和依赖范围:依赖范围不仅可以控制依赖与classpath的关系,还会对传递性依赖产生影响。依赖范围只会随着传递性依赖一致或者收缩,不会扩大。
包冲突
假设 A->B->C->D1, E->F->D2,D1,D2 分别为 D 的不同版本。
如果 pom.xml 文件中引入了 A 和 E 之后,按照 Maven 传递依赖原则,工程内需要引入的实际 Jar 包将会有:A B C D1 和 E F D2,因此 D1,D2 将会产生包冲突。
如何解决包冲突
Maven 解析 pom.xml 文件时,同一个 jar 包只会保留一个,这样有效的避免因引入两个 jar 包导致的工程运行不稳定性。
Maven 默认处理策略
最短路径优先
Maven 面对 D1 和 D2 时,会默认选择最短路径的那个 jar 包,即 D2。E->F->D2 比 A->B->C->D1 路径短 1。
最先声明优先
如果路径一样的话,如:A->B->C1, E->F->C2 ,两个依赖路径长度都是 2,那么就选择最先声明。
可选依赖:如B的依赖X的optional为true时,表示此依赖是可选依赖,当A依赖B时,由于X是可选依赖,那么依赖将不会得以传递。
排除依赖:传递性依赖会给项目隐式地引入很多依赖,虽然极大的简化了项目依赖的管理,但是有些时候也会带来问题。如不想引入某个传递性依赖,代码中使用exclusions元素声明排除依赖,exclusions可以
包含一个或者多个exclusion子元素,因此可以排除一个或者多个传递性依赖。需要注意声明exclusion的时候只需要groupId和artifactId,而不需要version元素。
归类依赖:如项目中有很多关于Sping框架的依赖,它们是来自一个项目的不同模块,因此它们的版本是相同的。而且可预见的是升级版本时,依赖也会跟着一起全部升级,所以应该在一个唯一的地方定义
版本,并且在dependency声明中引用这一版本。在properties中定义属性,然后再依赖中使用美元符号和大括弧环绕的方式引入Maven属性。
优化依赖:Maven会自动解析所有项目的直接依赖和传递性依赖,并且根据规则正确判断每个依赖的范围,对一些依赖冲突也能进行调节,以确保任何一个构建只有唯一的版本在依赖中存在。在这些工作之后,最后得到的那些依赖称为已解析依赖。查看已解析依赖:mvn dependency:list,查看依赖树:mvn dependency:tree,分析当前项目的依赖:mvn dependency:analyze
仓库
在某个位置统一存储所有Maven项目共享的构件,这个位置叫做仓库。每个构件都有一个唯一的坐标,根据这个坐标可以定义其在仓库中的唯一存储路径。,该路径与坐标的大致关系为:
groupId/artifactId/version/artifactId-version.packaging
仓库分为本地仓库和远程仓库
远程仓库
远程服务器上的maven仓库
私服是一种特殊的远程仓库,为了节省带宽和时间,应该在局域网内架设一个私有的仓库服务器,用其代理所有外部的远程仓库。当Maven需要下载构件的时候,它从私服请求,如果私服上不存在该构件,则从外部的远程仓库下载,缓存在私服上之后,再为Maven的下载请求提供服务。另外内部的项目还能部署到私服上供其他项目使用。
本地仓库
一个构件只有在本地仓库中之后,才能由其他Maven项目使用
构件如何进入到本地仓库?
从远程仓库下载到本地仓库中
将本地项目的构件安装到本地仓库中
如,本地有两个项目A,B,两者都无法从远程仓库获得,而同时A又依赖B,为了能构件A,B就必须首先得以构件并安装到本地仓库中
当Maven根据坐标寻找构件的时候,它首先会查看本地仓库,如果本地仓库存在此构件,则直接使用;
如果本地仓库不存在此构件,或者需要查看是否有更新的构件版本,Maven就会去远程仓库查找,发现需要的构件之后,下载到本地仓库再使用。
如果本地仓库和远程仓库都没有需要的构件,Maven则会报错。
中央仓库:
中央仓库是一个默认的远程仓库,Maven的安装文件自带了中央仓库的配置。中央仓库的id为central。
配置自定义远程仓库:在很多情况下,默认的仓库可能无法满足需求,可能需要的构件位于另一个远程仓库中,这就需要在POM中配置远程仓库。如:
<!--远程仓库配置--> <repositories> <repository> <id>jboss</id>// 唯一的id,若于中央仓库id相同,则会覆盖中央仓库的配置 <name>JBOSS Repository</name>//仓库名称 <url>http://repository.jboss.com/maven2</url>// 基于http协议,可以在浏览器中直接打开 <releases> <enabled>true</enabled>//开启发布版本下载支持 <updatePolicy>daily</updatePolicy>// 从远程仓库检查更新的频率,默认daily,还有never(从不)、always(每次构建)、interval:X(每隔X分钟) <checksumPolicy>ignore</checksumPolicy>//检查校验和文件的策略,当构建部署到Maven仓库中时会同时部署对应的校验和文件。在下载构件的时候Maven 会验证校验和文件,如果校验和失败怎么办?warn:输出警告信息,fail:让构建失败,ignore:忽略校验和错误 </releases> <snapshots> <enabled>false</enabled>//关闭仓库快照版本的下载支持 </snapshots> <layout>default</layout>//仓库的布局是Maven3的默认布局 </repository> </repositories>
远程仓库的认证:如访问私服需要配置认证信息才能访问。认证信息必须配置在setting文件中。
<servers> <server> <id>jboss</id> // 需要认证的远程仓库的id <username>repouser</username>// 认证用户名 <password>repopwd</password>//认证密码 </server> </servers>
部署到远程仓库中:部署到远程仓库中,供其他团队成员使用。在POM文件中做如下配置,然后执行mvn clean deploy,Maven就会将项目构建输出的构件部署到配置对应的远程仓库。
<!--部署到远程仓库的配置--> <distributionManagement> <repository>//发布版本的仓库 <id></id> <name></name>//仓库名称其实只是为了方便人阅读 <url></url> </repository> <snapshotRepository>//快照版本的仓库 <id></id> <name></name> <url></url> </snapshotRepository> </distributionManagement>
仓库解析依赖:若依赖的版本时RELEASE或者LASTED(都不推荐使用),它们分别对应了仓库中存在的该构件的最新发布版本和最新版本(包含快照),则基于更新策略读取所有仓库的元数据groupId/artifactId/maven-metadata.xml,将其与本地本地仓库的对应元数据合并后,计算出RELEASE或者LASTED最新快照版本的值,然后基于该值检查本地仓库,或者从远程仓库下载。
镜像
如果仓库X可以提供仓库Y存储的所有内容,那么就可以认为X是Y的一个镜像。换句话说,任何一个可以从仓库Y的构件,都能够从它的镜像中获取。关于镜像最常见的用法是结合私服。由于私服可以代理任何外部的公共仓库(包括中央仓库),因此,对于组织内部的Maven用户来说,使用一个私服地址就等于使用了所有需要的外部仓库,这可以将配置集中到私服,从而简化Maven本身的配置。在这种情况下,任何需要的构件都可以从私服获得,私服就是所有仓库的镜像。这时可以在setting.xml中这样配置:
<mirrors> <mirror> <id>mirrorId</id> <mirrorOf>*</mirrorOf>// 匹配所有仓库,其他如external:匹配所有不在本机上的远程仓库;repo1,repo2:匹配仓库repo1和repo2;*,!repo1:所有远程仓库除了repo1 <name>Human Readable Name for this Mirror.</name> <url>http://my.repository.com/repo/path</url> </mirror> </mirrors>
补充:如遇包下载不了的情况,请在pom中配置国内的远程仓库地址(阿里云)
<repositories> <repository> <!-- Maven 自带的中央仓库使用的Id为central 如果其他的仓库声明也是用该Id就会覆盖中央仓库的配置 --> <id>alirep</id> <name>Central Repository</name> <url>http://maven.aliyun.com/nexus/content/groups/public/</url> <layout>default</layout> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories>
阿里云仓库:https://developer.aliyun.com/mvn/guide
生命周期和插件
生命周期
Maven的生命周期就是为了对所有的构件过程进行抽象和统一,maven从大量项目和构件工具中学习和反思,然后总结了一套高度完善的、易扩展的生命周期。
这个生命周期包括了清理、初始化、编译、测试、打包、验证、部署和站点生成等几乎所有的构件步骤
生命周期包括清理-->编译-->测试-->打包-->部署等,其中每一步其实都是交给插件去完成,如针对编译的插件maven-compiler-plugin,针对测试的插件maven-surefire-plugin等。
Maven定义的生命周期和插件机制一方面保证了所有Maven项目有一致的构建标准,另一方面又通过默认插件简化和稳定了实际项目的构建。用户也可以自行编写插件来定义构建行为。
三套相互独立的生命周期:clean、default、site。各个生命周期之间互不影响。
每个生命周期都包含一些阶段(phase),并且后面的阶段依赖于前面的阶段,即调用生命周期后面的阶段时,前面的阶段都会执行。
clean生命周期
1、pre-clean:执行一些清理前需要完成的工作
2、clean:清理上一次构建生成的文件
3、post-clean:执行一些清理后需要完成的工作
default生命周期
default生命周期定义了真正构建时所需要执行的所有步骤,它是生命周期中最核心的部分。
1、validate
2、initialize
3、generate-sources
4、process-sources
5、generate-resources
6、process-resources:处理项目主资源文件。一般来说,是对src/main/resources目录的内容进行变量替换等工作后,复制到项目输出的主classpath目录中
7、compile:编译项目的主代码,一般来说,是编译src/main/java目录下的Java文件至项目输出的主classpath目录中
8、process-classes
9、generate-test-sources
10、process-test-sources
11、generate-test-resources
12、process-test-resources:处理项目测试资源文件。一般来说,是对src/test/resurces目录的内容进行变量替换等工作后,复制到项目输出的测试classpath目录中
13、test-compile:编译项目的测试代码。一般来说,是编译src/test/java目录下的Java文件至输出的测试classpath目录中
14、process-test-classes
15、test:使用单元测试框架运行测试,测试代码不会被打包或部署
16、prepare-package
17、package:接受编译好的代码,打包成可发布的格式,如jar
18、pre-integration-test 19、integration-test 20、post-integration-test
21、verify
22、install:将包安装到本地Maven仓库,供本地其他Maven项目使用
23、deploy:将最终的包复制到远程仓库,供其他开发人员和Maven使用
site生命周期
site生命周期的目的是建立和发布站点,Maven能够基于POM所包含的信息自动生成一个友好的站点,方便团队交流和发布项目信息。
1、pre-site:执行一些在生成项目站点之前需要完成的工作
2、site:生成项目站点文档
3、post-site:执行一些在生成项目站点周需要完成的工作
4、site-deploy:将生成的站点发布到服务器上
命令行与生命周期
从命令行执行Maven任务最主要的方式就是调用Maven的生命周期阶段。
mvn clean:调用clean生命周期的clean阶段,实际执行的阶段为pre-clean和clean
mvn test:调用default生命周期的test阶段,实际执行的阶段为validate、initialize等直到test及之前的所有阶段,所以执行测试的时候,项目的代码能够自动得以编译
mvn clean install:该命令调用clean生命周期的clean阶段和default生命周期的install阶段。实际执行的阶段为pre-clean、clean;validate...install。在执行真正的项目
构建之前清理项目是一个很好的实践。
mvn clean deploy site-deploy:该命令调用clean生命周期的clean阶段,default生命周期的deploy阶段以及site生命周期的site-deploy阶段。实际执行的阶段为pre-clean、clean;
default生命周期的所有阶段和site生命周期的所有阶段。
插件和生命周期的绑定关系
Maven的核心仅仅定义了抽象的生命周期,具体的任务是交由插件去完成的,插件以独立的构件形式存在。对于插件本身,为了能够复用代码,它往往能够完成多个任务。
如:maven-dependency-plugin,它能够分析项目依赖,找出无用依赖(mvn dependency:analyze)、列出项目的依赖树,分析依赖来源(mvn dependency:tree)、列出所有已解析
的依赖(mvn dependency:list)。每一个功能就是一个插件目标(:号后面对应插件目标)
Maven的生命周期和插件相互绑定,用以完成实际的构建任务。具体而言,是生命周期的阶段与插件的目标相互绑定,以完成某个具体的构建任务。例如maven-compiler-plugin这
一插件的compile目标与default生命周期的compile这一阶段绑定,用以完成对应的项目编译任务。
内置绑定:为了能让用户几乎不用任何配置就能构建maven项目,maven在核心为一些主要的生命周期阶段绑定了很多插件的目标,当用户通过命令行调用生命周期阶段的时候,
对应的插件目标就会执行相应的任务。
如default生命周期的内置插件绑定关系及具体任务:
生命周期阶段 | 插件目标 | 执行任务 |
process-resources | maven-resources-plugin:resources | 复制主资源文件至主输出目录 Copy the resources to the output directory for including in the JAR. |
compile | maven-compiler-plugin:compile | 编译主代码至主输出目录 Compiles Java sources. |
process-test-resources | maven-resources-plugin:testResources | 复制测试资源文件至测试输出目录 |
test-compile | maven-compiler-plugin:testCompile | 编译测试代码至测试输出目录 |
test | maven-surefire-plugin:test | 执行测试用例 |
package | maven-jar-plugin:jar | 创建项目jar包 |
install | maven-install-plugin:install | 将项目输出构件安装到本地仓库 Install the built artifact into the local repository. |
deploy | maven-deploy-plugin:deploy | 将项目输出构件部署到远程仓库 Deploy the built artifact to the remote repository. |
自定义绑定:除了内置绑定之外,用户还能够自己选择将某个插件目标绑定到生命周期的某个阶段上,这种自定义绑定方式能让Maven项目在构建过程中执行更富特色的任务。
如创建项目的源码jar包:
<!--创建项目源码的jar包,最终会创建一个以-sources.jar结尾的源码文件包--> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>2.1.1</version> <executions> <execution>// 每个execution用来配置执行一个任务 <id>attach-sources</id> // 一个id为attach-sources的任务 <phase>verify</phase> // 将插件目标绑定到default生命周期的verify阶段 <goals>jar-no-fork</goals>// 插件目标,将项目的主代码打包成jar文件 </execution> </executions> </plugin>
聚合与继承
一般来说,一个项目的子模块都应该使用相同的groupId,如果它们一起开发和发布,还应该使用同样的version,此外,它们的artifactId还应该使用一致的前缀,以方便同其他项目区分。
一个多模块的项目有一个父模块,也叫聚合模块,它仅仅包含一个pom文件,不像其他模块有src/main/java等目录,聚合模块仅仅是帮助聚合其他模块的工具,它本身并无实质的内容。
父模块的pom文件中packaging标签值为pom。
<packaging>pom</packaging>
POM的继承:一个项目的POM之间可能会有很多相同的配置,一些相同的jar包依赖,插件等配置,pom的继承能让我们抽取出重复的配置。
子模块中关于父模块的配置:
<parent> <groupId>com.suning.epp</groupId> <artifactId>sfuibs</artifactId> <version>1.0.0</version> <relativePath>../pom.xml</relativePath>//父模块POM的相对路径,Maven默认父POM在上一层目录下 </parent>
另外子模块中会隐式从父模块继承groupId和version两个元素,如果子类这两个元素不同,可以在子POM中显式声明。子artifactId是必须显示声明的。
可继承的POM元素(只列举常见的):
groupId:项目组Id
version:项目版本
distributionManagement:项目的部署配置
properties:自定义的Maven属性
dependencies:项目的依赖配置
dependencyManagement:项目的依赖管理配置
repositories:项目的仓库配置
build:包括项目的源码目录配置、输出目录配置、插件配置、插件管理配置等
依赖管理:
虽然子模块可以继承父模块POM中的dependencies元素,但是如果将所有的依赖都放在父POM中,如果一个子模块根本不需要这些依赖,那么这么做就是合理的。Maven提供的dependencyManagement元素既能让子模块继承到父模块的依赖配置,又能保证子模块依赖使用的灵活性。在dependencyManagement元素下的依赖声明不会引入实际的依赖,不过它能够
约束dependencies下的依赖使用。
父模块中dependencyManagement声明的依赖既不会给父模块引入依赖,也不会给它的子模块引入依赖,不过这段配置是会继承的。子模块只需要配置简单的groupId和artifactId就能获得对应的依赖信息,其他如version和scope从父模块中继承。这样能够避免子模块之间版本不一致的情况,还可以降低依赖冲突的概率。
如果子模块不声明依赖的使用,即使该依赖已经在父POM的dependencyManagement中声明了,也不会产生任何实际的效果。
插件管理:
类似Maven也提供了pluginManagement元素帮助管理插件(统一版本)。在该元素中配置的依赖不会造成实际的插件调用行为,当子POM中配置了真正的plugin元素,并且其
groupId和artifactId和pluginManagement中配置的插件匹配时,pluginManagement的配置才会影响实际的插件行为。
如,父POM中pluginManagement的配置:
<build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>2.1.1</version> <executions> <execution> <id>attach-sources</id> <phase>verify</phase> <goals>jar-no-fork</goals> </execution> </executions> </plugin> </plugins> </pluginManagement> </build>
子POM只需要声明plugin的groupId和artifactId即可
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> </plugin> </plugins> </build>
约定优于配置
Maven提倡约定优于配置,也是Maven的核心设计理念之一,使用约定可以减少大量配置。如Maven约定项目的:
源码目录为src/main/java
编译输出目录为target/classes/
打包方式为jar(web项目需要显式指定为war)
包输出目录为target/
其实任何一个Maven项目都隐式继承自超级POM。类似于Java中的Object,Maven3中超级POM位于$MAVEN_HOME/lib/maven-model-builder-x.x.x.jar中的
org/apache/maven/pom-4.0.0.xml路径下。
反应堆(Reactor):
在一个多模块的Maven项目中,反应堆是指所有模块组成的一个构建结构。对于单模块的项目,反应堆就是该模块本身,对于多模块项目来说,反应堆就包含了各模块之间继承
与依赖的关系,从而能够自动计算出合理的模块构建顺序。
反应堆的构建顺序:Maven按照父POM中modules标签的顺序读取POM,如果该POM没有依赖模块,那么就构建该模块,否则就先构建其依赖模块,如果该依赖还依赖其他模块,
则进一步先构建依赖的依赖。Maven不允许循环依赖,如果出现循环依赖,就会报错。
构建Web应用
Maven属性${project.groupId}、${project.version}分别表示项目的groupId和version。
POM中fileName元素的配置用来标识项目生成的主构建的名称,该元素的默认值已在超级POM中指定,值为${project.artifactId}-${project.version},一般生产环境要显示指定。
使用jetty-maven-plugin插件进行项目的打包和部署。它会周期性地检查项目内容,发现变更后自动更新到内置的jetty Web容器中。它默认很好地支持了Maven项目的目录结构,在
通常情况下,我们只需要直接在IDE中修改源码,IDE能够执行自动编译,jetty-maven-plugin发现编译后的文件变化后,自动将其更新到jetty容器,即时生效。
<plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>jetty-maven-plugin</artifactId> <version>7.1.6</version> <configuration> <scanIntervalSeconds>10</scanIntervalSeconds>// 每10秒扫描项目变更实现热部署,默认是0秒即不扫描 <webAppConfig> <contextPath>/test</contextPath>// 部署后的上下文 </webAppConfig> </configuration> </plugin>
启动,不过为了能在命令行直接运行mvn jetty:run,用户需要在setting.xml配置文件的pluginGroups标签加上如下:
<pluginGroups> <pluginGroup>org.mortbay.jetty</pluginGroup> </pluginGroups>
如果希望使用其他端口:
mvn jetty:run -Djetty.port=9999
灵活构建
一个优秀的构建系统必须足够灵活,它应该能够在不同的环境下识别所在的环境并使用正确的配置。Maven为了支持构建的灵活性,内置了三大特性,即属性、Profile和资源过滤。
Maven属性
内置属性:两个常用的内置属性——${basedir}表示项目根目录,即包含pom.xml文件的目录;${version}表示项目版本
POM属性:用户可以使用该类属性引用POM文件中对应元素的值。例如${project.artifactId}就对应了<project>中<artifactId>元素的值。常用的POM属性包括:
${project.build.sourceDirectory}:项目的主源码目录,默认为src/main/java/
${project.build.testSourceDirectory}:项目的测试源码目录,默认为src/test/java/
${project.build.target}:项目构建输出目录,默认为target/
${project.outputDirectory}:项目主代码编译输出目录,默认为target/classes/
${project.testOutputDirectory}:项目测试代码编译输出目录,默认为target/test-classes/
${project.groupId}:项目的groupId
${project.artifactId}:项目的artifactId
${project.version}:项目的version,与${version}等价
${project.build.finalName}:项目打包输出文件的名称,默认为${project.artifactId}-${project.version}
自定义属性:用户可以在POM配置文件的properties元素下自定义属性
Setting属性:与POM属性同理,用户使用setting. 开头的属性引用setting.xml文件中XML元素的值,如常用${setting.localRepository}指向用户本地仓库的地址
Java系统属性:如${user.home}执行了用户目录
环境变量属性:所有环境变量都可以使用以.env开头的Maven属性引用,例如${env.JAVA_HOME}指代了JAVA_HOME的值。
资源过滤:
如在POM文件中根据不同的Profiles配置一些不同的properties属性,但是在项目配置文件properties中使用${db.Name}来获取实际环境下的属性值。
<profile> //针对开发环境的一些配置 <id>pre</id> <properties> <db.userName>xxx</db.userName> <db.password>xxx</db.password> </properties> </profile>
由于maven属性默认只有在POM文件中才会被解析。也就是说,${db.Name}在POM中会解析成xxx,但是如果放到src/main/resources目录下的文件中,构建的时候它仍然还是${db.Name}。
因此需要让maven解析资源文件中的maven属性,需要在POM中加上一些配置,能够让maven-resources-plugin插件在将项目资源文件复制到输出目录中的时候能够解析资源文件中
的maven属性,即开启资源过滤。在build标签下加上以下配置即可:
<resources>// 指定主资源目录 <resource> <directory>${project.basedir}/src/main/resources</directory>// 也可以是自定义资源目录 <filtering>true</filtering> // 开启资源过滤,解析其中的$占位符。这些参数值取自properties属性和filters元素中列出的配置文件的属性 </resource> </resources> <testResources>// 为测试资源目录开启资源过滤 <testResource> <directory>${project.basedir}/src/test/resources</directory> <filtering>true</filtering> // 开启资源过滤,解析其中的$占位符 </testResource> </testResources>
Profile
不同环境的构建很有可能是不同的,如各种配置文件的配置。使用profile用户可以是有个很多方式激活profile,以实现构建在不同环境下的移植。
如:使用maven-resources-plugin插件在不同的环境下构建时复制不同目录下的资源文件到 编译后的target/classess目录下的配置:
<profiles> <profile> <id>sit</id> <activation> // 默认激活此profile <activeByDefault>true</activeByDefault> </activation> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>2.5</version> <executions> <execution> <id>copy-resources</id> <phase>validate</phase> // 绑定到default生命周期的validate阶段 <goals> <goal>copy-resources</goal> // 目标是复制资源 </goals> <configuration> <outputDirectory>${basedir}/target/classess</outputDirectory> // 目的地 <resources> <resource> <directory>${basedir}/src/main/resources/config/sit</directory> // 从哪复制 <filtering>true</filtering> // 开启资源过滤,替换其中的$占位符 <includes> // 复制哪些文件 <include>*.xml</include> </includes> </resource> </resources> </configuration> </execution> </executions> </plugin> </plugins> </build> </profile> ... </profiles>
激活Profile
1、命令行激活,使用mvn命令行参数-P加上profile的id来激活profile,若有多个id,之间以逗号隔开,如:mvn clean install -Ppre
2、默认激活,即上面的activeByDefault
3、setting配置文件激活,配置activeProfiles标签,值为激活的profile
filter规则
maven通过过滤器来修改部署时的不同配置:
<build> <filters> // 将指定目录下的配置文件中的配置加载到pom的properties中 <filter>${basedir}/src/main/resources/config/${active.env}/config.properties</filter> </filters> // 指定资源文件夹目录 <resources> <resource> <directory>${basedir}/src/main/resources</directory> <filtering>true</filtering> // 开启资源过滤,此时整个resources文件夹的资源文件都会被过滤解析${} <!--排除下面目录下的文件,打包时这里面的文件不会被打进war包--> <excludes> <exclude>config/**</exclude> </excludes> </resource> </resources> </build> <profile> <id>pre</id> <properties> <active.env>pre</active.env> // 激活的环境变量名作为属性 </properties> ... </profile>
filter的作用相当于将指定环境下的配置文件中的配置加载到POM文件中,类似于POM文件中的properties标签
再加上开启资源过滤,那么在/src/main/resources中的文件就可以通过使用${},来获取在filter标签中的config.properties中的配置了。
需要注意的是在Springboot项目中,springboot把默认的占位符号${}改成了@,所以需要在POM properties中指定
<resource.delimiter>${}</resource.delimiter> 或者在Maven resources插件中指定占位符:
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<delimiters>
<delimiter>${}</delimiter>
</delimiters>
<useDefaultDelimiters>false</useDefaultDelimiters>
<filters> // filter也可以在此定义
<filter>${basedir}/src/main/resources/config/${profiles.active}/config.properties </filter>
</filters>
</configuration> </plugin>
否则会导致配置文件中的占位符不能解析替换
上面的方式会让整个resources文件下的资源文件全部被过滤,若只想过滤部分资源文件,则定义两个resources即可:
<resources> <!--资源目录--> <resource> <directory>${basedir}/src/main/resources</directory> <!--需要过滤资源目录,相当于解析其中的$占位符--> <includes> <include>application.properties</include> </includes> <filtering>true</filtering> </resource> <resource> // 其他资源文件不会被过滤 <directory>${basedir}/src/main/resources</directory> <!--打包时排除下面目录下的文件,这里面的文件不会被打进war包--> <excludes> <exclude>config/**</exclude> </excludes> </resource> </resources>
总结: 实现灵活构建的步骤
1)在项目内或项目外配置各个环境的配置文件
2)在父模块或部署模块的pom文件中,build元素内添加 filters(即各环境下的config.properties)、resources目录及filtering为true(解析resources下的配置文件内的占位符)
2)在部署模块的pom中配置profiles,在构建时将指定的环境下的某些配置文件复制到 target/classes 下,并解析配置文件中的占位符
Web资源过滤
在Web项目中,资源文件同样位于src/main/resources/目录下,它们经过处理后会位于WAR包的WEB-INF/classes目录下,这也是Java代码编译打包后的目录。也就是说这类资源文件在
打包后会位于应用程序的classpath中。Web项目还有另外一类资源文件,默认它们的源码位于src/main/webapp/目录,经打包后位于WAR包的根目录。例如,一个Web项目的css源码文件在
src/main/webapp/css目录,项目打包后可以在WAR包的css/目录下找到对应的css文件。这一类资源文件称做web资源文件,它们在打包后不位于应用程序的classpath中。
与一般的资源文件一样,web资源文件默认不会被过滤。开启一般资源文件的过滤也不会影响到web资源文件。 不过有的时候我们可能希望在构建项目的时候,为不同的客户使用不一样
的资源文件(如客户的logo不同或者css主题不同)。这时候可以在web资源文件中使用Maven属性,例如用${client.logo}表示客户的图片,然后使用profiles,并在每个profiles的properties分
别定义这些Maven属性的值,然后选择激活某个profile。
最后需要配置maven-war-plugin对src/main/webapp/这一web资源目录开启过滤,使用includes指定要过滤的文件,如:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.2</version> <configuration> <warName>sfuibs-${maven.build.timestamp}</warName> <webResources> <resource> <filtering>true</filtering> <directory>src/main/webapp</directory> //要过滤的Web资源目录 <includes> // 为Web资源目录下的所有css和js开启过滤 <include>**/*.css</include> <include>**/*.js</include> </includes> </resource> </webResources> </configuration> </plugin>