Springboot打包执行源码解析
一、打包
Springboot打包的时候,需要配置一个maven插件[spring-boot-maven-plugin]
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
这个插件提供了5个功能模块,包括:
- build-info:生成项目的构建信息文件build-info.properties
- repackage:默认goal。在mvn package执行之后,再次打包生成可执行的jar/war,同时重命名mvn package生成的jar/war为 ***.origin
- run:这个可以用来运行Spring Boot应用
- start:这个在 mvn integration-test 阶段,进行 Spring Boot 应用生命周期的管理
- stop:这个在 mvn integration-test 阶段,进行 Spring Boot 应用生命周期的管理
如果想mvn package打的包不被重命名,可以配置classifier,这样Springboot打包生成的可执行jar就是XXX-executable.jar了。
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<classifier>executable</classifier>
</configuration>
</plugin>
</plugins>
</build>
二、区别
Springboot打的包和Maven打的包区别在哪里呢?
把maven package打的包 a.jar.original 重命名成a-original.jar;然后和Springboot打的包a.jar比较一下,发现:
Springboot打的包的MANIFEST.MF文件多了如下几行:
...
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.xxx.XxxApplication
...
这里的Start-Class就是我们自己写的代码的main入口类了;而Main-Class是Springboot给我们添加的启动类;
三、源码Debug
要了解Springboot可执行jar的执行过程,最好的途径就是debug一下。下面就先配置一下。
设置执行jar的命令
一般执行jar使用的命令是 java -jar xxx.jar
debug需要开调试端口,命令是 java -agentlib:jdwp=transport=dt_socket,server=y,address=5005,suspend=y -jar xxx.jar在idea中配置remote debug,设置端口号为5005,Host为localhost
在项目的pom.xml中,增加spring-boot-loader的maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader</artifactId>
<version>1.5.10.RELEASE</version>
</dependency>
完成上述配置之后,先debug启动程序,在打开idea的调试
四、源码解析
先看看前面提说到的Main-Class
即org.springframework.boot.loader.JarLauncherpublic class JarLauncher extends ExecutableArchiveLauncher { static final String BOOT_INF_CLASSES = "BOOT-INF/classes/"; static final String BOOT_INF_LIB = "BOOT-INF/lib/"; public JarLauncher() {
} protected JarLauncher(Archive archive) {
super(archive);
} @Override
protected boolean isNestedArchive(Archive.Entry entry) {
if (entry.isDirectory()) {
return entry.getName().equals(BOOT_INF_CLASSES);
}
return entry.getName().startsWith(BOOT_INF_LIB);
} public static void main(String[] args) throws Exception {
new JarLauncher().launch(args);
} }
跟着进父抽象类Launcher的launch方法
在这个方法里面,通过getClassPathArchives()把jar包里的jar抽象成Archive对象列表。
在Springboot loader中,抽象出了Archive概念。
一个archive可以是一个jar(JarFileArchive),也可以是一个文件目录(ExplodedArchive)。这些都可以理解为Springboot抽象出来的统一访问资源的层。
此List包括:- \BOOT-INF\classes (项目的class)
- \BOOT-INF\lib 目录下的所有jar
遍历List,获得每个Archive的URL,组成List
创建一个自定义的类加载器LaunchedURLClassLoader。这个类加载器继承自jdk自带的java.net.URLClassLoader
加载,创建MainMethodRunner
看下Launcher的方法,逻辑很清晰public abstract class Launcher { /**
* 1、获取List<Archive>
* 2、创建LaunchedURLClassLoader
* 3、找到main class
* 4、加载
*/
protected void launch(String[] args) throws Exception {
JarFile.registerUrlProtocolHandler();
ClassLoader classLoader = createClassLoader(getClassPathArchives());
launch(args, getMainClass(), classLoader);
} /**
* 创建classloader
*/
protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
List<URL> urls = new ArrayList<URL>(archives.size());
for (Archive archive : archives) {
urls.add(archive.getUrl());
}
return createClassLoader(urls.toArray(new URL[urls.size()]));
} /**
* 创建LaunchedURLClassLoader
*/
protected ClassLoader createClassLoader(URL[] urls) throws Exception {
return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
} /**
* 通过指定的classloader,加载main class
*/
protected void launch(String[] args, String mainClass, ClassLoader classLoader)
throws Exception {
Thread.currentThread().setContextClassLoader(classLoader);
createMainMethodRunner(mainClass, args, classLoader).run();
} /**
* 创建MainMethodRunner
*/
protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) {
return new MainMethodRunner(mainClass, args);
} /**
* 抽象方法,用来获取main class
*/
protected abstract String getMainClass() throws Exception; /**
* 抽象方法,用来获取List<Archive>
*/
protected abstract List<Archive> getClassPathArchives() throws Exception; protected final Archive createArchive() throws Exception {
ProtectionDomain protectionDomain = getClass().getProtectionDomain();
CodeSource codeSource = protectionDomain.getCodeSource();
URI location = (codeSource == null ? null : codeSource.getLocation().toURI());
String path = (location == null ? null : location.getSchemeSpecificPart());
if (path == null) {
throw new IllegalStateException("Unable to determine code source archive");
}
File root = new File(path);
if (!root.exists()) {
throw new IllegalStateException(
"Unable to determine code source archive from " + root);
}
return (root.isDirectory() ? new ExplodedArchive(root)
: new JarFileArchive(root));
}
}
执行MainMethodRunner的run方法,启动主类的main
public class MainMethodRunner { private final String mainClassName; private final String[] args; public MainMethodRunner(String mainClass, String[] args) {
this.mainClassName = mainClass;
this.args = (args == null ? null : args.clone());
} /**
* 通过反射找到main方法,然后invoke
*/
public void run() throws Exception {
Class<?> mainClass = Thread.currentThread().getContextClassLoader()
.loadClass(this.mainClassName);
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
mainMethod.invoke(null, new Object[] { this.args });
}
}
Springboot打包执行源码解析的更多相关文章
- SpringBoot源码学习1——SpringBoot自动装配源码解析+Spring如何处理配置类的
系列文章目录和关于我 一丶什么是SpringBoot自动装配 SpringBoot通过SPI的机制,在我们程序员引入一些starter之后,扫描外部引用 jar 包中的META-INF/spring. ...
- springboot自动配置源码解析
springboot版本:2.1.6.RELEASE SpringBoot 自动配置主要通过 @EnableAutoConfiguration, @Conditional, @EnableConfig ...
- SpringBoot自动装配源码解析
序:众所周知spring-boot入门容易精通难,说到底spring-boot是对spring已有的各种技术的整合封装,因为封装了所以使用简单,也因为封装了所以越来越多的"拿来主义" ...
- SpringBoot Profile使用详解及配置源码解析
在实践的过程中我们经常会遇到不同的环境需要不同配置文件的情况,如果每换一个环境重新修改配置文件或重新打包一次会比较麻烦,Spring Boot为此提供了Profile配置来解决此问题. Profile ...
- 了解腾讯开源的多渠道打包技术 VasDolly源码解析
一.概要 大家应该都清楚,大家上线app,需要上线各种平台,比如:小米,华为,百度等等等等,我们多数称之为渠道,如果发的渠道多,可能有上百个渠道. 针对每个渠道,我们希望可以获取各个渠道的一些独立的统 ...
- SpringBoot 2.0.3 源码解析
前言 用SpringBoot也有很长一段时间了,一直是底层使用者,没有研究过其到底是怎么运行的,借此机会今天试着将源码读一下,在此记录...我这里使用的SpringBoot 版本是 2.0.3.RE ...
- SpringBoot之DispatcherServlet详解及源码解析
在使用SpringBoot之后,我们表面上已经无法直接看到DispatcherServlet的使用了.本篇文章,带大家从最初DispatcherServlet的使用开始到SpringBoot源码中Di ...
- SpringBoot的条件注解源码解析
SpringBoot的条件注解源码解析 @ConditionalOnBean.@ConditionalOnMissingBean 启动项目 会在ConfigurationClassBeanDefini ...
- springboot源码解析-管中窥豹系列之Runner(三)
一.前言 Springboot源码解析是一件大工程,逐行逐句的去研究代码,会很枯燥,也不容易坚持下去. 我们不追求大而全,而是试着每次去研究一个小知识点,最终聚沙成塔,这就是我们的springboot ...
随机推荐
- mysql abs() 获取绝对值
mysql> -); +----------+ | abs(-) | +----------+ | | +----------+ row in set (0.00 sec)
- 【POJ3083】Children of the Candy Corn
本题传送门 本题知识点:深度优先搜索 + 宽度优先搜索 本题题意是求三个路径长度,第一个是一直往左手边走的距离,第二个是一直往右手边走的距离,第三个是最短距离. 第三个很好办,就是一个简单的bfs的模 ...
- 微信小程序 base64格式图片的显示及保存
当我们拿到如下base64格式的图片(如下图)时, base64格式的图片数据: 如何显示 ? 使用image标签,src属性添加data:image/png;base64, (注意:若imgData ...
- rancher2.x的安装
docker run -d --restart=unless-stopped \-p 80:80 -p 443:443 \-v /var/lib/rancher:/var/lib/rancher/ ...
- php中socket、fsockopen、curl、stream 区别
socket 水泥.沙子,底层的东西fsockopen 水泥预制件,可以用来搭房子curl 毛坯房,自己装修一下就能住了 水泥.沙子不但可以修房子,还能修路.修桥.大型雕塑.socket也是,不但可以 ...
- 第10组 Beta冲刺(5/5)
链接部分 队名:女生都队 组长博客: 博客链接 作业博客:博客链接 小组内容 恩泽(组长) 过去两天完成了哪些任务 描述 将数据分析以可视化形式展示出来 新增数据分析展示等功能API 服务器后端部署, ...
- vs2015 编译obs studio 遇到的几个错误
1. >D:\project\vs\obs\ObsProject\obs-studio\plugins\win-wasapi\win-wasapi.cpp(245): error C2065: ...
- Spark(五十三):Spark RPC初尝试使用
基本用法主要掌握一点就行: master slave模式运用:driver 就是master,executor就是slave. 如果executor要想和driver交互必须拿到driver的Endp ...
- 布局优化: <include />、<merge /> 、<ViewStub /> 标签的使用
在布局优化中,Androi的官方提到了这三种布局<include />.<merge />.<ViewStub />,并介绍了这三种布局各有的优势,下面也是简单说一 ...
- JQuery selector form input
var inputPhoneInFormActivity = $("form#formtab input[name='phone']"); if(inputPhoneInFormA ...