美文网首页
聊聊如何避免多个jar通过maven打包成一个jar,多个同名配

聊聊如何避免多个jar通过maven打包成一个jar,多个同名配

作者: linyb极客之路 | 来源:发表于2023-02-27 11:01 被阅读0次

    前言

    不知道大家在开发的过程中,有没有遇到这种场景,外部的项目想访问内部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>
    
    

    打包后的配置文件的效果如下图

    image.png

    眼尖的朋友应该发现了,同名的配置内容是通过追加的方式,但仅仅追加,其实有时候还满足不了要求,比如spring.factories文件,他需要达到的效果应该是如下图


    image.png

    后面我通过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文件内容,将

    image.png

    调整成形如下即可


    image.png

    自定义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阶段执行

    打包后的效果图如下

    image.png

    总结

    之前在看开源框架的时候,很经常都是聚焦在源码上,而不会去注意一些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

    相关文章

      网友评论

          本文标题:聊聊如何避免多个jar通过maven打包成一个jar,多个同名配

          本文链接:https://www.haomeiwen.com/subject/gkuhldtx.html