文章目录
- 坐标和依赖
- 坐标详解
- 依赖
- 配置
- 依赖范围
- 传递性依赖
- 依赖调节
- 可选依赖
- 优化
- 排除依赖
- 归类依赖
- 优化依赖
- 仓库
- 本地仓库
- 远程仓库
- 仓库镜像
- 常用搜索地址
- 生命周期与插件
- 三套生命周期
- clean生命周期
- default生命周期
- site生命周期
- 插件
- 聚合与继承
- 更加灵活的构建
- 常见问题
- 使用jdk来运行,而不是用jre
- Invalid signature file digest for Manifest main attributes
- 参考文献
坐标和依赖
Maven的核心功能之一:管理项目依赖
Maven中有构件的概念,其实就是指我们平时用的jar、war等文件。
Maven世界中有数量非常巨大的构件,通过坐标,我们可以唯一标识一个构件。
Maven中构件的坐标包括以下元素:
- groupId
- artifactId
- version
- packaging
- classifier
比如说,当需要使用Java5平台上的TestNG构件的5.8版本时,就告诉Maven,去给我按照一下坐标搜索:
groupId=org.testng;
artifactId=testng;
version=5.8;
classifier=jdk1.5;
Maven就会根据以上坐标,去自己的中央仓库去找。
而我们在开发自己项目的时候,也需要为其定义适当的坐标。
坐标详解
<groupId>org.wlh</groupId>
<artifactId>helloworld</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
上面是我一个自定义项目的信息。
- groupId:该项目隶属的实际父项目,一个父项目下可能有多个子项目,就是groupId一样,但是artifactId不一样。或者是填公司等组织信息;
- artifactId:实际项目名称
- version:版本
- packaging:该项目的打包方式
- classifier:帮助定义构建输出的一些附属构件,不能直接定义项目的classifier,而是由附加插件帮助生成。
依赖
配置
一个正式的依赖声明如下:
<project>
...
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
...
</project>
dependencies元素下包含一个或者多个dependency元素,每个dependency元素声明一个依赖。每个依赖包含以下元素:
- groupId、artifactId、version:基本坐标;
- type:依赖的类型,对应于项目坐标中的packaging;默认值是jar;
- scope:依赖的范围;
- optional:标记依赖是否可选;
- exclusions:用来排除传递性依赖;
依赖范围
依赖范围就是指这个依赖生效的范围。
Maven在编译、测试、运行项目的时候,分别需要一套classpath。就是说,编译的时候使用编译classpath、测试的时候使用测试的classpath,以此类推。
Maven有以下几种依赖范围:
- compile:编译依赖范围。默认值。使用compile的依赖,对于编译、测试、运行这三种classpath都有效,简单说就是编译、测试、运行的时候都需要用到这个依赖;
- test:测试依赖范围。只有在编译测试代码 + 运行测试代码的时候才需要;
- provided:已提供依赖范围。编译和测试时使用,运行时无效;
- runtime:运行时依赖范围。测试和运行时有效,编译主代码时无效;
- system:系统依赖范围。与provided一致,唯一的区别是system的依赖必须通过systemPath元素显式的指定依赖文件的路径,与本机是强绑定,会导致不可移植,慎用。systemPath中可以引用环境变量。
- import:导入依赖范围。
大概了解下吧,这个会影响打包时候是否把该依赖打进去的问题,test、provided这种就不用打进去了嘛,反正运行的时候也不需要。
传递性依赖
什么是传递性依赖?
项目所依赖的依赖本身也依赖其他依赖。
项目A有一个compile范围的依赖B,而B有一个compile范围的依赖C,那么C就称为是A的compile范围依赖,也是A的一个传递性依赖。
Maven的传递性依赖机制,可以再让你使用B的时候,不需要知道B依赖了什么,也不用担心会引入多余的依赖,Maven会自动帮你管理,它会去解析B,把那些必要的间接依赖,以传递性依赖的形式隐式的引入到当前的项目中去。
传递性依赖的依赖范围?
横向是第一直接依赖,就是B在A里的范围;
纵向是第二直接依赖,就是C在B中的范围。
举个例子,当第二直接依赖范围是test的时候,依赖C将不会传递至项目A中。
这个感觉大概了解下,有用到的时候再说吧。
依赖调节
Maven引入的传递性依赖机制,一方面大大简化和方便了依赖的声明,另一方面,大部分情况下我们只需要关心项目的直接依赖是什么,而不用考虑这些直接依赖会给项目引入什么样的传递性依赖。
但是,当传递性依赖造成问题的时候,我们需要清楚的知道这个传递性依赖是被哪个直接依赖所引入的。
依赖调节第一原则:路径最近者优先。
例如,项目A有这样的依赖关系:
- A -> B -> C -> X(1.0);
- A - > D -> X(2.0)
由于后者的路径长度更短,所以X(2.0)会被解析使用。
依赖调节第二原则:第一声明者优先。
在依赖路径长度相等的前提下,在POM中依赖声明的顺序决定了谁会被解析使用,顺序最靠前的那个依赖优先。
可选依赖
假设有这样的依赖关系,项目A依赖于项目B,项目B可选依赖于X和Y,如果这几个项目的依赖范围都是compile,那么X和Y都应该是A的可传递依赖。但是由于X和Y是B的可选依赖,所以实际上X和Y不会传递给A。
为什么会有可选依赖这种东西呢?
可能项目B实现了两个特性,其中的特性一依赖X,特性二依赖Y,但是两个特性是互斥的,不可能同时使用两个特性。
比如说B是个工具包,它支持多种数据库,MySQL、DB2等,在构建工具包的时候肯定是需要这两种数据库的驱动包,但是每次实际上只需要一种驱动包。
如何标明是可选依赖?
使用<optional>
元素
<dependency>
<groupId>...</groupId>
<optional>true</optional>
</dependency>
理想情况下,是不应该使用可选依赖的。
优化
排除依赖
项目A依赖项目B,但是由于一些原因,不想引入传递性依赖C,而是想自己显式的声明对于项目C的直接依赖。
这时候该怎么做呢?
首先使用exclusions元素来排除依赖,然后dependency再显式引入项目C即可。
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
</exclusion>
</exclusions>
</dependency>
exclusions中可以包含一个或者多个exclusion元素,代表着可以排除一个或者多个传递性依赖。
需要注意的是,声明exclusion的时候只需要指定groupId和artifactId,不需要指定version元素。因为没必要,Maven解析后,直接依赖里不可能存在版本不同的相同依赖。所以通过groupId和artifactId就可以实现唯一定位了。
归类依赖
其实就是使用properties元素来定义属性,然后通过${属性}
的方式来调用。
最突出的用法就是用来统一定义依赖的版本,一般来讲,一类依赖的版本都应该是一样的,比如统一Flink的版本。
<properties>
<flink.version>1.13.5</flink.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-java</artifactId>
<version>${flink.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
这种用法还是很实用的。
优化依赖
MAVEN会自动解析所有项目的直接依赖和传递性依赖,并根据规则正确判断每个依赖的范围,对于一些依赖冲突,也能根据规则进行调节,以确保任何一个构件都有唯一一个版本在最终的依赖中存在。这些工作之后,最后得到的那些依赖被称为已解析依赖(resolved dependency)。
可以运行mvn dependency:list
来查看当前项目的已解析依赖。
这个好像IDE也能做。
运行mvn dependency:tree
来查看当前项目的依赖树。
可以通过解析树,来查看某个依赖是通过哪条传递路径被引进来的。这个IDE也支持。
运行mvn dependency:analyze
来辅助分析当前项目的依赖使用情况。
这个功能很重要,暂时没有在IDE上发现功能入口。
analyze的结果分两部分:
- Used undeclared dependencies:项目中使用到的,但是没有显式声明的依赖。如果存在这种依赖,需要显式申明一下;
- Unused declared dependencies:项目中未使用,但是显式声明了的依赖。但是这种依赖不能简单的直接删除,因为analyze只会分析编译主代码和测试代码是用到的依赖,执行测试和运行时需要的依赖它分析不出来,所以不能盲目直接删除,要自己仔细瞅瞅。
可以在IDEA中安装Maven Helper插件,来图形化查看依赖冲突情况,并支持以列表形式和树形式查看所有依赖。使用方法详见参考文献6。
其入口藏得比较隐蔽,就是pom.xml下方多了个Dependency Analyzer选项卡,点那个就行。
仓库
仓库,存储构件,并提供构件下载。
一个构件,在仓库中的存储路径,跟坐标是大致对应的:groupId/artifactId/version/artifactId-version.packaging。
仓库的分类:本地仓库和远程仓库。
Maven在寻找构件的时候,会先去本地仓库寻找,如果本地仓库没有找到该构件,那就会去远程仓库,从远程仓库中下载构件到本地仓库,然后再从本地仓库调用。
中央仓库,是Maven核心自带的远程仓库。
私服,是另一种特殊的远程仓库。局域网内自建。
除了中央仓库和私服之外,还有很多公开的远程仓库。
本地仓库
Maven默认的本地仓库是放在用户目录下的.m2/repository/
目录下,不论是windows还是linux,都是这样。
如果想修改默认的本地仓库地址的话,有两种方式:
- 修改用户级别settings.xml,位于
.m2/repository/
目录下; - 修改全局层级settings.xml,在maven安装目录下。
修改方式是,在settings元素下,修改localRepository元素:
<settings>
<localRepository>D:\\software\\Maven\\repository\\</localRepository>
</settings>
需要注意的是,默认情况下,.m2/repository/
下的settings.xml是不存在的,如果你想设置用户层级的settings.xml的话,需要从安装目录复制出一个xml放在.m2/repository/
下。
一般来讲,推荐设定用户层级配置文件进行修改,但是个人电脑的话,无所谓,全局设置更方便一些。
至于怎么安装本地jar包到本地仓库,这个之后再查吧。
如何安装本地jar包到本地仓库?
D:/software/IntelliJ_IDEA/plugins/maven/lib/maven3/bin/mvn install:install-file -Dfile=D:/tmp/smartbi-SDK-smartbi-SDK.jar -DgroupId=com.smartbi -DartifactId=smartbi-SDK -Dpackaging=jar -Dversion=1.0
如果下面出现了BUILD SUCCESS字眼的话,那就可以。
远程仓库
可以在repositories元素下自定义远程仓库:
<repositories>
<repository>
<id>Dcm4Che</id>
<name>Dcm4Che</name>
<url>http://www.dcm4che.org/maven2/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
可以使用repository元素定义一个或者多个远程仓库。
任何一个仓库声明的id必须是唯一的,Maven自带的中央仓库使用的id是central。
releases元素与snapshots元素分别用来控制Maven对于发布版和快照版构件的下载。releases为true代表允许下载发布版本的构件,为false代表不允许下载发布版本的构件。
其中,快照版本就是现行版本,是不稳定的版本。
另外,releases元素与snapshots元素还可以通过updatePolicy元素来配置Maven从远程仓库检查更新的频率。暂且不表,用到再说。
远程仓库的认证。
大部分远程仓库是免费的,无需认证即可访问,但是有些是需要认证的,那怎么配置认证信息呢?
在settings.xml中:
<servers>
<server>
<id>my-proj</id>
<username>repo-user</username>
<password>repo-pwd</password>
</server>
</servers>
注意:server元素的id必须与POM中需要认证的repository元素的id完全一致。
部署到远程仓库
怎么把自己生成的构件,部署到远程仓库给大家使用呢?
这里就需要修改项目的pom.xml文件了,在pom中配置distributionManagement元素:
<project>
<distributionManagement>
<repository>
<id>releases</id>
<name>Internal Releases</name>
<url>http://localhost:8081/nexus/content/repositories/releases</url>
</repository>
<snapshotRepository>
<id>Snapshots</id>
<name>Internal Snapshots</name>
<url>http://localhost:8081/nexus/content/repositories/snapshots</url>
</snapshotRepository>
</distributionManagement>
</project>
url可以换成相应的远程仓库地址。
repository元素表示发布版本构件的仓库,snapshotRepository表示快照版本的仓库,其中id是远程仓库的唯一标识。
往远程仓库部署构件的时候,往往需要验证,这个之前提过了,是一样的。
一切配置完成后,命令行运行mvn clean deploy
,Maven就会将项目构建形成的构件部署到对应的远程仓库。如果你构建的是快照版本,那就部署到快照仓库,否则就部署到发布版本仓库。
快照版本与发布版本的区别?
快照版本不稳定,基本是内部开发迭代使用的。
版本名为2.1-snapshot的话,实际上在发布的时候,Maven会在后面打上时间戳,比如2.1-20091214-13就表示2.1快照版本在2009年12月14日的第13次快照,有了这个时间戳,有人调用这个构建版本的时候,Maven就会找到最新时间戳的版本。
这样有两个好处,发布方可以随时部署快照版本到远程仓库,而调用法也可以随时从远程仓库调用,反正每次调用都是最新的,二者之间不用担心版本的差异问题。
当快照版本稳定了,就把快照版本修改为发布版本,比如2.1-snapshot改为2.1.
从仓库解析以来的机制
太长了,暂时不想看。
仓库镜像
镜像就是跟主远程仓库一模一样。
在settings.xml中加入你所选定的远程仓库的镜像(下面代码示例没有用):
<mirrors>
<mirror>
<id>Maven2</id>
<name>Nexus Mirror</name>
<url>http://xxx/repository/maven-public</url>
<mirrorOf>*</mirrorOf>
</mirror>
</mirrors>
<mirrorOf>*</mirrorOf>
是星号的意思是,该镜像配置是所有Maven仓库的镜像,不管是对哪个远程仓库的请求,都会被转移到http://xxx/repository/maven-public。
如果镜像仓库需要认证,那么配置一个id为internal-repository的<server>
,其他一样。
<mirrorOf>repo1,repo2</mirrorOf>
,代表匹配仓库repo1,repo2.。。
<mirrorOf>*,!repo2</mirrorOf>
,匹配所有远程仓库,但是repo2除外。使用感叹号表示排除。
常用搜索地址
几个常用的,功能强大的公共Maven仓库搜索服务。
可以在这里面进行各版本的jar包搜索、pom格式查看以及jar包下载等。
sonatype nexus:https://repository.sonatype.org/
mvn repository:https://mvnrepository.com/ 这个比较好
生命周期与插件
Maven仓库的另外两个核心概念:生命周期和插件。
简单的总结一下:根据命令行的输入来执行插件,进而推进Maven的生命周期。
如mvn package
就表示执行默认生命周期阶段package。而Maven的生命周期是抽象的,其实际行为是由插件来完成的,如package阶段的任务可以由maven-jar-plugin
来完成。
生命周期和插件协同工作,密不可分。
什么是生命周期
生命周期其实就是对构建过程的抽象。每个周期都是构建过程中的一个环节。
这个生命周期包含了项目的清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成等所有构建步骤。
在Maven中,实际的任务都是交由插件来完成的。这种思想模式与设计模式中的模板方法(Template Method)非常相似。模板方法模式在父类中定义算法的整体结构,子类可以通过实现或者重写父类的方法来控制实际的行为,这样既保证了算法有足够的的扩展性,又能严格控制算法的整体结构。
每个构建步骤下,都绑定了一个或多个插件行为。
用户也可以自行编写插件来自定义构建行为。
三套生命周期
Maven的生命周期不是一个整体,而是拥有三套独立的生命周期,分别是clean、default和site。
- clean生命周期:最终目的是清理项目
- default生命周期:最终目的是构建项目;
- site生命周期:建立项目站点。
每个生命周期内都包含一些有顺序的阶段,并且后面的阶段依赖于前面的阶段。
用户与Maven最直接的交互方式就是调用这些生命周期阶段。
以clean生命周期为例,就包含pre-clean、clean和post-clean阶段。
clean生命周期
- pre-clean:执行一些清理前需要完成的工作;
- clean:清理上一次构建生成的文件;
- post-clean:执行一些清理后需要进行的工作。
default生命周期
定义了真正的构建过程中需要执行的所有步骤,是所有生命周期中最核心的地方。
包含的阶段实在太多了,这里只介绍几个重要阶段。
- process-sources:处理项目主资源文件,一般来讲,是对src/main/resources目录的内容进行变量替换等工作后,复制到项目输出的主classpath目录中;
- compile:编译项目的主源码。一般来讲,是吧src/main/java目录下的java文件编译输出至主classpath目录;
- test-compile:编译项目的测试代码,跟compile基本一致;
- test:使用单元测试框架进行测试,测试代码不会被部署或者打包;
- package:将编译好的代码打包成可发布格式,如jar;
- install:将打好的包安装到Maven本地仓库;
- deploy:将打好的包复制到Maven远程仓库。
完整周期可参考:https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html
site生命周期
建立和发布项目站点。
- pre-site:生成站点之前的工作;
- site:生成站点文档
- post-site:生成站点之后的工作
- site-deploy:将生成的站点发布到服务器上。
一些常用的Maven命令跟生命周期的配合:
mvn clean
:调用clean生命周期的pre-clean和clean阶段;mvn clean install
:调用clean生命周期的pre-clean和clean阶段,然后调用default生命周期的从validate到install的所有阶段。在构建项目前进行项目清理,是一个很好的习惯。
插件
Maven的核心分发包很小,只定义了抽象的生命周期,插件可以在需要的时候再额外下载。
涉及的有些复杂,暂且不表。。。
插件构件同样存在于Maven仓库中,
对于Maven官方的插件的话,其groupId都是:org.apache.maven.plugins
下面引入了一个编译插件,里面的configuration表示编译java1.8版本的源文件,生成与1.8版本兼容的字节码文件。
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
在Maven3中,当插件没有声明版本的时候,会解析最新的release,而不是latest,这样子可以避免由于快照频繁更新而导致的插件行为不稳定。
但是,还是推荐显式设定版本。
聚合与继承
粗略看了看,太难了,而且暂时用不到,先搁置。
聚合、继承、反应堆。
更加灵活的构建
这个可以看看,比较有意思。
其他的暂时可以先不用看。
常见问题
使用jdk来运行,而不是用jre
见参考文献用idea中No compiler is provided in this environment. Perhaps you are runningon a JRE,报错信息为:
No compiler is provided in this environment. Perhaps you are running on a JRE rather than a JDK?
这个问题折磨了我很久。
网上资料说是因为mvn的运行环境用的是jre,所以会导致mvn test
、mvn dependency:analyze
等命令会编译失败,报这个错误。
我看网上资料,mvn -version
打印出来的信息里,都是有java_home指向jdk的,但是我的打印出来是这样的:
C:\Users\xxx>mvn -version
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: D:\software\Maven\apache-maven-3.6.3\bin\..
Java version: 1.8.0_111, vendor: Oracle Corporation, runtime: D:\software\jdk1.8\jre
Default locale: zh_CN, platform encoding: GBK
OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"
runtime指向的是jre。
尝试了很久,没有修改成功,最后尝试使用参考文献的方式,在pom.xml中强行指定compile插件的jdk版本以及路径:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<verbose>true</verbose>
<fork>true</fork>
<!--jdk路径-->
<executable>D:\software\jdk1.8\bin\javac</executable>
</configuration>
</plugin>
然后再mvn test
就可以了。。。。但总感觉不是正规方案。。。
Invalid signature file digest for Manifest main attributes
正常使用打包插件打包,而且指定了入口主类,但是有时候打完jar包运行之后,还是会报标题的错。
这是因为jar包的META-INF/文件夹下,混杂了太多的其他文件,比如说ECLIPSE.RSA
, ECLIPSE.SF
。
解决方法很简单,要么手动删除这些文件,要么通过插件配置过滤,举个例子:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.custdata.Main</mainClass>
</transformer>
</transformers>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
上面就是pom.xml中的一个完整的build,可以看到最后面过滤了三类文件。
然后重新打包,就可以运行了。
参考文献:
- Invalid signature file digest for Manifest main attributes
参考文献
- 通过IDEA 快速 生成 可执行 jar包(超级简单) 没成功,但是过程有点意思。
- Maven3种打包方式之一maven-shade-plugin的使用
- [1119]使用maven插件maven-shade-plugin对可执行java工程及其全部依赖jar进行打包
- Maven插件 maven-shade-plugin 讲了shade插件每部分元素是干什么的,深入力荐。
- Apache-Maven-Project Resource Transformer 插件的Transformer元素详解
- IDEA Maven Helper插件(详细使用教程)
- Maven在pom.xml文件中添加自定义远程仓库
- 【Maven】Maven之远程仓库的认证配置