背景:
项目使用一个版本号,导致测试生产环境jar无法隔离,不能轻易deploy,尤其是测试环境的jar。
从而只能在发版不服务内install到本地仓库,单机环境内jar可用。
由于模块比较多,负责和维护的人不同,不可能所有项目都checkout到本地,并随时install最新的包到本地仓库,
这样就导致本地开发环境无法下载远程仓库相应的jar。无法编译以及本地运行。
目标:
实现一种对开发人员透明的高效的自动化的动态版本管理进行环境的隔离解决方案。
方案:
一、相同版本号,不同classifier
二、不同版本号
实施方案一:
计划:
A项目版本号使用原来的“1.0.0-SNAPSHOT”, 增加classifier, 测试环境为500, 生成环境为800,进行环境隔离。
每次发版A项目自动deploy对应的classifier的包到线上仓库。对开发人员透明化自动化。
执行:
1,A项目parent pom增加env.classifier.version变量,并配置计划的目标指。
<profiles>
<profile>
<id>500</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<build.profile.id>500</build.profile.id>
<env.classifier.version>500</env.classifier.version>
</properties>
</profile>
<profile>
<id>800</id>
<properties>
<build.profile.id>800</build.profile.id>
<env.classifier.version>800</env.classifier.version>
</properties>
</profile>
</profiles>
2,配置打包插件,增加classifier配置
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<classifier>${env.classifier.version}</classifier>
</configuration>
</plugin>
3,项目子模块,maven依赖增加classifier配置。
<dependency>
<groupId>com.gome.gsh</groupId>
<artifactId>snake-rpc-api</artifactId>
<version>${project.version}</version>
<classifier>${env.classifier.version}</classifier>
</dependency>
<dependency>
<groupId>com.gome.gsh</groupId>
<artifactId>snake-service</artifactId>
<version>${project.version}</version>
<classifier>${env.classifier.version}</classifier>
</dependency>
4、发版脚本增加deploy命令
检查:
打包过程中,成功deploy对应的classifier的jar到仓库中。
打包后检查打包jsw下的lib目录,发现缺少当前模块(snake-web)的jar包,并且项目启动报错找不到main class。
分析:
target目录下是生成了snake-web-500.jar包的,说明maven-jar-plugin是成功的,只是打包成可执行过程没成功。
appassembler-maven-plugin插件没有将snake-web-500.jar拷贝成功。
处理:
修改appassembler-maven-plugin
计划:
修改appassembler-maven-plugin源码实现当前模块的jar拷贝功能。
执行:
1、下载插件源码
2、修改AbstractAppAssemblerMojo类,增加参数userClassifier
@Parameter
protected String userClassifier;
3、增加拷贝jar代码,指定新编译成功后的jar名称。
if ( !source.isDirectory() )
{
......
}else {
getLog().info( "Before Replace Installing artifact " + source.getPath() + " to " + destination );
source = new File( source.getParentFile(), artifact.getArtifactId()+"-"+artifact.getVersion() + "-"+userClassifier+".jar" );
FileUtils.copyFile( source, destination );
}
4、deploy插件到仓库。
5、修改项目打包插件为自定义插件,并传递自定义参数userClassifier
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>appassembler-maven-plugin</artifactId>
<version>2021</version>
<configuration>
<userClassifier>${env.classifier.version}</userClassifier>
<!-- 根目录 -->
<assembleDirectory>${project.build.directory}</assembleDirectory>
......
</plugin>
检查:
成本拷贝jar到可执行包lib下,发版成本,项目成功运行。
验证deploy的包的可用性和隔离性。
使用两个项目B依赖A项目,B项目增加配置
<dependency>
<groupId>com.gome.gsh</groupId>
<version>1.0.0-SNAPSHOT</version>
<artifactId>snake-rpc-api</artifactId>
<classifier>500</classifier>
</dependency>
发版成功运行,查看lib目录下成功下载并拷贝好我们添加的jar包。
隔离性验证:
修改A项目rpc-api的pom增加一个dubbo模块依赖,并设置环境为800,打包并deploy。
仓库中成功上传了一个800的jar和maven依赖。
再从新打包B项目并执行。发现lib目录下多了一个800环境的dubbo依赖。
验证环境隔离失败
分析:
这种方式使用jar增加classifier来区分。实现了不同jar文件对应不用的环境。
但是版本号的相同,在本地仓库或远程仓库却是共用一个目录,并且pom.xml名称相同。
这样就导致了deploy 500环境的包是会包800环境的pom.xml覆盖掉。
总结这种方式,实现了jar的环境隔离,pom未实现环境隔离。
可能还有其他的办法来改造与实现。考虑到时间进度,成本已经改造过大造成的风险等。暂且搁置。
=========================================================================
实施方案二:
方案二使用了不同的版本号,这样使得在文件目录结构上使得jar和pom都相互独立。不存在方案一的问题。
计划:
使用不同的version来进行环境的隔离。
执行:
1、百度maven动态版本号的实现。
2、参考其方案。
修改A项目的parent的pom,增加<version>${env.project.version}</version>
所有子项目包含的parent的version定义成<version>${env.project.version}</version>
在顶层pom定义好变量的值,以及默认值。
<profile>
<id>500</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<build.profile.id>500</build.profile.id>
<env.project.version>1.0.0-DEVELOP</env.project.version>
</properties>
</profile>
<profile>
<id>800</id>
<properties>
<build.profile.id>800</build.profile.id>
<env.project.version>1.0.0-PRODUCE</env.project.version>
</properties>
</profile>
3、所有子模块之间的依赖不是用常量值,使用变量值<version>${project.version}</version>
<dependency>
<groupId>com.gome.gsh</groupId>
<artifactId>snake-rpc-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.gome.gsh</groupId>
<artifactId>snake-service</artifactId>
<version>${project.version}</version>
</dependency>
检查:
发版A项目到测试环境500,发现项目成功deploy对应的版本号的jar和pom到仓库中。A项目也成功启动成功。
检查仓库中包的可用性
修改B项目依赖A项目,并设置version为变量参数,根据打包命令传如的值动态设置。
发现编译不过。依赖的A项目的某模块的pom文件配置的parent version为${env.project.version}, maven无法找到该version。
查询远程仓库中pom文件,确实是变量。
分析:
存在变量值问题转换问题。可以把仓库比喻的开发项目中的数据库样。要处理值的替换。有两个方案。
一insert(deploy)阶段就把值替换好放进去。二query(compiler)的时候解析变量替换成对应的值。
个人倾向与insert阶段,事先把数据处理好,后面不管多少个query的版本接口都能正常解析。
计划:
修改maven-deploy-plugin在deploy前替换parent里的version变量。
执行:
1、下载maven-deploy-plugin插件
在DeployMojo类中增加参数
@Parameter( property = "dynamicVersionName" )
private String dynamicVersionName;
@Parameter( property = "dynamicVersionValue" )
private String dynamicVersionValue;
2、增加方法
private File replaceVsersion(File input) throws IOException {
getLog().info("*******POM deploy*******");
String source = "<version>${"+dynamicVersionName+"}</version>";
String target = "<version>" + dynamicVersionValue + "</version>";
File tmp = new File("/tmp/" + UUID.randomUUID().toString() + ".xml");
FileWriter fw = new FileWriter(tmp);
FileReader reder = new FileReader(input);
BufferedReader bufReader = new BufferedReader(reder);
String line = null;
while((line=bufReader.readLine())!=null) {
if(line.indexOf(source) > 0) {
line = line.replace(source, target);
getLog().info("动态版本号替换结果:" + line);
}
fw.append(line + "\r\n");
}
fw.close();
bufReader.close();
reder.close();
return tmp;
}
3、在deploy pom的地方使用该方法进行转换
deploy( replaceVsersion(pomFile), pomArtifact, repo, getLocalRepository(), retryFailedDeploymentCount );
4、打包maven-deploy-plugin到仓库。
重新打包发版项目A
检查:
发现parent的pom文件变量成功替换,但是所有子模块的pom文件却未能替换,和parent pom deploy走的程序逻辑不是同一个,子模块是包含有jar包的。
而且此时有引申处另一个问题。如果项目install了,其他项目依赖是会使用本地仓库的jar和pom,并不会使用远程仓库中的,那install过程也的改。保证install的pom也是正确的。
=========================================================================
计划:
修改maven-install-plugin查询,install到本地磁盘的pom文件的变量替换掉。
执行:
1、继续下载maven-install-plugin插件
2、同样在InstallMojo文件里增加
@Parameter( property = "dynamicVersionName" )
private String dynamicVersionName;
@Parameter( property = "dynamicVersionValue" )
private String dynamicVersionValue;
和replaceVsersion方法
private File replaceVsersion( File input )
{
try
{
if ( dynamicVersionName == null || "".equals( dynamicVersionName ) )
{
return input;
}
if ( dynamicVersionValue == null || "".equals( dynamicVersionValue ) )
{
return input;
}
if ( !"pom.xml".equals( input.getName() ) )
{
return input;
}
getLog().info( "*******POM install " + input.getName() + "*******" );
String source = "<version>${" + dynamicVersionName + "}</version>";
String target = "<version>" + dynamicVersionValue + "</version>";
File tmp = new File( "/tmp/" + UUID.randomUUID().toString() + ".xml" );
String os = System.getProperty( "os.name" );
if ( os.toLowerCase().startsWith( "win" ) )
{
tmp = new File( "d:\\" + UUID.randomUUID().toString() + ".xml" );
}
FileWriter fw = new FileWriter( tmp );
FileReader reder = new FileReader( input );
BufferedReader bufReader = new BufferedReader( reder );
String line = null;
while ( ( line = bufReader.readLine() ) != null )
{
if ( line.indexOf( source ) > 0 )
{
line = line.replace( source, target );
getLog().info( "动态版本号替换结果:" + line );
}
fw.append( line + "\r\n" );
}
fw.close();
bufReader.close();
reder.close();
return tmp;
}
catch ( Exception e )
{
getLog().error( e );
return input;
}
}
3、在installProject方法内,把原生pir内的pom文件替换成修改后的pom文件。
private void installProject( ProjectBuildingRequest pbr, ProjectInstallerRequest pir )
throws MojoFailureException, MojoExecutionException
{
try
{
pir.getProject().setFile( replaceVsersion( pir.getProject().getFile() ) );
installer.install( session.getProjectBuildingRequest(), pir );
}
catch ( IOException e )
{
throw new MojoFailureException( "IOException", e );
}
catch ( ArtifactInstallerException e )
{
throw new MojoExecutionException( "ArtifactInstallerException", e );
}
catch ( NoFileAssignedException e )
{
throw new MojoExecutionException( "NoFileAssignedException", e );
}
}
4、deploy插件maven-install-plugin到仓库
5、配置插件版本,以及参数
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<version>3.0.0-GOME</version>
<configuration>
<dynamicVersionName>env.project.version</dynamicVersionName>
<dynamicVersionValue>${env.project.version}</dynamicVersionValue>
</configuration>
</plugin>
6、编译打包发版A项目
检查:
发现本地仓库和远程仓库的pom文件的变量都替换成了对应环境的值。猜想deploy包无关,无需修改deploy插件。还原deploy插件。
重新打包发版,终于与预想的结果一样,pom文件都被正确替换。
验证仓库中包的可用性。
编译打包发版B项目, 成功编译并运行B项目。并且lib下正确引入了依赖的A项目的jar包。
隔离性验证,这种方案已经把jar和pom包都分成不同的文件目录,完全相互独立,已经不会产生影响了。
=========================================================================
总结:
到此maven动态版本实现了环境依赖隔离,
对开发人员透明化自动化实现过程,
提高了开发人员快速敏捷开发效率。
提高了生产环境的独立性和安全性。