天天看點

如何避免多個jar通過maven打包成jar,同名配置檔案發生覆寫問題

前言

不知道大家在開發的過程中,有沒有遇到這種場景,外部的項目想通路内部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>

           

打包後的配置檔案的效果如下圖

如何避免多個jar通過maven打包成jar,同名配置檔案發生覆寫問題

眼尖的朋友應該發現了,同名的配置内容是通過追加的方式,但僅僅追加,其實有時候還滿足不了要求,比如spring.factories檔案,他需要達到的效果應該是如下圖

如何避免多個jar通過maven打包成jar,同名配置檔案發生覆寫問題

後面我通過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檔案内容,将

如何避免多個jar通過maven打包成jar,同名配置檔案發生覆寫問題

調整成形如下即可

如何避免多個jar通過maven打包成jar,同名配置檔案發生覆寫問題

自定義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階段執行

打包後的效果圖如下

如何避免多個jar通過maven打包成jar,同名配置檔案發生覆寫問題

總結

之前在看開源架構的時候,很經常都是聚焦在源碼上,而不會去注意一些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

繼續閱讀