写在前面

源码这里。本文分析的是通过java -jarjar包方式启动,关于直接运行main函数启动过程可以参考这里。

1:创建helloworld程序

1.1:创建maven项目

file->new->project,然后选择左侧的maven,选择jdk的版本为8,直接next创建。

1.2:配置依赖

<packaging>jar</packaging><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.10.RELEASE</version>
</parent>
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
</dependencies><!-- 这个插件,可以将应用打包成一个可执行的jar包;-->
<build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>2.2.5.RELEASE</version></plugin></plugins></build>

我们最终要探究的可执行jar包就是通过插件spring-boot-maven-plugin打出来的。

1.3:创建启动类

@SpringBootApplication
public class HelloWorldMainApplication {public static void main(String[] args) {SpringApplication.run(HelloWorldMainApplication.class, args);}
}

1.4:创建测试controller

@Controller
public class HelloController {@ResponseBody@RequestMapping("/hello")public String hello(){return "Hello World!";}
}

2:生成并运行jar

2.1:生成jar

按照下图生成:

2.2:运行jar

$ java -jar springboot-helloworld-1.0-SNAPSHOT.jar.   ____          _            __ _ _/\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \\\/  ___)| |_)| | | | | || (_| |  ) ) ) )'  |____| .__|_| |_|_| |_\__, | / / / /=========|_|==============|___/=/_/_/_/:: Spring Boot ::       (v2.2.10.RELEASE)2020-12-02 13:50:55.004  INFO 25496 --- [           main] dongshi.daddy.HelloWorldMainApplication  : Starting HelloWorldMainApplication v1.0-SNAPSHOT on jhp with PID 25496 (C:\Users\Administrator\Desktop\temp\springboot-helloworld-1.0-SNAPSHOT.jar started by Administrator in C:\Users\Administrator\Desktop\temp)
2020-12-02 13:50:55.006  INFO 25496 --- [           main] dongshi.daddy.HelloWorldMainApplication  : No active profile set, falling back to default profiles: default
2020-12-02 13:50:55.744  INFO 25496 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8083 (http)
2020-12-02 13:50:55.752  INFO 25496 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2020-12-02 13:50:55.753  INFO 25496 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.38]
2020-12-02 13:50:55.796  INFO 25496 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2020-12-02 13:50:55.796  INFO 25496 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 760 ms
2020-12-02 13:50:55.926  INFO 25496 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2020-12-02 13:50:56.039  INFO 25496 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8083 (http) with context path ''
2020-12-02 13:50:56.041  INFO 25496 --- [           main] dongshi.daddy.HelloWorldMainApplication  : Started HelloWorldMainApplication in 1.285 seconds (JVM running for 1.564)

2.3:访问测试

D:\program_files\Redis-x64-2.8.2402>curl http://localhost:8083/hello
Hello World!

3:jar包启动分析

3.1:入口分析

这里生成jar包的清单文件MENIFEST.MF如下:

Manifest-Version: 1.0
Implementation-Title: springboot-helloworld
Implementation-Version: 1.0-SNAPSHOT
Start-Class: dongshi.daddy.HelloWorldMainApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.2.5.RELEASE
Created-By: Maven Archiver 3.4.0
Main-Class: org.springframework.boot.loader.JarLauncher

可以看到这里Main-Classs的值是org.springframework.boot.loader.JarLauncher,这就是通过jar包方式启动程序的入口了。其顶层的抽象父类是org.springframework.boot.loader.Launcher,该抽象类定义了如何通过一个归档文件(如jar包)来启动应用程序。

3.2:主函数

源码如下:

org.springframework.boot.loader.JarLauncher
public static void main(String[] args) throws Exception {new JarLauncher().launch(args);
}

其中调用的launch(args)方法是在org.springframework.boot.loader.Launcher父类中,源码如下:

protected void launch(String[] args) throws Exception {// <1>JarFile.registerUrlProtocolHandler();// <2>ClassLoader classLoader = createClassLoader(getClassPathArchives());// <3>launch(args, getMainClass(), classLoader);
}

<1>处代码注册协议处理器,处理jar:的协议,用于最终类加载器从jar包归档文件中加载依赖的.class文件,<2>处代码创建类加载器,用于加载相关类,会使用到<1>中设置的协议处理器(在jarFileUrl.openConnection()的时候使用到)<3>处代码调用启动类,启动应用程序,启动类最终会通过反射调用应用程序的main函数启动应用程序(后面会分析到)

3.3:JarFile.registerUrlProtocolHandler

源码:

org.springframework.boot.loader.jar.JarFile#registerUrlProtocolHandler
public static void registerUrlProtocolHandler() {// <1>String handlers = System.getProperty(PROTOCOL_HANDLER, "");// <2>// <3>System.setProperty(PROTOCOL_HANDLER,("".equals(handlers) ? HANDLERS_PACKAGE : handlers + "|" + HANDLERS_PACKAGE));// <4>resetCachedUrlHandlers();
}

先简单说下java.protocol.handler.pkgs环境变量的作用,我们知道,有很多的通信协议,比如http://,jar://,file://,ftp://等等,不同的协议对应的资源都会有一个地址,在jdk中使用类java.net.URL类来表示的,而不同协议对应的资源都需要专门的处理器类来进行获取和处理,比如我们这里就是要处理jar://对应的jar包资源。我们可以自定义处理器类,定义完毕之后通过环境变量java.protocol.handler.pkgs来设置自定义处理器类所在的包路径,而下面所作的事情就是设置处理jar://协议资源的处理器类所在的包,自定义处理器也比较简单,只需要继承java.net.URLStreamHandler抽象类就可以了,springboot的处理器其实就是可执行jar包中的org.springframework.boot.loader.jar.Handler。如下图:

<1>处代码是获取名称为java.protocol.handler.pkgs的环境变量,其中第二个参数为默认值,<2>处代码是如果没有名称为java.protocol.handler.pkgs的属性则设置其值为org.springframework.boot.loader,该值正式上图的资源处理器所在的包,<3>设置协议处理器,多个通过|分割设置。<4>处代码重置协议处理器,防止存在的jar:协议处理器覆盖自定义的协议处理器。
其源码如下:

private static void resetCachedUrlHandlers() {try {URL.setURLStreamHandlerFactory(null);}catch (Error ex) {// Ignore}
}

接下来我们继续看创建类加载器。

3.4:createClassLoader(getClassPathArchives())

所在的代码为:

org.springframework.boot.loader.Launcher#launch(java.lang.String[])
protected void launch(String[] args) throws Exception {...ClassLoader classLoader = createClassLoader(getClassPathArchives());...
}

源码:

protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {List<URL> urls = new ArrayList<>(archives.size());for (Archive archive : archives) {urls.add(archive.getUrl());}return createClassLoader(urls.toArray(new URL[0]));
}

我们先看方法getClassPathArchives()

3.4.1:getClassPathArchives()

该方法用于获取BOOT-INF/lib下所有的jar,以及BOOT-INF/classes下所有的应用程序.class和资源配置文件。位置在org.springframework.boot.loader.ExecutableArchiveLauncher#getClassPathArchives,源码如下:

@Override
protected List<Archive> getClassPathArchives() throws Exception {// <1>List<Archive> archives = new ArrayList<>(this.archive.getNestedArchives(this::isNestedArchive));postProcessClassPathArchives(archives);return archives;
}

<1>处代码使用的是java8的lambda表达式用法,其中代码this::isNestedArchive相当于代码:

new Archive.EntryFilter() {@Overridepublic boolean matches(Archive.Entry entry) {return isNestedArchive(entry);}
}

其中isNestedArchive(entry)调用的代码如下:

org.springframework.boot.loader.JarLauncher#isNestedArchive
@Override
protected boolean isNestedArchive(Archive.Entry entry) {// static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";// 如果是目录只过滤BOOT-INF/classes/if (entry.isDirectory()) {return entry.getName().equals(BOOT_INF_CLASSES);}// 当这里不是目录,那就是文件了// 如果是文件则必须是BOOT-INF/lib/目录下的(jar包)return entry.getName().startsWith(BOOT_INF_LIB);
}

List<Archive> archives = new ArrayList<>(this.archive.getNestedArchives(this::isNestedArchive));这行代码的整体意思就是通过filterthis::isNestedArchive过滤this.archive获取最终符合要求的集合结果。其中getNestedArchives源码如下:

org.springframework.boot.loader.archive.JarFileArchive#getNestedArchives
// 这里的参数EntryFilter filter是通过lambda表达式this::isNestedArchive定义的匿名类
@Override
public List<Archive> getNestedArchives(EntryFilter filter) throws IOException {List<Archive> nestedArchives = new ArrayList<>();// this代表的是this.archive.getNestedArchives(this::isNestedArchive)中的// this.archive// 在循环中通过filter过滤entry,这里this可以循环的原因是该对象是可迭代的for (Entry entry : this) {if (filter.matches(entry)) {nestedArchives.add(getNestedArchive(entry));}}return Collections.unmodifiableList(nestedArchives);
}

其中归档文件archive的获取是通过属性org.springframework.boot.loader.ExecutableArchiveLauncher#archive来的,那么接下来的问题就变成了这个属性到底是怎么初始化的呢?我们单起一小部分来看下这个问题。

3.4.2:this.archives的初始化

初始化是通过org.springframework.boot.loader.ExecutableArchiveLauncher的构造函数完成的,源码如下:

org.springframework.boot.loader.ExecutableArchiveLauncher#ExecutableArchiveLauncher()
public ExecutableArchiveLauncher() {try {this.archive = createArchive();}catch (Exception ex) {throw new IllegalStateException(ex);}
}

其中方法createArchive();是在父类org.springframework.boot.loader.Launcher中的,源码如下:

protected final Archive createArchive() throws Exception {/* 获取root区域开始 */ProtectionDomain protectionDomain = getClass().getProtectionDomain();CodeSource codeSource = protectionDomain.getCodeSource();URI location = (codeSource != null) ? codeSource.getLocation().toURI() : null;String path = (location != null) ? location.getSchemeSpecificPart() : null;if (path == null) {throw new IllegalStateException("Unable to determine code source archive");}File root = new File(path);/* 获取root区域结束 */if (!root.exists()) {throw new IllegalStateException("Unable to determine code source archive from " + root);}// <1>return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root));
}

我们来验证下获取root区域中最终获取的root到底是什么,首先在我们的helloworld应用程序中增加Launcher的依赖:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-loader</artifactId>
</dependency>

然后修改HelloWorldMainApplication,修改后代码如下:

@SpringBootApplication
public class HelloWorldMainApplication {public static void main(String[] args) throws URISyntaxException {ProtectionDomain protectionDomain = Launcher.class.getProtectionDomain();CodeSource codeSource = protectionDomain.getCodeSource();URI location = (codeSource != null) ? codeSource.getLocation().toURI() : null;String path = (location != null) ? location.getSchemeSpecificPart() : null;if (path == null) {throw new IllegalStateException("Unable to determine code source archive");}File root = new File(path);System.out.println("root is: ");System.out.println(root);SpringApplication.run(HelloWorldMainApplication.class, args);}
}

之后重新打jar包,然后执行:

$ java -jar springboot-helloworld-1.0-SNAPSHOT.jar
root is:
C:\Users\Administrator\Desktop\springboot-helloworld-1.0-SNAPSHOT.jar.   ____          _            __ _ _/\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \\\/  ___)| |_)| | | | | || (_| |  ) ) ) )'  |____| .__|_| |_|_| |_\__, | / / / /=========|_|==============|___/=/_/_/_/:: Spring Boot ::       (v2.2.10.RELEASE)...

可以看到输出的结果是C:\Users\Administrator\Desktop\springboot-helloworld-1.0-SNAPSHOT.jar其实就是jar包自己。那么这个Archive又是什么呢?其实就是 <1>处代码的ExplodedArchive或者是JarFileArchive。我们来进一步验证代码org.springframework.boot.loader.ExecutableArchiveLauncher#getClassPathArchives中的代码List<Archive> archives = new ArrayList<>(this.archive.getNestedArchives(this::isNestedArchive));最终获取的归档文件都有哪些,执行如下操作,将HelloWorldMainApplication修改为如下代码:

@SpringBootApplication
public class HelloWorldMainApplication {public static void main(String[] args) throws URISyntaxException, IOException {ProtectionDomain protectionDomain = Launcher.class.getProtectionDomain();CodeSource codeSource = protectionDomain.getCodeSource();URI location = (codeSource != null) ? codeSource.getLocation().toURI() : null;String path = (location != null) ? location.getSchemeSpecificPart() : null;if (path == null) {throw new IllegalStateException("Unable to determine code source archive");}File root = new File(path);System.out.println("root is: ");System.out.println(root);Archive archive = new JarFileArchive(root);//  List<Archive> archives = new ArrayList<>(this.archive.getNestedArchives(this::isNestedArchive));/*List<Archive> archives = new ArrayList<>(archive.getNestedArchives(new Archive.EntryFilter() {@Overridepublic boolean matches(Archive.Entry entry) {return HelloWorldMainApplication.isNestedArchive(entry);}}));*/List<Archive> archives= new ArrayList<>(archive.getNestedArchives(HelloWorldMainApplication::isNestedArchive));System.out.println("所有的归档信息是:");for (int i = 0; i < archives.size(); i++) {System.out.println(archives.get(i));}SpringApplication.run(HelloWorldMainApplication.class, args);}// 在可执行jar包中开发人员编写的代码编译而成的.class文件以及配置文件所在的目录// 原始对应的就是src/main/java和src/main/resources目录下的文件static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";//在可执行jar包中的依赖jar所在的目录static final String BOOT_INF_LIB = "BOOT-INF/lib/";protected static boolean isNestedArchive(Archive.Entry entry) {if (entry.isDirectory()) {return entry.getName().equals(BOOT_INF_CLASSES);}return entry.getName().startsWith(BOOT_INF_LIB);}}

同样重新生成jar包,然后执行,如下:

C:\Users\Administrator\Desktop\temp\springboot-helloworld-1.0-SNAPSHOT.jar
所有的归档信息是:
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/classes!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/spring-boot-starter-web-2.2.10.RELEASE.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/spring-boot-starter-2.2.10.RELEASE.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/spring-boot-2.2.10.RELEASE.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/spring-boot-autoconfigure-2.2.10.RELEASE.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/spring-boot-starter-logging-2.2.10.RELEASE.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/logback-classic-1.2.3.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/logback-core-1.2.3.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/slf4j-api-1.7.30.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/log4j-to-slf4j-2.12.1.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/log4j-api-2.12.1.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/jul-to-slf4j-1.7.30.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/jakarta.annotation-api-1.3.5.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/spring-core-5.2.9.RELEASE.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/spring-jcl-5.2.9.RELEASE.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/snakeyaml-1.25.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/spring-boot-starter-json-2.2.10.RELEASE.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/jackson-databind-2.10.5.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/jackson-annotations-2.10.5.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/jackson-core-2.10.5.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/jackson-datatype-jdk8-2.10.5.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/jackson-datatype-jsr310-2.10.5.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/jackson-module-parameter-names-2.10.5.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/spring-boot-starter-tomcat-2.2.10.RELEASE.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/tomcat-embed-core-9.0.38.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/tomcat-embed-el-9.0.38.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/tomcat-embed-websocket-9.0.38.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/spring-boot-starter-validation-2.2.10.RELEASE.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/jakarta.validation-api-2.0.2.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/hibernate-validator-6.0.20.Final.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/jboss-logging-3.4.1.Final.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/classmate-1.5.1.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/spring-web-5.2.9.RELEASE.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/spring-beans-5.2.9.RELEASE.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/spring-webmvc-5.2.9.RELEASE.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/spring-aop-5.2.9.RELEASE.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/spring-context-5.2.9.RELEASE.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/spring-expression-5.2.9.RELEASE.jar!/
jar:file:/C:/Users/Administrator/Desktop/temp/springboot-helloworld-1.0-SNAPSHOT.jar!/BOOT-INF/lib/spring-boot-loader-2.2.10.RELEASE.jar!/.   ____          _            __ _ _/\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \\\/  ___)| |_)| | | | | || (_| |  ) ) ) )'  |____| .__|_| |_|_| |_\__, | / / / /=========|_|==============|___/=/_/_/_/:: Spring Boot ::       (v2.2.10.RELEASE)...

从结果可以看到BOOT-INF/classes下的应用程序类和资源文件被封装为一个ArchiveBOOT-INF/lib下的每一个jar文件被封装为一个Archive。现在我们已经将所有的归档文件列表全部获取到了,接下就是本小结的主题,创建自定义类加器了。

3.4.3:createClassLoader

源码如下:

protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {// <1>List<URL> urls = new ArrayList<>(archives.size());// <2>for (Archive archive : archives) {urls.add(archive.getUrl());}// <3>return createClassLoader(urls.toArray(new URL[0]));
}

<1><2>处代码是将归档文件资源文件地址URLjar:协议的地址存储到list集合中,<3>使用URL集合来创建自定义的类加载器。其中createClassLoader源码如下:

protected ClassLoader createClassLoader(URL[] urls) throws Exception {return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
}

3.5:launch(args, getMainClass(), classLoader)

被调用位置:

org.springframework.boot.loader.Launcher#launch(java.lang.String[])
protected void launch(String[] args) throws Exception {...launch(args, getMainClass(), classLoader);
}

先来看下getMainClass(),源码如下:

org.springframework.boot.loader.ExecutableArchiveLauncher#getMainClass
@Override
protected String getMainClass() throws Exception {Manifest manifest = this.archive.getManifest();String mainClass = null;if (manifest != null) {mainClass = manifest.getMainAttributes().getValue("Start-Class");}if (mainClass == null) {throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this);}return mainClass;
}

代码比较简单就是获取jar包中的MENIFEST.MF清单文件中的Start-Class,如下:

Manifest-Version: 1.0
Start-Class: dongshi.daddy.HelloWorldMainApplication
...

此时最终的结果字符串就是dongshi.daddy.HelloWorldMainApplication.接着来看launch方法.

3.5.1:launch

位置:

org.springframework.boot.loader.Launcher#launch(java.lang.String[])
protected void launch(String[] args) throws Exception {...launch(args, getMainClass(), classLoader);
}

源码如下:

org.springframework.boot.loader.Launcher#launch(java.lang.String[], java.lang.String, java.lang.ClassLoader)
protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception {// <1>Thread.currentThread().setContextClassLoader(classLoader);// <2>createMainMethodRunner(mainClass, args, classLoader).run();
}

<1>处代码设置类加载器为自定义的类加载器。<2>处代码是创建用于启动应用程序的类org.springframework.boot.loader.MainMethodRunner,并调用其run方法启动,源码如下:

org.springframework.boot.loader.Launcher#createMainMethodRunner
protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) {return new MainMethodRunner(mainClass, args);
}

继续看构造函数:

public MainMethodRunner(String mainClass, String[] args) {// 赋值启动主类名称到全局变量this.mainClassName = mainClass;// 启动参数this.args = (args != null) ? args.clone() : null;
}

启动方法源码如下:

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 });
}

直接通过自定义的类加载器加载启动类dongshi.daddy.HelloWorldMainApplication,并通过反射调用main方法,最终启动。

最后:都让开,我要喝瑞幸

通过helloworld程序分析springboot的jar包启动过程相关推荐

  1. springboot项目jar包启动脚本

    当工具用习惯了.也就懒的去关心底层的东西了.项目部署的时候,用习惯了jenkins也就不关心运维写的脚本了.但是当工具出问题,那就要从最基础的脚本来部署项目.脚本我也不太懂,在这里只是与大家一起分享启 ...

  2. jar构建docker镜像_dockerfile构建docker镜像详细说明,主要是springboot的jar包构建镜像样例...

    dockerfile构建docker镜像详细说明,主要是springboot的jar包构建镜像样例 1.镜像构建命令:docker build 图解 启动命令:(注意最后面有一个点,不要忘记) doc ...

  3. java原生和SpringBoot读取jar包中MANIFEST.MF的方式

    我们经常看到java的一些jar包META-INF目录下包含一个MANIFEST.MF文件,里面包含一些版本信息,标题,实现组织,很多第三方的jar包还会自定义一个属性. 本文讲解如何读取jar包中M ...

  4. 【SpringBoot】32、SpringBoot项目Jar包如何瘦身部署

    一.背景 SpringBoot 为我们快速开发提供了很好的架子,使得我们只需要少量配置就能开始我们的开发工作,但是当我们需要打包上传部署时,却是很神伤的一个问题,因为打出来的 Jar 包少则十几兆,多 ...

  5. 关于SpringBoot项目通过jar包启动之后无法读取项目根路径静态资源

    前言:这个是昨天晚上在部署一个项目的时候发现的,在此记录一下 关于SpringBoot项目通过jar包启动之后无法读取项目根路径静态资源 问题描述 在部署了一个项目之后,打开项目页面进行测试,发现有一 ...

  6. 解决springboot读取jar包中文件的问题

    解决springboot读取jar包中文件的问题 参考文章: (1)解决springboot读取jar包中文件的问题 (2)https://www.cnblogs.com/songxiaotong/p ...

  7. Dockerfile文件创建centos:7,配置JDK8的环境变量,与运行springboot的jar包,的镜像

    DockerFile文件创建centos:7 配置JDK8的环境变量 与运行springboot的jar包 准备文件 一:官网下载Linux的JDK包 https://www.oracle.com/j ...

  8. [maven] springboot将jar包打包到指定目录

    大家好,我是烤鸭: 今天分享一下springboot将jar包打包到指定目录下. 由于之前上线都是一个打包到一个jar,由于服务多了,1个包100多M,哪怕是小版本上线都需要重新上传jar包. 1.目 ...

  9. SpringBoot项目运行jar包启动

    本文来说下SpringBoot项目运行jar包启动 文章目录 概述 概述

最新文章

  1. 基于.Net Core开发现代化Web应用程序系列课程和文章
  2. java连接Excel数据库读取,写入,操纵Excel表格
  3. 百度李彦宏:无人驾驶何时商用
  4. CentOS-64位安装mysql5.7
  5. DBus glib 各数据类型接收与发送详解—C语言(3)
  6. 在MonoTouch中正确而简单的使用 Sqlite 数据库
  7. hdu3527spy(STL,map)
  8. java 协议这个概念_java网络协议概念是什么?
  9. python二叉树最大深度的计算_Python学习笔记24(二叉树遍历、最大深度、最大宽度)...
  10. kibana如何使用linux命令,Kibana 用户指南(安装Kibana)
  11. FAT32文件系统结构
  12. 转:TED高赞演讲:我们的认知,正在被这3种偏见毁掉
  13. 制作京东登陆页面 HTML+CSS
  14. 成都待慕电商:抖音极速版商品卡免佣扶持政策规则
  15. 第七讲:专注创造现实 第八讲:专注感激 第九讲:感激改变
  16. 史上最全私募基金的投资模式和策略总结
  17. linux双系统如何选择顺序,Ubuntu和Windows双系统选择开机顺序
  18. 大数据的5大关键技术点
  19. 【记录】基于uni-app开发的微信小程序商城项目
  20. 宝塔解压文件,通过SSH命令解压缩.tar.gz、.gz、.zip文件的方法

热门文章

  1. 7-7 六度空间 (25 分)
  2. 【云函数】 利用云函数SCF完成每日一封邮件的发送
  3. [C语言]用指针保存小于或等于lim的所有素数
  4. Data Guard 和 GoldenGate的区别
  5. 暑假N天乐【比赛篇】 —— 2019牛客暑期多校训练营(第四场)
  6. 深度学习论文: YOLOv6 v3.0: A Full-Scale Reloading及其PyTorch实现
  7. 基于预训练模型的多标签专利分类研究
  8. 中西医结合,缓解肝癌晚期疼痛
  9. 中小型技术团队的岗位与主要职责
  10. Sallen-Key滤波器的详细介绍