避免多個(gè)jar通過(guò)maven打包導(dǎo)致同名配置文件覆蓋沖突問(wèn)題
前言
不知道大家在開(kāi)發(fā)的過(guò)程中,有沒(méi)有遇到這種場(chǎng)景,外部的項(xiàng)目想訪問(wèn)內(nèi)部nexus私倉(cāng)的jar,因?yàn)樗絺}(cāng)不對(duì)外開(kāi)放,導(dǎo)致外部的項(xiàng)目沒(méi)法下載到私倉(cāng)的jar,導(dǎo)致項(xiàng)目因缺少jar而無(wú)法運(yùn)行。
通常遇到這種場(chǎng)景,常用的解法有,外部項(xiàng)目跟內(nèi)部nexus的網(wǎng)絡(luò)打通,比如通過(guò)VPN?;蛘邔⑺絺}(cāng)的jar直接下載下來(lái)給到外部項(xiàng)目。對(duì)于第二種方案有時(shí)候因?yàn)樗絺}(cāng)的jar里面有依賴(lài)其他的內(nèi)部jar,導(dǎo)致要下載多個(gè)jar的情況。這時(shí)候?yàn)榱朔奖悖覀兛赡軙?huì)將這些jar合并成一個(gè)大jar,再給出去。而目前有些jar都是一些starter,會(huì)有一些同名的配置文件,比如spring.factories。如果不進(jìn)行處理,直接打包,就會(huì)出現(xiàn)同名配置文件覆蓋的情況
本文就是要來(lái)聊聊當(dāng)多個(gè)jar合并成一個(gè)jar,如何解決多個(gè)同名配置文件覆蓋的情況
解決思路
通過(guò)maven-shade-plugin這個(gè)插件,利用插件的org.apache.maven.plugins.shade.resource.AppendingTransformer來(lái)處理處理多個(gè)jar包中存在重名的配置文件的合并。他的核心是在于合并多個(gè)同名配置文件內(nèi)容,而非覆蓋
示例配置如下
<build>
<plugins>
<!-- 防止同名配置文件,在打包時(shí)被覆蓋,用來(lái)處理多個(gè)jar包中存在重名的配置文件的合并
參考dubbo:https://github.com/apache/dubbo/blob/master/dubbo-all/pom.xml-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.factories</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.handlers</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.schemas</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.tooling</resource>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>打包后的配置文件的效果如下圖

眼尖的朋友應(yīng)該發(fā)現(xiàn)了,同名的配置內(nèi)容是通過(guò)追加的方式,但僅僅追加,其實(shí)有時(shí)候還滿足不了要求,比如spring.factories文件,他需要達(dá)到的效果應(yīng)該是如下圖

后面我通過(guò)maven-shade-plugin的官方示例(https://maven.apache.org/plugins/maven-shade-plugin/examples/resource-transformers.html)試圖想找到解決方案,但是有點(diǎn)遺憾,沒(méi)找到。
于是在我面前就有兩條路,一條是放棄maven-shade-plugin插件,比如選擇其他類(lèi)似的插件,比如maven-assembly-plugin,這種方案我試過(guò),發(fā)現(xiàn)maven-assembly-plugin這個(gè)插件的擴(kuò)展配置,比maven-shade-plugin復(fù)雜一些,于是放棄。最后選擇了在maven-shade-plugin基礎(chǔ)再擴(kuò)展一下。
擴(kuò)展的思路
我并沒(méi)采用直接修改maven-shade-plugin插件的方式,而是在maven-shade-plugin打包后的基礎(chǔ)上,再進(jìn)行插件定制。實(shí)現(xiàn)的思路也不難,就是修改maven-shade-plugin打成jar后的spring.factories文件內(nèi)容,將

調(diào)整成形如下即可

自定義maven插件spring-factories-merge-plugin
核心思路
1、如何讀取配置文件spring.factories中key重復(fù)的內(nèi)容,而不被覆蓋
如果是直接使java.util.properties的讀取,當(dāng)配置文件中有key重復(fù)時(shí),比如有多個(gè)org.springframework.boot.autoconfigure.EnableAutoConfiguration時(shí),最后會(huì)出現(xiàn)value值被覆蓋的情況。
解決方案,我們可以利用org.apacche.commons.configuration.PropertiesConfiguration來(lái)進(jìn)行處理
在項(xiàng)目的pom引入GAV
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-configuration2</artifactId>
<version>${commons-configuration2}</version>
</dependency>讀取配置示例代碼
@SneakyThrows
public static Map<String, Set<String>> readFactoriesFile(InputStream input) {
// 讀取 spring.factories 內(nèi)容
//利用PropertiesConfiguration取配置文件中key重復(fù)的內(nèi)容,而不被覆蓋
PropertiesConfiguration properties = new PropertiesConfiguration();
properties.read(new InputStreamReader(input));
Map<String, Set<String>> multiSetMap = new LinkedHashMap<>();
Iterator<String> keys = properties.getKeys();
while(keys.hasNext()) {
String key = keys.next();
String[] values = properties.getStringArray(key);
Set<String> collectSet = new LinkedHashSet<>();
buildKeyValues(values, collectSet);
multiSetMap.put(key,collectSet);
}
return multiSetMap;
}2、如何將修改后的配置文件,重新寫(xiě)入jar
我這邊的思路就是直接利用IO進(jìn)行操作了
示例如下
public static void writeFactoriesFile(String factoriesBaseClassPathDir,String finalJarName) throws IOException {
String jarFilePath = String.format(factoriesBaseClassPathDir + "/target/" + finalJarName).replace("\\", "/").replaceAll("http://+", "/");
if(!jarFilePath.endsWith(".jar")){
jarFilePath = jarFilePath + ".jar";
}
JarFile jarFile = new JarFile(jarFilePath);
if(jarFile != null){
List<JarEntry> jarFiles = jarFile.stream().collect(Collectors.toList());
@ Cleanup FileOutputStream fos = new FileOutputStream(jarFile.getName(), true);
@ Cleanup JarOutputStream jos = new JarOutputStream(fos);
for (JarEntry jarEntry : jarFiles) {
if(jarEntry.getName().startsWith(SpringFactoriesLoader.FACTORIES_RESOURCE_LOCATION)){
try {
@ Cleanup InputStream input = jarFile.getInputStream(jarEntry);
Map<String, Set<String>> factoriesMap = readFactoriesFile(input);
jos.putNextEntry(new JarEntry(jarEntry.getName()));
generateFactoriesContent(factoriesMap,jos);
} catch (IOException e) {
e.printStackTrace();
}
}else{
//表示將該JarEntry寫(xiě)入jar文件中 也就是創(chuàng)建該文件夾和文件
jos.putNextEntry(new JarEntry(jarEntry));
jos.write(streamToByte(jarFile.getInputStream(jarEntry)));
}
}
}
}項(xiàng)目中如何配置插件
<build>
<plugins>
<!-- 防止同名配置文件,在打包時(shí)被覆蓋,用來(lái)處理多個(gè)jar包中存在重名的配置文件的合并
參考dubbo:https://github.com/apache/dubbo/blob/master/dubbo-all/pom.xml-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.factories</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.handlers</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.schemas</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.tooling</resource>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.github.lybgeek.jar</groupId>
<artifactId>spring-factories-merge-plugin</artifactId>
<version>0.0.1-SNAPSHOT</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>springFactoriesMerge</goal>
</goals>
</execution>
</executions>
<configuration>
<factoriesBaseClassPathDir>${basedir}</factoriesBaseClassPathDir>
<finalJarName>${project.artifactId}-${project.version}</finalJarName>
</configuration>
</plugin>
</plugins>
</build>這邊有個(gè)小細(xì)節(jié)是當(dāng)maven-shade-plugin和spring-factories-merge-plugin的執(zhí)行生命周期都是相同階段,比如都是在package時(shí),則maven-shade-plugin放置順序得在spring-factories-merge-plugin之前,因?yàn)閟pring-factories-merge-plugin是對(duì)maven-shade-plugin打包后的結(jié)果進(jìn)行二次加工。如果maven-shade-plugin不放置順序得在spring-factories-merge-plugin之前,則spring-factories-merge-plugin的執(zhí)行階段就要比maven-shade-plugin靠后,比如maven-shade-plugin在package階段執(zhí)行,則spring-factories-merge-plugin就得在install或者deploy階段執(zhí)行
打包后的效果圖如下

總結(jié)
之前在看開(kāi)源框架的時(shí)候,很經(jīng)常都是聚焦在源碼上,而不會(huì)去注意一些maven插件,這次因?yàn)橛羞@打jar的需求。我發(fā)現(xiàn)不管是springboot還是dubbo本身就集成一些寶藏插件,比如這個(gè)maven-shade-plugin插件,我就是dubbo那邊找到的,地址在
https://github.com/apache/dubbo/blob/master/dubbo-all/pom.xml。比如版本占位符插件flatten-maven-plugin在dubbo和springboot都有看到使用。如果后面有對(duì)maven插件由需求,推薦可以從springboot或者dubbo那邊去搜,估計(jì)會(huì)有意想不到的收獲
demo鏈接 https://github.com/lyb-geek/springboot-learning/tree/master/springboot-jar-merge
以上就是避免多個(gè)jar通過(guò)maven打包導(dǎo)致同名配置文件覆蓋沖突問(wèn)題的詳細(xì)內(nèi)容,更多關(guān)于maven打包jar同名沖突的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- maven打包本地jar到項(xiàng)目中的方法實(shí)現(xiàn)
- maven打包所有依賴(lài)對(duì)外提供sdk.jar
- Maven打包所有依賴(lài)到一個(gè)可執(zhí)行jar中遇到的問(wèn)題
- maven工程打包引入本地jar包的實(shí)現(xiàn)
- IDEA在SpringBoot項(xiàng)目使用Maven打包后jar包太小問(wèn)題及解決
- idea本地jar使用maven打包本地依賴(lài)實(shí)現(xiàn)自動(dòng)編譯到項(xiàng)目里的操作
- maven打包加入本地jar包的實(shí)現(xiàn)
- maven多模塊項(xiàng)目單獨(dú)打包指定模塊jar包方式
- Maven項(xiàng)目打包為jar的四種方式
相關(guān)文章
如何基于FTP4J實(shí)現(xiàn)FTPS連接過(guò)程解析
這篇文章主要介紹了如何基于FTP4J實(shí)現(xiàn)FTPS連接過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10
為什么不建議使用Java自定義Object作為HashMap的key
這篇文章主要介紹了為什么不建議使用Java自定義Object作為HashMap的key,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的小伙伴可以參考一下2022-06-06
解決Java 部署Tomcat時(shí)使用jni和jna調(diào)用DLL文件的問(wèn)題
這篇文章主要介紹了解決Java 部署Tomcat時(shí)使用jni和jna調(diào)用DLL文件的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11
Spring Boot中Redis序列化優(yōu)化配置詳解
在使用Spring Boot集成Redis時(shí),序列化方式的選擇直接影響數(shù)據(jù)存儲(chǔ)的效率和系統(tǒng)兼容性,默認(rèn)的JDK序列化存在可讀性差、存儲(chǔ)空間大等問(wèn)題,本文將深入探討如何優(yōu)化Redis序列化配置,感興趣的朋友跟隨小編一起看看吧2025-05-05

