spring的jar默认会把所有依赖打进jar包里面,也就是fit jar,这样会造成整个包非常大,不利于依赖管理,发布等操作。特别是像这种数据服务项目会依赖很多hadoop的sdk,轻轻松松上500MB+,所以不得不说java发展到现在,确实过于臃肿了,使用go去做gateway也确实是一个很好的选项。
为了解决发布包过大的问题,我把工程的发布做了修改,分离了发布包与依赖项,也就是最后发布出来的并不是一个单纯的jar包,而是一个目录,大概如下:
| root/xxxx.jar
 lib
 config
 application.yaml
 
 | 
root是根目录,jar是真正的业务代码的内容,所有的第三方依赖全部丢到了lib里面,然后把lib下的所有依赖写入MANIFEST.MF,达到在运行的classpath中可以找到的效果,配置统一归类到config目录,这样的好处是在不涉及引入新的依赖或者依赖更新的情况下,只需要更新业务的内容就行,而这个jar通常只有几百KB,从CI/CD或者测试来说效率非常高。
这样运行一直没有什么问题,但是某一天团队突然碰到一个问题,在程序启动的时候出现Class NotFound的错误,按理说这个错误实在不应该,因为这一段时间都没有涉及过依赖的修改,所有的依赖都在lib里面,仅仅修改的是业务的代码。
然后看NotFound的Class是一个公司内部的SDK的class,这个SDK安安静静地躺在lib目录下没有发生过任何变动,但是为什么会发生NotFound的错误呢,错误肯定毋庸置疑就是jvm在加载的时候所有类加载器都没有找到这个class,首先依赖肯定在,其次为何只有这一个依赖找不到,我能肯定是在运行的时候classpath下没有找到这个第三方jar,于是我使用jar xvf xxx.jar解压后打开META-INF下的MANIFEST.MF看了下,发现里面的jar的名字是XXXX-0.0.1-public-20211009.121515-24.jar,再和lib下的一对比发现后面的时间戳貌似发生了变化,lib下的public-后面的时间戳比这个小。
于是问题肯定找到了,就是lib下的jar和打包写入到MANIFEST.MF的jar的名字不对,造成运行的classpath的时候找不到,因此抛出了Class Not Found的错误,找到问题了就得看原因是什么。
因为lib更新频率低,而业务jar更新频率很快,每一次业务jar打包都会从maven里面拉取依赖的jar写入MANIFEST.MF,但是并不会实际打包第三方jar,这个时候如果maven里面的jar发生了变动,但是maven的版本号又没有发生修改,那么就可能出现这个问题。
再看了一下maven引入该包的dependency,是使用的0.0.1-public-SNAPSHOT版本,于是原因大概就推断出来了,因为该SDK的发布者并没有严格遵循jar发布的版本管理规则,或者说因为是SHNAPSHOT,所以就默认是最新的Jar,导致了这个问题的产生,我们的maven里面引用的是0.0.1-public-SNAPSHOT的版本,只要不升级这个版本,我们lib目录下的jar就会去更新,而如果这个时候SDK的发布者不断的往nexus仓库推送新的jar,版本号依旧是0.0.1-public-SNAPSHOT,但是jar的名字发生修改,就像后面加一个当前时间戳,这样就会造成放入lib下的jar和maven plugin在打包的时候写入MANIFEST.MF的名字不一致,造成运行的classpath的时候找不到这个class。
附录:maven plugin
| <plugin><groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-maven-plugin</artifactId>
 <version>${project.parent.version}</version>
 <configuration>
 
 <fork>true</fork>
 
 <includeSystemScope>true</includeSystemScope>
 
 <includes>
 <include>
 <groupId>nothing</groupId>
 <artifactId>nothing</artifactId>
 </include>
 </includes>
 </configuration>
 <executions>
 <execution>
 <goals>
 <goal>repackage</goal>
 </goals>
 </execution>
 </executions>
 </plugin>
 
 
 <plugin>
 <groupId>org.apache.maven.plugins</groupId>
 <artifactId>maven-compiler-plugin</artifactId>
 <configuration>
 
 <source>${java.version}</source>
 
 <target>${java.version}</target>
 
 <encoding>UTF-8</encoding>
 
 <compilerArguments>
 <verbose/>
 <bootclasspath>${java.home}/lib/rt.jar:${java.home}/lib/jce.jar</bootclasspath>
 </compilerArguments>
 </configuration>
 </plugin>
 
 
 <plugin>
 <groupId>org.apache.maven.plugins</groupId>
 <artifactId>maven-dependency-plugin</artifactId>
 <executions>
 <execution>
 <id>copy-dependencies</id>
 <phase>prepare-package</phase>
 <goals>
 <goal>copy-dependencies</goal>
 </goals>
 <configuration>
 
 <outputDirectory>${project.build.directory}/lib</outputDirectory>
 <overWriteReleases>false</overWriteReleases>
 <overWriteSnapshots>false</overWriteSnapshots>
 <overWriteIfNewer>true</overWriteIfNewer>
 </configuration>
 </execution>
 </executions>
 </plugin>
 
 
 <plugin>
 <groupId>org.apache.maven.plugins</groupId>
 <artifactId>maven-jar-plugin</artifactId>
 <configuration>
 <archive>
 <manifest>
 
 <addClasspath>true</addClasspath>
 
 <classpathPrefix>lib/</classpathPrefix>
 
 <mainClass>com.bytedance.emr.flowagent.server.FlowAgentServerApplication</mainClass>
 </manifest>
 </archive>
 
 <excludes>
 
 <exclude>*.properties</exclude>
 <exclude>*.xml</exclude>
 <exclude>*.yml</exclude>
 </excludes>
 </configuration>
 </plugin>
 
 <plugin>
 <groupId>org.apache.maven.plugins</groupId>
 <artifactId>maven-antrun-plugin</artifactId>
 <executions>
 <execution>
 <id>copy-jar-file</id>
 <phase>package</phase>
 <goals>
 <goal>run</goal>
 </goals>
 <configuration>
 <target>
 <copy todir="${project.build.directory}/config">
 <fileset dir="${project.basedir}/src/main/resources">
 <include name="*" />
 <include name="*/*" />
 <include name="*/*/*" />
 </fileset>
 </copy>
 </target>
 </configuration>
 </execution>
 </executions>
 </plugin>
 
 | 
扫码手机观看或分享:
