写在前面
源码这里。本文分析的是通过java -jar
jar包方式启动,关于直接运行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() {
@Override
public 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() {
@Override
public 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
下的应用程序类和资源文件被封装为一个Archive
,BOOT-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
,如下:
...
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方法,最终启动。