通过helloworld程序分析springboot的jar包启动过程
写在前面
源码这里。本文分析的是通过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() {@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
下的应用程序类和资源文件被封装为一个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
,如下:
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包启动过程相关推荐
- springboot项目jar包启动脚本
当工具用习惯了.也就懒的去关心底层的东西了.项目部署的时候,用习惯了jenkins也就不关心运维写的脚本了.但是当工具出问题,那就要从最基础的脚本来部署项目.脚本我也不太懂,在这里只是与大家一起分享启 ...
- jar构建docker镜像_dockerfile构建docker镜像详细说明,主要是springboot的jar包构建镜像样例...
dockerfile构建docker镜像详细说明,主要是springboot的jar包构建镜像样例 1.镜像构建命令:docker build 图解 启动命令:(注意最后面有一个点,不要忘记) doc ...
- java原生和SpringBoot读取jar包中MANIFEST.MF的方式
我们经常看到java的一些jar包META-INF目录下包含一个MANIFEST.MF文件,里面包含一些版本信息,标题,实现组织,很多第三方的jar包还会自定义一个属性. 本文讲解如何读取jar包中M ...
- 【SpringBoot】32、SpringBoot项目Jar包如何瘦身部署
一.背景 SpringBoot 为我们快速开发提供了很好的架子,使得我们只需要少量配置就能开始我们的开发工作,但是当我们需要打包上传部署时,却是很神伤的一个问题,因为打出来的 Jar 包少则十几兆,多 ...
- 关于SpringBoot项目通过jar包启动之后无法读取项目根路径静态资源
前言:这个是昨天晚上在部署一个项目的时候发现的,在此记录一下 关于SpringBoot项目通过jar包启动之后无法读取项目根路径静态资源 问题描述 在部署了一个项目之后,打开项目页面进行测试,发现有一 ...
- 解决springboot读取jar包中文件的问题
解决springboot读取jar包中文件的问题 参考文章: (1)解决springboot读取jar包中文件的问题 (2)https://www.cnblogs.com/songxiaotong/p ...
- Dockerfile文件创建centos:7,配置JDK8的环境变量,与运行springboot的jar包,的镜像
DockerFile文件创建centos:7 配置JDK8的环境变量 与运行springboot的jar包 准备文件 一:官网下载Linux的JDK包 https://www.oracle.com/j ...
- [maven] springboot将jar包打包到指定目录
大家好,我是烤鸭: 今天分享一下springboot将jar包打包到指定目录下. 由于之前上线都是一个打包到一个jar,由于服务多了,1个包100多M,哪怕是小版本上线都需要重新上传jar包. 1.目 ...
- SpringBoot项目运行jar包启动
本文来说下SpringBoot项目运行jar包启动 文章目录 概述 概述
最新文章
- 基于.Net Core开发现代化Web应用程序系列课程和文章
- java连接Excel数据库读取,写入,操纵Excel表格
- 百度李彦宏:无人驾驶何时商用
- CentOS-64位安装mysql5.7
- DBus glib 各数据类型接收与发送详解—C语言(3)
- 在MonoTouch中正确而简单的使用 Sqlite 数据库
- hdu3527spy(STL,map)
- java 协议这个概念_java网络协议概念是什么?
- python二叉树最大深度的计算_Python学习笔记24(二叉树遍历、最大深度、最大宽度)...
- kibana如何使用linux命令,Kibana 用户指南(安装Kibana)
- FAT32文件系统结构
- 转:TED高赞演讲:我们的认知,正在被这3种偏见毁掉
- 制作京东登陆页面 HTML+CSS
- 成都待慕电商:抖音极速版商品卡免佣扶持政策规则
- 第七讲:专注创造现实 第八讲:专注感激 第九讲:感激改变
- 史上最全私募基金的投资模式和策略总结
- linux双系统如何选择顺序,Ubuntu和Windows双系统选择开机顺序
- 大数据的5大关键技术点
- 【记录】基于uni-app开发的微信小程序商城项目
- 宝塔解压文件,通过SSH命令解压缩.tar.gz、.gz、.zip文件的方法