序言

笔者前段时间在使用自研框架NF( 传送门 )开发一个自动模板生成工具之后,想将他发布到Linux下,之前一直使用IDE直接run as运行,在遇到发布的时候考虑过发布为war或者jar,在一番抉择之后最终选择了jar(原因是NF自带服务容器,而war为tomcat而生,所以jar更适合NF),所以特意研究了一番如何将普通项目打包成jar发布。

不出意外,最终我成功了,在兴奋之余,希望能够将自己实现的过程及遇到的坑记录下来,让看到有此需求的同学们少走一些弯路!

一、何为Jar

JAR 文件格式以流行的 ZIP 文件格式为基础。与 ZIP 文件不同的是,JAR 文件不仅用于压缩和发布,而且还用于部署和封装库、组件和插件程序,并可被像编译器和 JVM 这样的工具直接使用。在 JAR 中包含特殊的文件,如 manifests 和部署描述符,用来指示工具如何处理特定的 JAR。

更多详情通过 传送门 查阅。

二、发布服务的几种方案

在web开发完成之后,我们往往想要发布服务到外网服务器中,而外网服务器大多是都是Linux系统,这时我们不能已常规方式直接在IDE中运行,需要特定几种形式去发布。

我们最初最常用的方式就是打包成.war 的格式发布到Tomcat的服务容器中,这之后Tomcat会帮助我们解压war包,并加载classes 文件夹下的.class 到内存中,加载完毕之后,我们的服务就可以在服务器中正常运行,但是.war 通常只适合配合Tomcat容器,对于其他服务容器,尤其是自研服务容器来讲,适用性非常差,而Spring Boot 率先打破了常规。

Spring Boot采用jar的方式发布,也就是说,我们可以使用Spring Boot提供的maven插件,通过mvn package 指令将服务打包成jar的形式发布,这就意味着服务中涉及的所有资源(class文件、依赖jar包、静态资源文件)都将会打包在一个jar包之内,在启动这个层次来讲就异常的简单了,只需要通过java -jar xxxx.jar 的方式就可以正常启动服务,这对于我们在自己的服务器中去启动服务来说非常的方便,而Spring Boot是怎么做到这一点的呢?

我们来看一下Spring Boot的pom.xml 依赖插件 spring-boot-maven-plugin ,全配置如下

<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>${start-class}</mainClass>
</configuration>
</plugin>

我们通过之前对Jar包的了解已经得知,一个可执行jar的必要因素就是需要一个主函数入口,在上述配置中,我们可以看到很明确的主函数配置<mainClass>${start-class}</mainClass>, 而占位符${start-class} 的值就是我们平常开发中用来启动Spring Boot的主函数入口所在类,继续深入spring-boot-maven-plugin 中,我们会看到这个插件内部依赖了更多maven自带的原生插件

  • maven-failsafe-plugin
  • maven-jar-plugin
  • maven-surefire-plugin
  • maven-war-plugin
  • maven-resources-plugin
  • maven-shade-plugin

另外少部分插件如下

  • exec-maven-plugin
  • git-commit-id-plugin
  • spring-boot-maven-plugin 自依赖,为了支持自己的插件

从以上插件列表分析,spring-boot-maven-plugin 中包含了很多maven原生插件,支持jar 和 war 的格式发布,我们只站在打包可执行jar的角度来分析以上插件的作用,可以这样理解

  • maven-jar-plugin

    设定manifest 中的Main-Class 参数

  • maven-shade-plugin

    用于把多个jar包,打成1个jar包

  • maven-resources-plugin

    处理将项目资源(src/main/ 和 src/test)复制到输出目录的操作

  • maven-surefire-plugin 和 maven-failsafe-plugin

    执行测试用例

依赖插件的同时,spring-boot-maven-plugin 中还使用<resources> 标签来重新定义jar包内部结构。

以上信息是否满足将我们的服务打包成可执行jar呢?我们分析一下,如果达到我们想要的效果,我们需要

  1. 自动配置主函数入口
  2. 静态资源打包
  3. 依赖打包

对比上述插件,我们需要的功能都有,那么我们是否可以使用上方的插件及标签自己写个打包插件试试呢? 当然!这里就不带着大家亲自尝试了,因为下面我要讲另一种Spring Boot没有用到的maven插件进行打包!

三、maven-assembly-plugin 插件打包Jar

maven-assembly-plugin 是一个超灵活maven项目打包工具,提供默认配置和自定义配置,同时提供Main-Class 的配置、静态文件Copy及依赖打包的功能,这里是官方对于这款插件的介绍

The Assembly Plugin for Maven is primarily intended to allow users to aggregate the project output along with its dependencies, modules, site documentation, and other files into a single distributable archive. 
Your project can build distribution “assemblies” easily, using one of the convenient, prefabricated assembly descriptors. These descriptors handle many common operations, such as packaging a project’s artifact along with generated documentation into a single zip archive. Alternatively, your project can provide its own descriptor and assume a much higher level of control over how dependencies, modules, file-sets, and individual files are packaged in the assembly.

大概意思就是

Maven的组装插件主要是允许用户将项目输出与它的依赖项、模块、站点文档和其他文件一起集成到一个可分发的归档文件中。您的项目可以使用一种方便的预制组装描述符轻松地构建分布“程序集”。这些描述符处理许多常见的操作,例如将项目的工件连同生成的文档打包到一个zip归档文件中。或者,您的项目可以提供自己的描述符,并对依赖项、模块、文件集和各个文件如何在程序集中打包具有更高的控制级别。

通俗一点,你可以自定义你的项目打包格式,maven-assembly-plugin 更像是多个打包插件的集成,并提供多种打包的文件格式,使用方面也很方便,最简单的一个使用如下

<plugin>
<artifactId> maven-assembly-plugin </artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>${main-class}</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>

descriptorRefs标签内部可以配置使用官方定制好的打包方式,其中如下可选配置 
- bin 
- jar-with-dependencies 
- src 
- project 
不过官方定制好的有很大的局限性,我们可以将上述改成如下配置,来自定义打包方式

<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>${main-class}</mainClass>
</manifest>
</archive>
<descriptors>
<descriptor>src/main/resource/assembly-fat.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>

可以看出,上述配置去掉了<descriptorRefs>标签,增加了<descriptors>配置,并且子标签中还指向了src/main/resource/assembly-fat.xml 这个配置文件,如果你的思路跟着这篇文章走,一定可以猜得到,这个配置文件就是我们自定义打包方式的入口!它的格式如下

<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
<id>distribution</id> <formats>
<format>jar</format>
</formats> <dependencySets>
<dependencySet>
<outputDirectory>/</outputDirectory>
<useProjectArtifact>true</useProjectArtifact>
<unpack>true</unpack>
<scope>runtime</scope>
</dependencySet>
</dependencySets> <includeBaseDirectory>false</includeBaseDirectory> <fileSets>
<fileSet>
<directory>${basedir}</directory>
<includes>
<include>*.txt</include>
</includes>
<excludes>
<exclude>README.txt</exclude>
<exclude>NOTICE.txt</exclude>
</excludes>
</fileSet>
</fileSets> <files>
<file>
<source>README.txt</source>
<outputDirectory>/</outputDirectory>
<filtered>true</filtered>
</file>
<file>
<source>NOTICE.txt</source>
<outputDirectory>/</outputDirectory>
<filtered>true</filtered>
</file>
</files>
</assembly>

下面是标签的相关介绍

  • <id> 生成文件的后缀,如果有,文件名将会是${artifactId}-${id}.jar
  • <formats> 生成文件的格式,可以同时生成多个格式的目标文件
  • dependencySets 依赖jar的打包方式
  • includeBaseDirectory 是否将项目目录引入进来,如果是True的话,生成的目标文件打开之后将会是项目主目录,我们打包的资源将会被放于这个主目录中(推荐Fasle,因为Main-Class路径通常直接是类路径)
  • <fileSets> 引入静态资源的配置(目录级)
  • files 引入静态资源的配置(文件级)

以上是最常用的几种标签,更多的配置大家可以查阅官网 传送门

配置完成之后可以通过mvn assembly:assembly或者mvn package指令打包。

介绍完毕,下面会拉取笔者自己用NF框架开发的模板工具来为大家演示一下maven-assembly-plugin在实战中的使用!

四、Jar方式发布服务实战

首先是项目结构

Project
│ LICENSE
│ pom.xml =》pom文件
│ README.md
├─src
│ └─main
│ ├─java =》源码目录
│ └─resource =》配置文件目录
└─web =》UI静态资源

从结构中可以看出,我们需要手动配置的打包资源是src/main/resourceweb这两个目录,所以我们需要所有配置,将上述两个目录随着我们的.class文件一起打包进jar中,首先在原pom.xml保持不变的基础上插入maven-assembly-plugin 插件

<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<archive>
<manifest>
<mainClass>org.nico.ct.CtApplication</mainClass>
</manifest>
</archive>
<descriptors>
<descriptor>src/main/resource/assembly-fat.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>

接下来编辑src/main/resource/assembly-fat.xml文件配置打包

<assembly
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
<id>RELEASE</id> <formats>
<format>jar</format>
</formats> <dependencySets>
<dependencySet>
<outputDirectory>/</outputDirectory>
<useProjectArtifact>true</useProjectArtifact>
<unpack>true</unpack>
<scope>runtime</scope>
</dependencySet>
</dependencySets> <includeBaseDirectory>false</includeBaseDirectory> <fileSets>
<fileSet>
<directory>src/main/resource</directory>
<outputDirectory>/</outputDirectory>
<includes>
<include>/**</include>
</includes>
</fileSet>
<fileSet>
<directory>web</directory>
<outputDirectory>/web</outputDirectory>
<includes>
<include>/**</include>
</includes>
</fileSet>
</fileSets> <files>
<file>
<source>README.md</source>
<outputDirectory>/</outputDirectory>
</file>
</files> </assembly>

然后运行mvn assembly:assembly,等待maven构建成功

...
[INFO] META-INF/ already added, skipping
[INFO] META-INF/MANIFEST.MF already added, skipping
[INFO] org/ already added, skipping
[INFO] org/nico/ already added, skipping
[INFO] META-INF/maven/ already added, skipping
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 7.627 s
[INFO] Finished at: 2018-06-30T15:39:47+08:00
[INFO] Final Memory: 24M/269M
[INFO] ------------------------------------------------------------------------

看到BUILD SUCCESS之后,你会发现项目target目录中会有两个jar

  • CoffeeTime-0.0.1-SNAPSHOT.jar
  • CoffeeTime-0.0.1-SNAPSHOT-RELEASE.jar

文件名请忽略,后缀带RELEASE的jar就是maven-assembly-plugin插件生成的jar,解压看下目录

│  assembly-fat.xml
│ cat-mysql-nico.xml
│ cat-mysql.xml
│ cat-redis-nico.xml
│ cat-redis.xml
│ cat.xml
│ logno.properties
│ module-info.class
│ README.md
├─com
│ ├─mchange
│ │
│ └─mysql
├─images
├─META-INF
│ ├─maven
│ │
│ └─services
├─net
│ └─sf
├─org
│ ├─apache
│ │
│ ├─gjt
│ │
│ ├─nico
│ ├─objectweb
│ └─slf4j

├─redis
│ └─clients

└─web
├─images
├─page
├─plugins
├─script
├─style
├─video
└─videojs

路径没问题,我们试下能不能运行,切到jar包所在的目录,执行jar -jar CoffeeTime-0.0.1-SNAPSHOT-RELEASE.jar运行之 

SUCCESS !

深入浅说服务如何以Jar包的方式发布的更多相关文章

  1. 通过导入Jar包的方式使用JSONObject

    如果想要在Java中使用JSONObject,而且只想通过导入jar包的方式下,那么仅仅导入Json的jar包还是不够的. 不然会报:java.lang.ClassNotFoundException: ...

  2. springboot服务引入外部jar包在windows运行正常,在linux环境上无法加载到引入jar包的类

    一.问题描述 最近开发了一个springboot程序,需要依赖第三方jar包,这个jar包无法直接通过pom远程仓库下载,需要从自己本地引入,于是配置pom文件如下:将本地jar包引入工程,syste ...

  3. [Spark Core] Spark 使用第三方 Jar 包的方式

    0. 说明 Spark 下运行job,使用第三方 Jar 包的 3 种方式. 1. 方式一 将第三方 Jar 包分发到所有的 spark/jars 目录下 2. 方式二 将第三方 Jar 打散,和我们 ...

  4. Maven将代码及依赖打成一个Jar包的方式

    Maven可以使用mvn package指令对项目进行打包,如果使用java -jar xxx.jar执行运行jar文件,会出现"no main manifest attribute, in ...

  5. java命令执行jar包的方式

    http://www.cnblogs.com/adolfmc/archive/2012/10/07/2713562.html 大家都知道一个java应用项目可以打包成一个jar,当然你必须指定一个拥有 ...

  6. storm学习之六-使用Maven 生成jar包多种方式

    Maven可以使用mvn package指令对项目进行打包,如果使用java -jar xxx.jar执行运行jar文件,会出现"no main manifest attribute, in ...

  7. git+jenkins jar包代码的发布加新建项目

    1.本地仓库  java开发 把代码上传上来 ,问一下他要上传到的主机ip , 分支 2.本地 , 设置-->仓库 更新数据,让他同步到南阳gitlab, 若没有这个项目,需要创建相同名字的项目 ...

  8. eclipse 添加jar包的方式

    参考资料地址:http://blog.csdn.net/mazhaojuan/article/details/21403717

  9. dubbo服务达成jar包

    <build> <finalName>dubbo-provider</finalName> <!-- jar包名,一般设置为提供者服务名 --> < ...

随机推荐

  1. 【7.9校内test】T2 极值问题

    这个题真的,毫无思路的说,但是我们会打表啊: lz的打表之路: 当然是手写一个暴力啦(*^▽^*)! 然后滚去配置lemon测试一下暴力可以得多少分qwq: 是的40分呢! 然后其实看上面也能看出来一 ...

  2. ubuntu18下lamp虚拟路劲配置

    一.配置二级域名 修改hosts文件,模拟dns解析. 位置:/etc/hosts 添加 127.0.0.1  myweb.service.com 二.创建项目目录 apache默认目录是/var m ...

  3. 01:kubernetes基础

    1.1kubernetes简介   参考博客:https://www.kubernetes.org.cn/k8s 1.kubernetes介绍 1. Kubernetes是容器集群管理系统,是一个开源 ...

  4. ELK的搭建以及使用

    一.架构如图: 二.工作机制: 在需要收集日志的应用上安装filebeat(需要修改配置文件,配置文件稍后介绍),启动filebeat后,会收集该应用的日志推送给redis,然后logstash从re ...

  5. c#服务端图片打包下载

    一,设计多图片打包下载逻辑:1,如果是要拉取腾讯云等资源服务器的图片,2,我们先把远程图片拉取到本地的临时文件夹,3,然后压缩临时文件夹,4,压缩完删除临时文件夹,5,返回压缩完给用户,6,用户就去请 ...

  6. 09.AutoMapper 之自定义类型转换器(Custom Type Converters)

    https://www.jianshu.com/p/47054d92db2a 自定义类型转换器(Custom Type Converters) 有时需要完全控制一种类型到另一种类型的转换.这一般发生在 ...

  7. Vue组件通信方式(8种)

    1.一图认清组件关系名词 父子关系:A与B.A与C.B与D.C与E 兄弟关系:B与C 隔代关系:A与D.A与E 非直系亲属:D与E 总结为三大类: 父子组件之间通信 兄弟组件之间通信 跨级通信 2.8 ...

  8. 77. Combinations (JAVA)

    Given two integers n and k, return all possible combinations of k numbers out of 1 ... n. For exampl ...

  9. 解释c# Peek 方法

    peek是用来确定你read的文件是否结束了,如果结束了会返回int型 -1 , 举个例子,你可以在输出每一行之前检查一下文件是否结尾,如果没结束就输出此行. StreamReader sr = ne ...

  10. ACR095 删一个求中位数 贪心求最大组合数 行列变换模拟(搜索)

    A B #include <bits/stdc++.h> #define PI acos(-1.0) #define mem(a,b) memset((a),b,sizeof(a)) #d ...