一个复杂Jar的运行,要解决classpath问题,否则运行时会有ClassNotFoundException抛出。而用java,需要手动维护一个classpath文件,或者将所有的库位置放到命令行参数里。有没有更好的办法?
Jar+库目录方式
核心配置在于两点,一是manifest.mf,二是复制依赖,两者缺一不可。
关于这两点,我们先一个个讲。
<!-- 配置生成的jar文件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>com.maya.prepay.server.main.Main</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
Manifest.mf的文件中有些东西根本就是没用的,什么created-by这样的信息,那就是扯淡的。真正有用的东西是两个,一个是main类,这个配置决定它的jar在运行时启动哪个class。另外一个就是依赖,这个可以保证平稳地调用第三方的jar。
Main类的配置就在于
<mainClass>com.maya.prepay.server.main.Main</mainClass>
这么一行代码。
而classpath的配置就比较复杂了。有以下两行配置:
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
但是classpath配置了这么两行没用啊,如果真正生产上要用,还需要去手动拷贝依赖。对于maven管理的项目,依赖的jar位于不同目录下,拷贝起来那是十分伤脑筋的。怎么办?
这就需要下一个技术点了。Copy依赖,代码如下:
<!-- 配置将依赖放入同一个文件夹 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.9</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<includeTypes>jar</includeTypes>
<type>jar</type>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<overWriteReleases>true</overWriteReleases>
<overWriteSnapshots>true</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
</configuration>
</execution>
</executions>
</plugin>
这种代码,死记硬背就行了。
Uber-jar方式
Jar+库目录的方式,只是实现了jar的可执行。但是在部署、分发程序的时候,需要传递jar和库目录,用起来十分不方便。
可以将所有依赖和源代码都打包到一个jar包里。这样运行的时候就非常方便,直接运行一个大jar包就行。当然这个jar包体积会非常大。
这种jar包叫做uber-jar,又叫fat jar。Uber其实是super的意思,因为super是大多数编程语言关键字,所以很多时候用uber做变量名以表达super的含义。
这需要用到maven-shade-plugin插件或者maven-assembly-plugin。以maven-shade-plugin为例子。在pom.xml中配置好需要运行的主类,和package时的goal就可以了。
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.zhongmin.demo.Main</mainClass>
</transformer>
</transformers>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
编译之后生成的包在target下
可以看到,有两个jar,一个是original-xxx.jar,另一个是可执行的jar。大小也不一样,
可执行的jar足足有745K大小,而源代码编译后的jar包只有3KB大小。
这个编译结果与spring-boot项目的编译结果不一致。Shade插件,是把依赖的jar包全部解压,然后和源代码编译后的classpath放在一起。而spring boot是把依赖的jar包进行拷贝,使用一个classloader去加载这些jar包。两者原理不一样。