Maven 本地仓库更新策略

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包的方式调用里面的方法

  • 4
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值