Maven 本地仓库更新策略
更新策略可以分为两个步骤:(1)更新元文件;(2)更新本地仓库jar包;
更新的步骤是:先更新元文件,再根据元文件更新本地仓库jar包。
元文件有三个:maven-metadata-local.xml,maven-metadata-snapshot-nexus.xml,resolver-status.properties
maven-metadata-local.xml
本地install代码后会生成该文件,记录的是本地项目编译的时间戳,示例内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<metadata modelVersion="1.1.0">
<groupId>com.hong</groupId>
<artifactId>springdemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<versioning>
<snapshot>
<localCopy>true</localCopy>
</snapshot>
<lastUpdated>20210716061648</lastUpdated>
<snapshotVersions>
<snapshotVersion>
<extension>jar</extension>
<value>0.0.1-SNAPSHOT</value>
<updated>20210716061648</updated>
</snapshotVersion>
<snapshotVersion>
<extension>pom</extension>
<value>0.0.1-SNAPSHOT</value>
<updated>20210716061648</updated>
</snapshotVersion>
</snapshotVersions>
</versioning>
</metadata>
maven-metadata-snapshot-nexus.xml
从远程仓库拉取jar包后,会同时从仓库上下载该元文件,一般命名格式为 maven-metadata--nexus.xml (这里以nexus仓库为例);该文件记录的是远程仓库上项目最新版本的时间戳,示例如下:
<?xml version="1.0" encoding="UTF-8"?>
<metadata modelVersion="1.1.0">
<groupId>com.hong</groupId>
<artifactId>springdemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<versioning>
<snapshot>
<timestamp>20210718.135001</timestamp>
<buildNumber>12</buildNumber>
</snapshot>
<lastUpdated>20210718135001</lastUpdated>
<snapshotVersions>
<snapshotVersion>
<extension>jar</extension>
<value>0.0.1-20210718.135001-12</value>
<updated>20210718135001</updated>
</snapshotVersion>
<snapshotVersion>
<extension>pom</extension>
<value>0.0.1-20210718.135001-12</value>
<updated>20210718135001</updated>
</snapshotVersion>
</snapshotVersions>
</versioning>
</metadata>
resolver-status.properties
从远程仓库拉取jar包的时候,也会生成该文件,并且每次拉取都会更新。该文件主要作用是记录maven-metadata--nexus.xml 文件的上次更新时间戳,并结合标签完成更新策略的一部分。示例如下:
#NOTE: This is a Maven Resolver internal implementation file, its format can be changed without prior notice.
#Sat Jul 24 11:08:47 CST 2021
maven-metadata-snapshot-nexus.xml.lastUpdated=1627096127974
-
更新本地仓库jar包
依赖于 maven-metadata-local.xml 和 maven-metadata-snapshot-nexus.xml 两个文件
-
如果只有 maven-metadata-local.xml 文件,那么maven不会从远程仓库下载任何jar包,只会从本地项目编译;若想从远程仓库拉取,只能先删除本地仓库的jar包,然后编译一个依赖了该jar包的其他项目,maven检查到本地没有jar包,就会从远程拉取到该项目的jar包;
-
如果两个文件都有,每次都需要比较一下两个文件的时间戳,即标签上的时间戳。
(1)如果local.xml的时间戳比snapshot.xml的时间戳要新,就不会从远程仓库下载;
(2)如果local.xml的时间戳比snapshot.xml的时间戳要旧,就会去检查一下本地maven仓库的该项目文件夹路径下是否有snapshot.xml对应版本的jar包
(3)如果没有该版本的jar包,就会从远程仓库拉取该版本的jar包;
(4)如果有该版本的jar包,就不会做任何行为;
-
-
更新本地元文件
更新本地仓库jar包决定于本地元文件 maven-metadata-snapshot-nexus.xml,该文件的更新取决于resolver-status.properties文件。
- 该文件的maven-metadata-snapshot-nexus.xml.lastUpdated变量记录了maven-metadata-snapshot-nexus.xml 上次的更新时间;
- 每次更新的时候,读取lastUpdated变量的值,然后与当前的时间做比较。然后时间相差的结果与标签的更新规则相比较;
- 如果是always,则立刻更新;如果是never,则不更新元文件;如果是daily,固定时间间隔是1天,结果超过1天则更新元文件,不超过就不更新;
- 如果是interval: x,x的单位是分钟,且结果比x大,则从远程仓库下载最新的maven-metadata-snapshot-nexus.xml文件;如果是interval: x,且结果比x小,则不更新;
- 接下来,就到了更新本地仓库jar包的阶段;
附上maven的settings.xml文件和依赖目标项目的项目pom文件核心配置如下:
settings.xml
<!--
其他配置标签
-->
<servers>
<server>
<id>maven-nexus</id>
<username>admin</username>
<password>123456</password>
</server>
<server>
<id>release-nexus</id>
<username>admin</username>
<password>123456</password>
</server>
<server>
<id>snapshot-nexus</id>
<username>admin</username>
<password>123456</password>
</server>
</servers>
<!--
其他配置标签
-->
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>org.example</groupId>
<artifactId>mavenDeployDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.hong</groupId>
<artifactId>springdemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>snapshot-nexus</id> <!-- 该id要在settings.xml的<server>标签里有配置账号和密码 -->
<name>maven-snapshot</name>
<url>http://localhost:8081/repository/maven-snapshots/</url> <!-- 远程仓库的snapshot仓库url路径 -->
<layout>default</layout>
<snapshots>
<enabled>true</enabled>
<updatePolicy>interval:5</updatePolicy> <!-- 更新策略为每隔5分钟更新一次本地元文件 -->
</snapshots>
</repository>
</repositories>
</project>
------------------------------------------------更新分割线--------------------------------------------------------------------------------------
前面关于下载元文件部分的描述有所错误,这里进行一个更正:
(1)本地仓库的项目路径下,如果只有maven-metadata-local.xml文件,并非不会再去下载maven-metadata-snapshot.xml文件,而是因为自己的配置有错。原来的配置只是在maven的setting.xml文件中,通过标签添加了远程仓库的snapshot仓库,这个配置是没有生效的。如果要生效,必须要按以下两种方式修改:
- 在maven的setting.xml文件中,添加注解,里面写上snapshot的id,这样才能正常让注解生效;
- 在启动构建的项目的pom.xml中,添加的snapshot信息,这样也可以生效;
按照以上配置真正生效后,就可以看到真实的情况了
(2)首先,会去检查项目路径下是否有maven-metadata-local.xml文件。如果有maven-metadata-local.xml,则比较文件的最近修改时间和当前的时间(注意,注意,这里文件最近修改时间不是文件里面的标签的内容,而是文件自身的最近修改时间的属性,即通过File.getLastUpdated方法获取到的参数;另一方面,当前时间是通过Calendar类来构建的);如果maven-metadata-local.xml文件不存在,那么就必然会去远程仓库下载maven-metadata-远程仓库类型.xml文件,比如maven-metadata-snapshot.xml文件;
(3)第(2)点的主文件的比较,依靠的规则依然是里配置的策略。根据策略比较最近修改时间和当前时间,如果需要更新,就继续走下载的步骤;
(4)最后就是对resolver-status.properties文件的操作了,这个与之前的一样;
记录一下源码里对这些过程的具体语句吧:
DefaultMetadataResolver.class
private List<MetadataResult> resolve( RepositorySystemSession session,
Collection<? extends MetadataRequest> requests ) {
// ...another code
// 获取到指定仓库的metadata信息
LocalRepositoryManager lrm = session.getLocalRepositoryManager();
LocalMetadataRequest localRequest = new LocalMetadataRequest( metadata, repository,
request.getRequestContext() );
LocalMetadataResult lrmResult = lrm.find( session, localRequest );
File metadataFile = lrmResult.getFile();
// 根据metadata信息获取本地仓库路径下的metadata文件,并获取本地metadata的文件修改时间
// 这里要关注getLocalFile方法,可以跳转到这个方法先看
Long localLastUpdate = null;
if ( request.isFavorLocalRepository() ) {
File localFile = getLocalFile( session, metadata );
localLastUpdate = localLastUpdates.get( localFile );
if ( localLastUpdate == null ) {
localLastUpdate = localFile != null ? localFile.lastModified() : 0;
localLastUpdates.put( localFile, localLastUpdate );
}
}
// 遍历处理每个仓库
for ( RemoteRepository repo : repositories ) {
// ... another code
// 创建一个UpdateCheck对象,并将上面获得的修改时间赋值
UpdateCheck<Metadata, MetadataTransferException> check = new UpdateCheck<>();
check.setLocalLastUpdated( ( localLastUpdate != null ) ? localLastUpdate : 0 );
// 真正开始解析metadata文件,并决定是否要更新本地metadata.xml就是在这里
updateCheckManager.checkMetadata( session, check );
}
}
private File getLocalFile( RepositorySystemSession session, Metadata metadata ) {
LocalRepositoryManager lrm = session.getLocalRepositoryManager();
LocalMetadataResult localResult = lrm.find( session, new LocalMetadataRequest( metadata, null, null ));
return localResult.getFile();
}
SimpleLocalRepositoryManager.class
public LocalMetadataResult find( RepositorySystemSession session, LocalMetadataRequest request ) {
requireNonNull( session, "session cannot be null" );
requireNonNull( request, "request cannot be null" );
LocalMetadataResult result = new LocalMetadataResult( request );
String path;
Metadata metadata = request.getMetadata();
String context = request.getContext();
// 获取到远程仓库的信息
RemoteRepository remote = request.getRepository();
// 如果远程仓库信息不为空,即获取maven-metadata-远程仓库名.xml文件路径,没有就获取maven-metadata-local.xml文件路径
// 根据getLocalFile的传参repository和context是null, 即必定获取到maven-metadata-local.xml文件路径
if ( remote != null ) {
path = getPathForRemoteMetadata( metadata, remote, context );
}
else {
path = getPathForLocalMetadata( metadata );
}
// maven-metadata-local.xml存在,即赋值给result的file属性,否则为null
File file = new File( getRepository().getBasedir(), path );
if ( file.isFile() ) {
result.setFile( file );
}
return result;
}
DefaultUpdateCheckManager.class
public void checkMetadata( RepositorySystemSession session, UpdateCheck<Metadata, MetadataTransferException>
check ) {
/**
* 如果maven-metadata-local.xml不存在,则此处getLocalLastUpdated() == 0,必须走入下载maven-metadata-x.xml文件流程
*/
if ( check.getLocalLastUpdated() != 0
&& !isUpdatedRequired( session, check.getLocalLastUpdated(), check.getPolicy() ) ) {
LOGGER.debug( "Skipped remote request for {} locally installed metadata up-to-date", check.getItem() );
check.setRequired( false );
return;
}
Metadata metadata = check.getItem();
RemoteRepository repository = check.getRepository();
// 获取远程仓库的maven-metadata-x.xml文件对象
File metadataFile = requireNonNull( check.getFile(), String.format( "The metadata '%s' has no file" +
"attached", metadata ) );
boolean fileExists = check.isFileValid() && metadataFile.exists();
// 获取resolver-status.properties文件的lastUpdated内容
File touchFile = getMetadataTouchFile( metadataFile );
Properties props = read( touchFile );
String updateKey = getUpdateKey( session, metadataFile, repository );
String dataKey = getDataKey( metadataFile );
String error = getError( props, dataKey );
long lastUpdated;
if ( error == null ) {
// 如果远程仓库maven-metadata-x.xml文件不存在,则必然走入下载流程;
// 否则,按照更新策略比较resolver-status.properties的lastUpdated参数和当前时间,决定是否进入下载流程
if ( fileExists ) {
// last update was successful
lastUpdated = getLastUpdated( props, dataKey );
}
else {
// this is the first attempt ever
lastUpdated = 0L;
}
} else if ( error.length() <= 0 ) {
// metadata did not exist
lastUpdated = getLastUpdated( props, dataKey );
} else {
// metadata could not be transferred
String transferKey = getTransferKey( session, metadataFile, repository );
lastUpdated = getLastUpdated( props, transferKey );
}
if ( lastUpdated == 0L ) {
check.setRequired( true );
} else if ( isAlreadyUpdated( session, updateKey ) ) {
LOGGER.debug( "Skipped remote request for {}, already updated during this session", check.getItem() );
check.setRequired( false );
if ( error != null ) {
check.setException( newException( error, metadata, repository ) );
}
} else if ( isUpdatedRequired( session, lastUpdated, check.getPolicy() ) ) {
check.setRequired( true );
} else if ( fileExists ) {
LOGGER.debug( "Skipped remote request for {}, locally cached metadata up-to-date", check.getItem() );
check.setRequired( false );
} else {
int errorPolicy = Utils.getPolicy( session, metadata, repository );
int cacheFlag = getCacheFlag( error );
if ( ( errorPolicy & cacheFlag ) != 0 ) {
check.setRequired( false );
check.setException( newException( error, metadata, repository ) );
} else {
check.setRequired( true );
}
}
}
DefaultUpdatePolicyAnalyzer.class
public boolean isUpdatedRequired( RepositorySystemSession session, long lastModified, String policy ) {
requireNonNull( session, "session cannot be null" );
boolean checkForUpdates;
if ( policy == null ) {
policy = "";
}
if ( RepositoryPolicy.UPDATE_POLICY_ALWAYS.equals( policy ) ) {
checkForUpdates = true;
}
// 关注这个日更新的策略,利用Calendar类的set方法,直接将时分秒设置为0,就可以得到当前日期的开始点了,再转化为毫秒与时间戳
// 比较,值得学习
else if ( RepositoryPolicy.UPDATE_POLICY_DAILY.equals( policy ) ) {
Calendar cal = Calendar.getInstance();
cal.set( Calendar.HOUR_OF_DAY, 0 );
cal.set( Calendar.MINUTE, 0 );
cal.set( Calendar.SECOND, 0 );
cal.set( Calendar.MILLISECOND, 0 );
checkForUpdates = cal.getTimeInMillis() > lastModified;
}
else if ( policy.startsWith( RepositoryPolicy.UPDATE_POLICY_INTERVAL ) )
{
int minutes = getMinutes( policy );
Calendar cal = Calendar.getInstance();
cal.add( Calendar.MINUTE, -minutes );
checkForUpdates = cal.getTimeInMillis() > lastModified;
}
else {
// assume "never"
checkForUpdates = false;
if ( !RepositoryPolicy.UPDATE_POLICY_NEVER.equals( policy ) ) {
LOGGER.warn( "Unknown repository update policy '{}', assuming '{}'",
policy, RepositoryPolicy.UPDATE_POLICY_NEVER );
}
}
return checkForUpdates;
}
最后的注意,这部分更新相关的代码是在maven-resolver-impl项目的代码中,不是在maven项目本身的源码中,maven项目是通过依赖jar包的方式调用里面的方法