IDEA现场离线环境问题总结
因为现场Java开发的离线环境,经常会导致引入jar等各种环境问题,现将离线开发过程中遇到的环境问题进行总结。
问题1:IDEA控制台报错,如下所示:
Process terminated
答案分析:一般情况下,该种情况是有maven的配置问题导致的。比如配置文件settings.xml中有错误(例如标签不匹配,缺少开始标签或结束标签、缩进或者空格有问题),或者idea中的maven配置有问题,比如对应的setting或repository文件配置不正确,或者在设置离线模式的情况下设定的repository文件夹是空的等情况。
此外,如果maven配置正常,只是设定为离线模式了,但是控制台依然报该错误,可能的原因是项目代码还未引入maven项目,此时的操作是取消离线模式,然后在外网机环境更新对应的依赖即可解决。
问题2:IDEA环境Maven引入报错,如下图1所示:
图1
答案分析:这种报错比较明显就是仓库中缺陷对应的包所导致。在此处要引入传递性依赖的概念。
讲述传递性依赖的概念之前要先说明下依赖范围。
Maven在编译项目主代码的时候需要使用一套classpath,假如编译项目主代码的时候需要用到spring-core包,该文件以依赖的方式被引入到classpath中。其次,Maven在编译和执行测试的时候会使用另外一套classpath。测试程序Junit也以依赖的方式引入到测试使用的classpath中,不同的是这里的依赖范围是test。最后,实际运行Maven项目的时候,又会使用一套classpath,但测试程序Junit就不需要。
依赖范围就是用来控制依赖与这三种classpath(编译classpath、测试classpath、运行classpath)的关系,Maven有以下几种依赖范围:
compile:编译依赖范围。如果没有指定,就会默认使用该依赖范围。使用此依赖范围的Maven依赖,对于编译、测试、运行三种classpath都有效。典型的例子是spring-core,在编译、依赖和运行的时候都需要使用该依赖。
test:测试依赖范围。使用此依赖范围的Maven依赖,只对于测试classpath有效。典型的例子是Junit,它只在编译测试代码及运行测试的时候才需要。
provided:已提供依赖范围。使用此依赖范围的Maven依赖,对于编译和测试classpath有效,但在运行时无效。典型的例子是servlet-api,编译和测试项目的时候需要该依赖,但在运行项目的时候,由于容器已经提供,就不需要Maven重复地引入一遍。
runtime:运行时依赖范围。使用此依赖范围的Maven依赖,对于测试和运行classpath有效,但在编译主代码时无效。典型的例子是JDBC驱动实现,项目主代码的编译只需要JDK提供的JDBC接口,只有在执行测试或者运行项目的时候才需要实现上述接口的具体JDBC驱动。
system:系统依赖范围。该依赖与三种classpath的关系,和provided依赖范围完全一致。但是,使用system范围的依赖时必须通过systemPath元素显示地指定依赖文件的路径。由于此类以来不是通过Maven仓库解析的,而且往往与本机系统绑定,可能造成构建的不可移植,因此应该谨慎使用。systemPath元素可以引用环境变量,如:
<dependency>
<groupId>javax.sql</groupId>
<artifactId>jdbc-stdext</artifactId>
<version>javax.sql</version>
<scope>system</scope>
<systemPath>${java.home}/lib/rt.jar</systemPath>
</dependency>
表1 依赖范围描述
依赖范围(Scope) |
对于编译classpath有效 |
对于测试classpath有效 |
对于运行时classpath有效 |
例子 |
compile |
Y |
Y |
Y |
spring-core |
test |
- |
Y |
- |
JUint |
provided |
Y |
Y |
- |
servlet-api |
runtime |
- |
Y |
Y |
JDBC驱动实现 |
system |
Y |
Y |
- |
本地的,Maven仓库之外的类库文件 |
考虑一个基于Spring Framework的项目,如果不使用Maven,那么在项目中就需要手动下载相关依赖。由于Spring Framework又会依赖于其他开源类库,因此实际中往往会下载一个很大的如 spring-framework-2.5.6-with-dependencies.zip的包,这里包含了所有Spring Framework的jar包,以及所有它依赖的其他jar包。这么往往就引入了很多不必要的依赖。另一种做法是只下载spring-framework-2.5.6.zip 这样一个包,这里不包含其他相关依赖,到实际使用的时候,再根据出错信息,或者查询相关文档,加入需要的其他依赖。很显然,这也是一件非常麻烦的事情。
Maven的传递性依赖机制可以很好地解决这一问题。假如一个项目有一个org.springframework:spring-core:2.5.6的依赖,而实际上spring-core也有自己的依赖,我们可以直接访问位于*仓库的该构件的POM:https://repo1.maven.org/maven2/org/springframework/spring-core/2.5.6/spring-core-2.5.6.pom。该文件包含了一个commons-logging依赖,见代码清单1。
代码清单1 spring-core的commons-logging依赖
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
该依赖没有声明依赖范围,那么其依赖范围就是默认的compile。例如项目TestProgram有一个compile范围的spring-core依赖,spring-core有一个compile范围的commons-logging依赖,那么commons-logging就会成为项目TestProgram的compile范围依赖,commons-logging是项目TestProgram的一个传递性依赖,如下图2所示:
图2 传递性依赖
有了传递性依赖机制,在使用Spring Framework的时候就不用去考虑它依赖了什么,也不用担心引入多余的依赖。Maven会解析各个直接依赖的POM,将那些必要的间接依赖,以传递性依赖的形式引入到当前的项目中。
依赖范围不仅可以控制依赖与三种classpath的关系,还对传递性依赖产生影响。上面的例子中,项目TestProgram对于spring-core的依赖范围是compile,spring-core对于commons-logging的依赖范围是compile,那么项目TestProgram对于commons-logging这一传递性依赖的范围也就是compile。假设A依赖于B,B依赖于C,我们即认为A对于B是第一直接依赖,B对于C是第二直接依赖,A对于C是传递性依赖。第一直接依赖的范围和第二直接依赖的范围决定了传递性依赖的范围,如表2所示,最左边一行表示第一直接依赖范围,最上面一行表示第二直接依赖范围,中间的交叉单元格则表示传递性依赖范围。
表2 依赖范围影响传递性依赖
|
compile |
test |
provided |
runtime |
compile |
compile |
------ |
------ |
compile |
test |
test |
------ |
------ |
test |
provided |
provided |
------ |
provided |
provided |
runtime |
runtime |
------ |
------ |
runtime |
为了能更好地帮助读者理解表1,再举个例子。项目TestProgram有一个com.icegreen:greenmail:1.3.1b的直接依赖,我们说这是第一直接依赖,其依赖范围是test;而greenmail又有一个javax.mail:mail:1.4的直接依赖,我们说这是第二直接依赖,其依赖范围是compile。显然javax.mail:mail:1.4是项目TestProgram的传递性依赖,对照表1可以知道,当第一直接依赖范围为test,第二直接依赖范围是compile的时候,传递性依赖的范围是test,因此javax.mail:mail:1.4是项目TestProgram的一个范围是test的传递性依赖。
仔细观察一下表2,可以发现这样的规律:当第二直接依赖范围的范围是compile的时候,传递性依赖的范围与第一直接依赖的范围一致;当第二直接依赖的范围是test的时候,依赖不会得以传递;当第二直接依赖的范围是provided的时候,值传递第一直接依赖范围也为provided的依赖,且传递性依赖的范围同样为provided;当第二直接依赖的范围是runtime的时候,传递性依赖的范围与第一直接依赖的范围一致,但compile例外,此时传递性依赖的范围为runtime。
在传递性依赖中导致依赖不同版本的jar包,如图1中spring-cloud-starter中有2.2.4.RELEASE和2.2.1.RELEASE两个版本被间接依赖。
建议在现场开发的时候,先在外网机环境上更新好对应的maven库。外网及环境maven依赖没有报错的情况下,将IDEA环境设定为Work offline模式,执行maven的clean、compile和package命令(如下图3),如能成功执行则表明外网机的maven库jar包更新完整。否则需要关闭work offline模型,并多次执行Reload All Maven Projects(如下图3左上角)命令,保证maven更新了完整的jar包,因为外网机环境可以直接连接仓库导致本地Maven库可能并未保存需要的jar包。
图3: maven命令操作界面标识
问题3:IDEA下载maven依赖拨错: Could not transfer artifact org.springframework.boot:spring-boot-maven-plugins*
答案分析:报这个错的原因是证书校验出现了问题,解决方法如下:
File->settings->Build->Build Tools->Maven->Runner。修改VM Option换上如下的参数(这样的设置可以跳过证书检查,接下来maven clean,compile,再install就可以解决该问题了)
-Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true
问题4:IDEA 错误: 找不到或无法加载主类
答案分析:可能的原因:1) 未能成功编译。操作:Build->Rebuild Project。一般情况下重新编译后即可正常启动。
2) 缓存问题。操作:File->Invalidate Caches/Restart 。选择Invalidate and REstart 或者选择Invalidate,然后清除掉缓存,接着Rebuild Project。
问题5:1) 明明maven库里有对应的jar包,但项目代码在却找不到。
2) 项目有以下在编译时提示类似以下报错:
Cannot access central (https://repo.maven.apache.org/maven2) in offline mode and the artifact dom4j:dom4j:pom:1.6.1 has not been downloaded from it before.
答案分析:解决方法,直接删除本地仓库中对应jar包所属文件夹的以.lastUpdated和.repositories为后缀的文件(没有以.lastUpdated为后缀的,仅删除以.repositories为后缀的文件即可)。在实际操作中要删除的该类文件可能会比较多,这点需要一定的耐心,本人测试过将外网机同一项目环境中运行正常的本地仓库拷贝到内网机中,有时依然需要这样操作。
原因分析:出现.lastUpdated文件一般情况下是由于网速慢、断网等原因导致jar包下载不下来或不完整导致出现该文件的,.repositories文件的作用相当于本地Maven仓库缓存了jar/pom的情况下修改了maven配置文件(settings.xml)后依然会去远程仓库获取。将其删除后就不会去这类文件中找远端仓库地址进行更新,而是直接引用了现有jar包,该问题也就解决了。