前言
不知道大家在開發的過程中,有沒有遇到這種場景,外部的專案想訪問內部nexus私倉的jar,因為私倉不對外開放,導致外部的專案沒法下載到私倉的jar,導致專案因缺少jar而無法執行。
通常遇到這種場景,常用的解法有,外部專案跟內部nexus的網路打通,比如透過VPN。或者將私倉的jar直接下載下來給到外部專案。對於第二種方案有時候因為私倉的jar裡面有依賴其他的內部jar,導致要下載多個jar的情況。這時候為了方便,我們可能會將這些jar合併成一個大jar,再給出去。而目前有些jar都是一些starter,會有一些同名的配置檔案,比如spring.factories。如果不進行處理,直接打包,就會出現同名配置檔案覆蓋的情況
本文就是要來聊聊當多個jar合併成一個jar,如何解決多個同名配置檔案覆蓋的情況
解決思路
透過maven-shade-plugin這個外掛,利用外掛的org.apache.maven.plugins.shade.resource.AppendingTransformer來處理處理多個jar包中存在重名的配置檔案的合併。他的核心是在於合併多個同名配置檔案內容,而非覆蓋
示例配置如下
<build>
<plugins>
<!-- 防止同名配置檔案,在打包時被覆蓋,用來處理多個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>
打包後的配置檔案的效果如下圖
眼尖的朋友應該發現了,同名的配置內容是透過追加的方式,但僅僅追加,其實有時候還滿足不了要求,比如spring.factories檔案,他需要達到的效果應該是如下圖
後面我透過maven-shade-plugin的官方示例(https://maven.apache.org/plugins/maven-shade-plugin/examples/resource-transformers.html)試圖想找到解決方案,但是有點遺憾,沒找到。於是在我面前就有兩條路,一條是放棄maven-shade-plugin外掛,比如選擇其他類似的外掛,比如maven-assembly-plugin,這種方案我試過,發現maven-assembly-plugin這個外掛的擴充套件配置,比maven-shade-plugin複雜一些,於是放棄。最後選擇了在maven-shade-plugin基礎再擴充套件一下。
擴充套件的思路
我並沒採用直接修改maven-shade-plugin外掛的方式,而是在maven-shade-plugin打包後的基礎上,再進行外掛定製。實現的思路也不難,就是修改maven-shade-plugin打成jar後的spring.factories檔案內容,將
調整成形如下即可
自定義maven外掛spring-factories-merge-plugin
核心思路
1、如何讀取配置檔案spring.factories中key重複的內容,而不被覆蓋
如果是直接使java.util.properties的讀取,當配置檔案中有key重複時,比如有多個org.springframework.boot.autoconfigure.EnableAutoConfiguration時,最後會出現value值被覆蓋的情況。
解決方案,我們可以利用org.apacche.commons.configuration.PropertiesConfiguration來進行處理
在專案的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 內容
//利用PropertiesConfiguration取配置檔案中key重複的內容,而不被覆蓋
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、如何將修改後的配置檔案,重新寫入jar
我這邊的思路就是直接利用IO進行操作了
示例如下
public static void writeFactoriesFile(String factoriesBaseClassPathDir,String finalJarName) throws IOException {
String jarFilePath = String.format(factoriesBaseClassPathDir + "/target/" + finalJarName).replace("\\", "/").replaceAll("//+", "/");
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寫入jar檔案中 也就是建立該資料夾和檔案
jos.putNextEntry(new JarEntry(jarEntry));
jos.write(streamToByte(jarFile.getInputStream(jarEntry)));
}
}
}
}
專案中如何配置外掛
<build>
<plugins>
<!-- 防止同名配置檔案,在打包時被覆蓋,用來處理多個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>
這邊有個小細節是當maven-shade-plugin和spring-factories-merge-plugin的執行生命週期都是相同階段,比如都是在package時,則maven-shade-plugin放置順序得在spring-factories-merge-plugin之前,因為spring-factories-merge-plugin是對maven-shade-plugin打包後的結果進行二次加工。如果maven-shade-plugin不放置順序得在spring-factories-merge-plugin之前,則spring-factories-merge-plugin的執行階段就要比maven-shade-plugin靠後,比如maven-shade-plugin在package階段執行,則spring-factories-merge-plugin就得在install或者deploy階段執行
打包後的效果圖如下
總結
之前在看開源框架的時候,很經常都是聚焦在原始碼上,而不會去注意一些maven外掛,這次因為有這打jar的需求。我發現不管是springboot還是dubbo本身就整合一些寶藏外掛,比如這個maven-shade-plugin外掛,我就是dubbo那邊找到的,地址在
https://github.com/apache/dubbo/blob/master/dubbo-all/pom.xml
。比如版本佔位符外掛flatten-maven-plugin在dubbo和springboot都有看到使用。如果後面有對maven外掛由需求,推薦可以從springboot或者dubbo那邊去搜,估計會有意想不到的收穫
demo連結
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-jar-merge