之前在 从使用传统Web框架到切换到Spring Boot后的总结 中提到关于 Spring Boot 编译打包,Spring Boot 应用程序不用额外部署到外部容器中,可以直接通过 Maven 命令将项目编译成可执行的 jar 包,然后通过 java -jar 命令启动即可,非常方便。

最近有小伙伴私信我说,打 jar 包方便是方便,就是每次打包出来的 jar 太大了,先不说上传时间的问题,如果只修改了 1 个类就需要重新打包项目,然后重新上传项目到服务器,怎么觉得还不如我之前使用 war 包方便呢,使用 war 包时,虽然要部署到 Tomcat 中,但只需要将修改的 class 替换一下,重启一下 Tomcat 就可以了。。。

其实到底选择哪种打包方式,主要还是看个人习惯和业务场景需求,毕竟 Spring Boot 也支持打包 war 包的。

今天的重点不是打包方式,而是解决困惑了小伙伴打包的 jar 太大的问题。

正常打包项目

给 Spring Boot 打包大家应该很熟了吧,只需要在 pom.xml 文件中配置 spring-boot-maven-plugin 打包插件:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

然后在项目根目录执行 mvn clean pavkage 就可以完成打包了,如下是我本地的一个项目打包情况:

可以看到打包出的 jar 应用是相当的大了,如小伙伴说的一样,如果每次修改一个 class 文件或者配置文件,就需要重新打包然后上传服务器的话,那确实是太麻烦了,可能上传就浪费大部分时间。。。

应用瘦身(分离lib和配置文件)

其实 jar 包大的原因在于所有的依赖包全部集成在 jar 包里面,如下是瘦身前的 jar 包内部结构:

其中 classes 就是我们项目的代码,仅仅1.3M,而 129MB 的 lib 目录是项目中所有的依赖(比如spinrg、Hibernate等依赖),如果我们能把这个 lib 目录提取出来,整个项目就会变得特别小了。说干就干。

我们知道 Spring Boot 的打包终究是依赖于 Maven ,所以想到更改打包信息,无非就是指定 Maven 的配置。

在 pom.xml 添加如下信息(后文解释):

<build>
   <finalName>你想要的jar包名称</finalName>
    <plugins>
      <!-- 1、编译出不带 lib 文件夹的Jar包 -->
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <!--表示编译版本配置有效-->
                <fork>true</fork>
                <!--引入第三方jar包时,不添加则引入的第三方jar不会被打入jar包中-->
                <includeSystemScope>true</includeSystemScope>
                <!--排除第三方jar文件-->
                <includes>
                    <include>
                        <groupId>nothing</groupId>
                        <artifactId>nothing</artifactId>
                    </include>
                </includes>
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    
    <!-- 2、完成对Java代码的编译,可以指定项目源码的jdk版本,编译后的jdk版本,以及编码 -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
              <!-- 源代码使用的JDK版本 --> 
                <source>${java.version}</source>
                <!-- 需要生成的目标class文件的编译版本 -->
                <target>${java.version}</target>
                <!-- 字符集编码 -->
                <encoding>UTF-8</encoding>
                <!-- 用来传递编译器自身不包含但是却支持的参数选项 -->  
                <compilerArguments>
                    <verbose/>
                    <!-- windwos环境(二选一) -->
                    <bootclasspath>${java.home}/lib/rt.jar:${java.home}/lib/jce.jar</bootclasspath>
                    <!-- Linux环境(二选一) -->
                    <bootclasspath>${java.home}/lib/rt.jar:${java.home}/lib/jce.jar</bootclasspath>
                </compilerArguments>
            </configuration>
        </plugin>

        <!-- 3、将所有依赖的jar文件复制到target/lib目录 -->
        <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>
                      <!--复制到哪个路径,${project.build.directory} 缺醒为 target,其他内置参数见下面解释-->
                        <outputDirectory>${project.build.directory}/lib</outputDirectory>
                        <overWriteReleases>false</overWriteReleases>
                        <overWriteSnapshots>false</overWriteSnapshots>
                        <overWriteIfNewer>true</overWriteIfNewer>
                    </configuration>
                </execution>
            </executions>
        </plugin>
        
    <!-- 4、指定启动类,指定配置文件,将依赖打成外部jar包 -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <configuration>
                <archive>
                    <manifest>
                        <!-- 是否要把第三方jar加入到类构建路径 -->
                        <addClasspath>true</addClasspath>
                        <!-- 外部依赖jar包的最终位置 -->
                        <classpathPrefix>lib/</classpathPrefix>
                        <!-- 项目启动类 -->
                        <mainClass>com.javam4.MyApplication</mainClass>
                    </manifest>
                </archive>
                <!--资源文件不打进jar包中,做到配置跟项目分离的效果-->
                <excludes>
                    <!-- 业务jar中过滤application.properties/yml文件,在jar包外控制 -->
                    <exclude>*.properties</exclude>
                    <exclude>*.xml</exclude>
                    <exclude>*.yml</exclude>
                </excludes>
            </configuration>
        </plugin>

    </plugins>
</build>

如下一一细拆如上配置:

1、spring-boot-maven-plugin

Springboot 默认使用 spring-boot-maven-plugin 来打包,这个插件会将项目所有的依赖打入项目 jar 包里面,正常打包时 spring-boot-maven-plugin 结构如下:

<plugin>  
    <groupId>org.springframework.boot</groupId>   
    <artifactId>spring-boot-maven-plugin</artifactId>  
    <configuration>  
        <mainClass>com.javam4.MyApplication</mainClass>  
        <layout>ZIP</layout>  
    </configuration>  
    <executions>  
    <execution>  
         <goals>  
             <goal>repackage</goal>  
         </goals>  
     </execution>  
   </executions>  
</plugin>  

如下是提取的修改项:

<configuration>
    <!--表示编译版本配置有效-->
    <fork>true</fork>
    <!--引入第三方jar包时,不添加则引入的第三方jar不会被打入jar包中-->
    <includeSystemScope>true</includeSystemScope>
    <!--排除第三方jar文件-->
    <includes>
        <include>
            <groupId>nothing</groupId>
            <artifactId>nothing</artifactId>
        </include>
    </includes>
</configuration>

修改的作用:

  • includeSystemScope:jar包分两种,一种是spring、mybatis等这种项目依赖的,再就是我们外部手动引入的第三方 jar 依赖,如果该参数不设置为 true 的话是不能被打包进来的~
  • includes:这个节点就是排除项目中所有的 jar,那还怎么打包?

其实我们需要将打包插件替换为 maven-jar-plugin,然后使用该插件拷贝依赖到 jar 到外面的 lib 目录。

2、maven-xxx-plugin

从 2、3、4 你会发现用到了 maven-xxx-plugin 格式的三种插件,简单说一下这三者的作用:

  • maven-compiler-plugin:

    完成对Java代码的编译,可以指定项目源码的jdk版本、编译后的jdk版本,以及编码,如果不写这个插件也是没问题的,不写会使用默认的 jdk 版本来处理,只是这样容易出现版本不匹配的问题,比如本地maven环境用的3.3.9版本,默认会使用jdk1.5进行编译,而项目中用的jdk1.8的编译环境,那就会导致打包时编译不通过。

  • maven-dependency-plugin:

    作用就是将所有依赖的jar文件复制到指定目录下,其中涉及到的 ${project.xx} 见下文补充。

  • maven-jar-plugin:

    主要作用就是将maven工程打包成jar包。

主要说一下 maven-jar-plugin 插件的如下配置:

<configuration>
    <!--资源文件不打进jar包中,做到配置跟项目分离的效果-->
    <excludes>
        <!-- 业务jar中过滤application.properties/yml文件,在jar包外控制 -->
        <exclude>*.properties</exclude>
        <exclude>*.xml</exclude>
        <exclude>*.yml</exclude>
    </excludes>
</configuration>

打包时排除资源配置文件,如果排除了配置文件那么项目启动是怎么读取呢?

配置文件有这么一个默认的优先级:

当前项目config目录下 > 当前项目根目录下 > 类路径config目录下 > 类路径根目录下

因此只需要将配置文件复制一份到与 jar 包平级目录下,或者与jar包平行config目录下,就能优先使用此配置文件,达到了伪分离目的。

最终的目录结构如下:

Maven 中的内置变量说明:

  • ${basedir} 项目根目录
  • ${project.build.directory} 构建目录,缺省为target
  • ${project.build.outputDirectory} 构建过程输出目录,缺省为target/classes
  • ${project.build.finalName} 产出物名称,缺省为{project.artifactId}-${project.version}
  • ${project.packaging} 打包类型,缺省为jar
  • ${project.packaging} 打包类型,缺省为jar
  • ${project.xxx} 当前pom文件的任意节点的内容

瘦身总结

Spring Boot 框架提供了一套自己的打包机制 — spring-boot-maven-plugin,Springboot 默认使用该插件来打包,打包时会将项目所有的依赖打入项目 jar 包里面,如果我们想要抽离依赖的 jar 仅仅使用该插件是不行的,就需要将打包插件替换为 maven-jar-plugin,并拷贝所有的依赖到 jar 外面的 lib 目录。

项目打包时,在分离依赖 jar 包基础上,我们又排除了配置文件,因为配置文件有一个默认的读取路径:

当前项目config目录下 > 当前项目根目录下 > 类路径config目录下 > 类路径根目录下

我们只需要在当前项目 jar 包同级目录创建一个 config 文件夹,然后将配置文件复制一份,这样就达到了伪分离目的。

之后再修改配置文件,比如端口号、数据库连接信息等,就不需要重新打包项目了,直接修改完配置文件重启项目就可以了。

而经过分离依赖后的 jar 包从原来的100多兆到现在的1兆,如果后面需要变更业务逻辑,只需要轻量的编译项目,快速的实现项目的上传替换,有效的减少了网络开销,提高项目部署的效率。

博客地址:https://niceyoo.cnblogs.com

更多原创内容可以移步我的公众号,回复「面试」获取我整理的2020面经。

Spring Boot 项目瘦身指南,瘦到不可思议!129M->1.3M的更多相关文章

  1. 我的Android进阶之旅------>Android APP终极瘦身指南

    首先声明,下面文字转载于: APK瘦身实践 http://www.jayfeng.com/2015/12/29/APK%E7%98%A6%E8%BA%AB%E5%AE%9E%E8%B7%B5/ APP ...

  2. 使用Docker部署Spring boot项目

    Docker是一个使用广泛的Linux容器管理工具包,它允许用户创建镜像,并将其容器实例化.通过本指南,我们可以学习到如何使用Docker部署Spring Boot项目. 先决条件 开发之前,你必须具 ...

  3. Spring Boot 2.0 升级指南

    Spring Boot 2.0 升级指南 前言 Spring Boot已经发布2.0有5个月多,多了很多新特性,一些坑也慢慢被填上,最近有空,就把项目中Spring Boot 版本做了升级,顺便整理下 ...

  4. spring boot学习01【搭建环境、创建第一个spring boot项目】

    1.给eclipse安装spring boot插件 Eclipse中安装Spring工具套件(STS): Help -> Eclipse Marketplace... 在Search标签或者Po ...

  5. Gitlab CI 集成 Kubernetes 集群部署 Spring Boot 项目

    在上一篇博客中,我们成功将 Gitlab CI 部署到了 Docker 中去,成功创建了 Gitlab CI Pipline 来执行 CI/CD 任务.那么这篇文章我们更进一步,将它集成到 K8s 集 ...

  6. 构建一个简单的Spring Boot项目

    11 构建一个简单的Spring Boot项目 这个章节描述如何通过Spring Boot构建一个"Hello Word"web应用,侧重介绍Spring Boot的一些重要功能. ...

  7. spring boot 集成apollo 快速指南

    目前市面上流行的三大配置中心框架:Spring CLoud Config .Alibaba Nacos 以及携程apollo, 我们相应架构组号召,就使用Apollo吧. Work Flow 简单解释 ...

  8. Spring Boot项目的打包和部署

    补充一点:搜索了下别人Spring Boot部署方案,大多都说:①packaging设为war:②要添加Spring Boot的tomcat依赖:③修改output路径,但是使用STS新建Spring ...

  9. 新建一个新的spring boot项目

    简单几步,在Eclipse中创建一个新的spring Boot项目: 1.Eclipse中安装STS插件: Help -> Eclipse Marketplace... Search或选择&qu ...

  10. spring boot项目发布tomcat容器(包含发布到tomcat6的方法)

    spring boot因为内嵌tomcat容器,所以可以通过打包为jar包的方法将项目发布,但是如何将spring boot项目打包成可发布到tomcat中的war包项目呢? 1. 既然需要打包成wa ...

随机推荐

  1. Hadoop高可用

    一.原因 - NameNode是HDFS的黑心配置HDFS有事hadoop的核心组件 NameNode 在Hadoop及群众至关重要 - NameNode的宕机导致集群的不可用 二.解决方案 其中 N ...

  2. 读书笔记:《数据结构与算法分析Java语言描述》

    目录 第 3 章 表.栈和队列 3.2 表 ADT 3.2.1 表的简单数组实现 3.2.2 简单链表 3.3 Java Collections API 中的表 3.3.1 Collection 接口 ...

  3. JavaSE基础语法学习-流程控制

    流程控制 用户交互Scanner Scanner**对象** 下面是创建 Scanner 对象的基本语法: Scanner s = new Scanner(System.in); 接下来我们演示一个最 ...

  4. win10免费通用永久激活秘钥分享 win1020H2正式版序列号推荐

    win10最新永久激活密钥神key如下: win10免费密钥 NW06D-722C0-5X6A1-MQ83B-2ER3D win10免费密钥 NP3KM-NQZD6-X406E-1QPKR-4VRZD ...

  5. java.sql.SQLException: Error: Error: could not match input

    impala执行sql,输出后我在控制台粘贴执行OK,奇怪了. java.sql.SQLException: Error: Error: could not match input 原因竟然是myba ...

  6. axios封装接口

    我们一般都是在做一个大型项目的时候,需要用到很多接口时,我们为了方便使用,就把接口封装起来. 先安装axios命令 :npm install axios --save 那么思路是什么呢? 首先在src ...

  7. 再聊 Blazor,它是否值得你花时间学习

    之前写了一篇文章<快速了解 ASP.NET Core Blazor>,大家关心最多的问题是,我该不该花时间去学习 Blazor.今天聊聊这个话题,并表达一下我个人的看法. 在此之前,我还是 ...

  8. git 最新笔记,工作中的必会技能

    1.状态查看: git status 可以查看工作区,暂存区的状态 untracked 在暂存区没有该文件 modified 修改过 staged 使用git add 暂存过 2.添加操作: git ...

  9. javascript九宫格碰撞检测

      JS九宫格碰撞检测这个东西 以前学过  这次主要是做面试项目web版的win10 桌面图片需要用碰撞检测 再写的时候竟然完全忘记了碰撞检测原理 和怎么写 综合来说还是写的太少  今天再学了一下 理 ...

  10. DocView 现在支持自定义 Markdown 模版了!

    前言 有小伙伴反馈说希望可以自定义 Markdown 模版,这样就可以导出自己想要的样式了!这个功能可以有,毕竟大家不可能都生成一模一样的文档.现在来一起看看如何实现自定义模版吧! 设置模版 Sett ...