打包构建
开发阶段我们一般都是在IDE里面直接运行Java代码,在项目发布部署时如何导出jar包或war包呢?
非Maven项目
以Eclipse为例,右击项目名,选择Export选择JAR file、Runnable JAR file或WAR file即可
Maven项目
Maven的打包策略有很多,在此一打可执行jar包为例进行探讨。首要步骤是需要在pom的<build>
节点下添加Maven的打包插件,常见的打包插件有:
- 核心插件
Maven-compiler-plugin
,用于指定编译级别,默认使用jdk1.5编译
- 打包可执行jar的
Maven-jar-plugin
,用于指定本项目生成的jar包中的MANIFEST.MF文件中的配置,如Class-Path和Main-Class
Maven-assembly-plugin
,支持定制化打包方式,负责将整个项目按照自定义的目录结构打成最终的压缩包,方便实际部署,可在此处设置打包拷贝路径,配置,以及打包好的jar文件等。
Maven-shade-plugin
,用来打可执行包,包含依赖,以及对依赖进行取舍过滤,当你只想将项目打成一个可执行包时,Maven-shade-plugin
非常适合。
打包策略选择:
- 直接打包,不打包依赖包,仅打包出项目中的代码到jar包中。在POM中添加
Maven-compiler-plugin
即可,随后执行Maven package
。直接打包意义不大,首先Pass。
- 将项目和依赖打包成一整个可执行jar包,用到的就是
Maven-shade-plugin
了。这是常规的做法,由于包含了所有依赖jar包,封装了内部细节,包容一且。
- 将依赖jar包输出到lib目录方式,Tomcat就是采用的这种打包策略。这种方案属于将项目结构重新组装,因此要用到的核心打包插件是
Maven-assembly-plugin
。通过配置,将依赖jar输出到lib目录,与构建后的主项目包分离,若程序需要升级仅需替换主程序jar即可,不必连同lib目录一起替换,大大提高了网络传输效率;结构分离整体上也够直观。
在提出最佳实践之前我们先来了解一下打包策略2需要的配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
<build>
<plugins>
<plugin>
<groupId>org.apache.Maven.plugins</groupId>
<artifactId>Maven-shade-plugin</artifactId>
<version>1.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<transformers>
<transformer
implementation="org.apache.Maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.cat.Application</mainClass>
</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>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
|
shade插件绑定的是package生命周期目标,并设置com.cat.Application为Main-Class,以及将META-INF/spring.*文件合并(追加而非覆盖),并过滤掉所有依赖的META/INF中SF,DSA,RSA后缀文件。这里涉及到filter配置和transformer配置。
最佳实践
综上所述,我们选择策略3作为最佳实践(向Tomcat看齐),首先看一下打包后的项目结构:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
D:\BANKSERVER //根目录
│ bankServer.jar //主程序包
│ README.md //说明文档
│ startup.bat //启动脚本
│
├─conf //配置文件目录
│ config.properties //配置文件
│ database.properties //数据库配置文件
│
├─lib //依赖目录
│ aopalliance-1.0.jar
│ bankServer-0.0.1-SNAPSHOT.jar
│ c3p0-0.9.5.2.jar
│ my-Entry-1.0.jar
│ commons-lang3-3.8.1.jar
│ druid-1.1.8.jar
|
这里用到了三个插件,Maven-compiler-plugin
指定编译级别;Maven-jar-plugin主要就是配置了MANIFEST.MF这个文件而已,就是让可执行文件知道自己怎么执行,加载哪些文件执行的描述,剩下的工作交由Maven-assembly-plugin
来处理。
贴上来pom.xml的配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
<build>
<finalName>bankServer</finalName>
<plugins>
<plugin>
<groupId>org.apache.Maven.plugins</groupId>
<artifactId>Maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
<encoding>utf-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.Maven.plugins</groupId>
<artifactId>Maven-jar-plugin</artifactId>
<configuration>
<archive>
<addMavenDescriptor>false</addMavenDescriptor>
<!-- 指定main信息 -->
<manifest>
<!--指定添加项目中使用的外部jar的classpath项-->
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<!-- 此处指定main方法入口的class -->
<mainClass>com.cat.bank.main.Luncher</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<artifactId>Maven-assembly-plugin</artifactId>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<descriptors>
<!--指定详细的外部配置,读取这个文件,防止pom.xml过于臃肿-->
<descriptor>packconf/package.xml</descriptor>
</descriptors>
</configuration>
<!--下面是为了使用 mvn package命令,如果不加则使用mvn assembly -->
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>assembly</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
|
Maven-assembly-plugin
引用的package.xml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
<?xml version="1.0" encoding="UTF-8"?>
<assembly>
<id>bin</id>
<!-- 最终打包成一个用于发布的zip文件 -->
<formats>
<format>zip</format>
</formats>
<!-- Adds dependencies to zip package under lib directory -->
<dependencySets>
<dependencySet>
<!-- 不使用项目的artifact,第三方jar不要解压,打包进zip文件的lib目录 -->
<useProjectArtifact>true</useProjectArtifact>
<outputDirectory>lib</outputDirectory>
<unpack>false</unpack>
</dependencySet>
</dependencySets>
<fileSets>
<!-- 把项目相关的说明文件,打包进zip文件的根目录 -->
<fileSet>
<directory>${project.basedir}</directory>
<outputDirectory>/</outputDirectory>
<includes>
<include>README*</include>
<include>LICENSE*</include>
<include>NOTICE*</include>
</includes>
</fileSet>
<!-- 把项目的配置文件,打包进zip文件的conf目录 -->
<fileSet>
<directory>${project.basedir}/conf</directory>
<outputDirectory>conf</outputDirectory>
<includes>
<include>*</include>
</includes>
</fileSet>
<!-- 把项目的脚本文件目录( src/main/scripts )中的启动脚本文件,打包进zip文件的跟目录 -->
<fileSet>
<directory>${project.build.scriptSourceDirectory}</directory>
<outputDirectory></outputDirectory>
<includes>
<include>startup.*</include>
</includes>
</fileSet>
<!-- 把项目自己编译出来的jar文件,打包进zip文件的根目录 -->
<fileSet>
<directory>${project.build.directory}</directory>
<outputDirectory></outputDirectory>
<includes>
<include>*.jar</include>
</includes>
</fileSet>
</fileSets>
</assembly>
|
==**问题记录:**==
- Java代码读取不到jar包外部配置文件
项目发布的时候通常会修改配置文件,这种情况下就需要把配置文件放到jar外部读取而不能直接打到jar包内。默认的config.properties
文件是放在src/main/resources路径,即classpath下的,采用如下代码读取,构建后config.properties
文件被打到了conf下,尝试多种方案,即使将"config.properties"
加上相对路径也读取不到,若用System.getProperty("user.dir")
这种方法在IDE下就不能运行了,因此亟需找到一个两全其美的办法同时支持在IDE下读取和构建后的外部目录读取。
原读取方法:
1
2
3
4
5
6
7
|
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("conf/config.properties");
Properties p = new Properties();
try {
p.load(inputStream);
} catch (IOException e1) {
e1.printStackTrace();
}
|
解决方案:
1). 将config.properties和database.properties从src/main/resources移到项目根目录的普通文件夹conf
2). 修改读取方式为文件流
1
2
3
4
5
6
7
8
9
|
try {
Properties properties = new Properties();
// 1.加载properties文件
InputStream is =new FileInputStream("conf/config.properties");
// 2.加载输入流
properties.load(is);
} catch (Exception e) {
logger.error("读取配置文件config.properties错误");
}
|
- 本地jar包打包后Maven无法加载
项目中需要引入一个my-Entry.jar的加密jar包,这个包Maven仓库中是没有的。回忆一下前面提到的依赖的system作用域,因此采用如下方式导入了该jar包,但是打包后调用该包内方法时提示Class Not Found。
1
2
3
4
5
6
7
8
|
<dependency>
<groupId>com.cat</groupId>
<artifactId>my-Entry</artifactId>
<!--注意这个版本号,下面会用到-->
<version>1.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/libs/my-Entry.jar</systemPath>
</dependency>
|
这是因为scope为system作用域时不参与运行时,解决这个问题有两种方案:
解决方案:
1)将该jar安装到本地仓库
执行以下命令,发布到本地仓库,然后修改pom.xml将scope
标签去掉即可。
1
|
mvn install:install-file -Dfile=D:\my-Entry.jar -DgroupId=com.cat -DartifactId=ccb-Entry -Dversion=1.0 -Dpackaging=jar
|
2)将外部包加入主jar包META-INF下的MANIFEST.MF文件
这个MANIFEST.MF里指定了外部依赖的jar包查找路径,需要在在Maven-jar-plugin 里配置Class-Path。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
<plugin>
<groupId>org.apache.Maven.plugins</groupId>
<artifactId>Maven-jar-plugin</artifactId>
<configuration>
<archive>
<addMavenDescriptor>false</addMavenDescriptor>
<!-- Manifest specific configuration -->
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<!-- 此处指定main方法入口的class -->
<mainClass>com.cat.bank.main.Luncher</mainClass>
</manifest>
<manifestEntries>
<!--注意这里要加上-1.0版本号,就是你引入本地依赖时指定的那个version-->
<Class-Path>lib/my-Entry-1.0.jar</Class-Path>
</manifestEntries>
</archive>
</configuration>
</plugin>
|
现在用解压软件打开bankServer.jar主jar文件会发现MANIFEST.MF文件中已经加上了my-Entry.jar。但是这时这个my-Entry.jar并没有发布到lib文件夹下,我们需要再配置Maven-assembly-plugin将scope为system的依赖也发布到lib目录,或者可以直接拷贝过去。
在package.xml中追加一行配置,这样就可以正常打包运行啦~
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<dependencySets>
<dependencySet>
<!-- 不使用项目的artifact,第三方jar不要解压,打包进zip文件的lib目录 -->
<useProjectArtifact>true</useProjectArtifact>
<outputDirectory>lib</outputDirectory>
<unpack>false</unpack>
</dependencySet>
<!--将<scope>system</scope>也发布到lib-->
<dependencySet>
<useProjectArtifact>true</useProjectArtifact>
<outputDirectory>lib</outputDirectory>
<unpack>false</unpack>
<scope>system</scope>
</dependencySet>
</dependencySets>
|
(全文完)