Maven(三):Maven的组成详解

坐标和依赖

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元素声明一个依赖。每个依赖包含以下元素:

  • groupIdartifactIdversion:基本坐标;
  • 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 testmvn 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,可以看到最后面过滤了三类文件。

然后重新打包,就可以运行了。

参考文献:

  1. Invalid signature file digest for Manifest main attributes

参考文献

  1. 通过IDEA 快速 生成 可执行 jar包(超级简单) 没成功,但是过程有点意思。
  2. Maven3种打包方式之一maven-shade-plugin的使用
  3. [1119]使用maven插件maven-shade-plugin对可执行java工程及其全部依赖jar进行打包
  4. Maven插件 maven-shade-plugin 讲了shade插件每部分元素是干什么的,深入力荐。
  5. Apache-Maven-Project Resource Transformer 插件的Transformer元素详解
  6. IDEA Maven Helper插件(详细使用教程)
  7. Maven在pom.xml文件中添加自定义远程仓库
  8. 【Maven】Maven之远程仓库的认证配置

猜你喜欢

转载自blog.csdn.net/wlh2220133699/article/details/131291935
今日推荐