资讯专栏INFORMATION COLUMN

Spring-Boot启动之前做了哪些事?

susheng / 776人阅读

摘要:也就是说,可以执行成功,是因为知道了的路径,说明在启动调用之前,指定了的位置。判断当前的介质,是启动,还是启动。

Spring-Boot启动之前做了哪些事? Spring Boot Jar文件探究

初始化一个Spring 应用,添加如下依赖

</>复制代码

  1. 4.0.0
  2. org.springframework.boot
  3. spring-boot-starter-parent
  4. 2.1.6.RELEASE
  5. com.fxipp.spring
  6. first-app-by-gui
  7. 0.0.1-SNAPSHOT
  8. first-app-by-gui
  9. Demo project for Spring Boot
  10. 1.8
  11. org.springframework.boot
  12. spring-boot-starter-web
  13. org.springframework.boot
  14. spring-boot-starter-test
  15. test
  16. org.springframework.boot
  17. spring-boot-maven-plugin

执行mvn package命令打包,查看jar包的目录结构

</>复制代码

  1. .
  2. ├── BOOT-INF
  3. │   ├── classes
  4. │   │   ├── application.properties
  5. │   │   └── com
  6. │   │   └── fxipp
  7. │   │   └── spring
  8. │   │   └── FirstAppByGuiApplication.class
  9. │   └── lib
  10. │   ├── classmate-1.4.0.jar
  11. │   ├── hibernate-validator-6.0.17.Final.jar
  12. │   ├── jackson-annotations-2.9.0.jar
  13. │   ├── jackson-core-2.9.9.jar
  14. │   ......
  15. ├── META-INF
  16. │   ├── MANIFEST.MF
  17. │   └── maven
  18. │   └── com.fxipp.spring
  19. │   └── first-app-by-gui
  20. │   ├── pom.properties
  21. │   └── pom.xml
  22. └── org
  23. └── springframework
  24. └── boot
  25. └── loader
  26. ├── ExecutableArchiveLauncher.class
  27. ├── JarLauncher.class
  28. ├── LaunchedURLClassLoader$UseFastConnectionExceptionsEnumeration.class
  29. ├── LaunchedURLClassLoader.class
  30. ├── ......
  31. ├── archive
  32. │   ├── Archive$Entry.class
  33. │   ├── Archive$EntryFilter.class
  34. │   ├── Archive.class
  35. │   ├── ......
  36. ├── data
  37. │   ├── RandomAccessData.class
  38. │   ├── RandomAccessDataFile$1.class
  39. │   ├──......
  40. ├── jar
  41. │   ├── AsciiBytes.class
  42. │   ├── Bytes.class
  43. │   ├── ......
  44. └── util
  45. └── SystemPropertyUtils.class
  46. 18 directories, 91 files

文件结构比较复杂,解释一下

BOOT-INF/classes: 存放应用编译后的class文件;

BOOT-INF/lib:class path目录, 存放应用依赖的jar包;

META-INF: 存放应用的元信息,如MANIFEST.MF文件;

org:存放Spring Boot自身的class文件;

Jar文件的执行器: Spring Boot Loader

我们先从MANIFEST.MF文件查看

</>复制代码

  1. Manifest-Version: 1.0
  2. Implementation-Title: first-app-by-gui
  3. Implementation-Version: 0.0.1-SNAPSHOT
  4. Start-Class: com.fxipp.spring.FirstAppByGuiApplication
  5. Spring-Boot-Classes: BOOT-INF/classes/
  6. Spring-Boot-Lib: BOOT-INF/lib/
  7. Build-Jdk-Spec: 1.8
  8. Spring-Boot-Version: 2.1.6.RELEASE
  9. Created-By: Maven Archiver 3.4.0
  10. Main-Class: org.springframework.boot.loader.JarLauncher

里面记录了应用的元信息,Spring的版本,应用的版本,Maven的版本,Main-Class等信息。不难发现,MainClass指向的是org.springframework.boot.loader.JarLauncher(以下简称JarLauncher),而不是我们自己编写的com.fxipp.spring.FirstAppByGuiApplication

JarLauncher从名字看出是一个jar的执行器,他的class文件位于org.springframework.boot.loader目录下,可见它是Spring自身的class文件。

</>复制代码

  1. JarLauncherGAV org.springframework.boot:spring-boot-loader:2.1.6.RELEASE

通常情况下,他会在spring-boot-starter-parent引入到应用中,既然main-class指向到是JarLauncher,那我们也可以直接执行java org.springframework.boot.loader.JarLauncher,也可以启动Spring项目的。

</>复制代码

  1. java org.springframework.boot.loader.JarLauncher
  2. . ____ _ __ _ _
  3. / / ___"_ __ _ _(_)_ __ __ _
  4. ( ( )\___ | "_ | "_| | "_ / _` |
  5. / ___)| |_)| | | | | || (_| | ) ) ) )
  6. " |____| .__|_| |_|_| |_\__, | / / / /
  7. =========|_|==============|___/=/_/_/_/
  8. :: Spring Boot :: (v2.1.6.RELEASE)
  9. 2019-06-19 20:30:52.202 INFO 3094 --- [ main] c.fxipp.spring.FirstAppByGuiApplication : Starting FirstAppByGuiApplication on fangxideMacBook-Pro.local with PID 3094 (/Users/fangxi/Java/workspace/default/spring-boot/first-app-by-gui/target/temp/BOOT-INF/classes started by fangxi in /Users/fangxi/Java/workspace/default/spring-boot/first-app-by-gui/target/temp)

既然可以执行,那就说明了,JarLauncher这个类才是Spring项目真正的入口。如果我们执行自己写的com.fxipp.spring.FirstAppByGuiApplication会怎么样?

</>复制代码

  1. classes java com.fxipp.spring.FirstAppByGuiApplication
  2. Exception in thread "main" java.lang.NoClassDefFoundError: org/springframework/boot/SpringApplication
  3. at com.fxipp.spring.FirstAppByGuiApplication.main(FirstAppByGuiApplication.java:10)
  4. Caused by: java.lang.ClassNotFoundException: org.springframework.boot.SpringApplication
  5. at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
  6. at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
  7. at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
  8. at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
  9. ... 1 more

启动报错,原因是找不到org.springframework.boot.SpringApplication这个类,说白了就是没有指定Class Path,Spring Boot应用的Class Path目录是BOOT-INF/lib

也就是说,JarLauncher可以执行成功,是因为Spring Boot知道了Class Path的路径,说明JarLauncher在启动调用com.fxipp.spring.FirstAppByGuiApplication之前,指定了Class Path的位置。

JarLauncher的代码如下

</>复制代码

  1. public class JarLauncher extends ExecutableArchiveLauncher {
  2. static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";
  3. static final String BOOT_INF_LIB = "BOOT-INF/lib/";
  4. public JarLauncher() {
  5. }
  6. protected JarLauncher(Archive archive) {
  7. super(archive);
  8. }
  9. @Override
  10. protected boolean isNestedArchive(Archive.Entry entry) {
  11. if (entry.isDirectory()) {
  12. return entry.getName().equals(BOOT_INF_CLASSES);
  13. }
  14. return entry.getName().startsWith(BOOT_INF_LIB);
  15. }
  16. public static void main(String[] args) throws Exception {
  17. new JarLauncher().launch(args);
  18. }
  19. }

Archive.Entry:这个类对对象,代编jar包中的资源文件。

isNestedArchive方法判断entry对象是不是位于jar包内,如果在jar内部,返回true。如果不在jar包里面,也就是我们解压了jar包,返回false。

重点看launch(String[])方法

</>复制代码

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

这个方法一共3步

扩展JAR协议

JDK默认支持file、http、jar等协议,所以JDK内部有默认的实现,位于sun.net.www.protocol包下。

JarFile.registerUrlProtocolHandler();这个方法将org.springframework.boot.loader包下对应的JAR协议实现,覆盖原有的JAR实现。

因为原有的JAR实现,ClassPath是我们自己配置环境变量的时候制定的,不是BOOT-INF/lib

创建一个classloader,用于加载JarLauncher类,因为jar包可能会被解压,解压前和解压后的的ClassLoader是不同的。

调用launch方法,将参数传递。

args是我们自己指定的参数。

getMainClass()是获取MANIFEST.MF文件里面Statr-Class属性,也就是获取我们自定义主类的Class 文件地址。

传递推出的类加载器

launch方法

</>复制代码

  1. protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception {
  2. Thread.currentThread().setContextClassLoader(classLoader);
  3. createMainMethodRunner(mainClass, args, classLoader).run();
  4. }
  5. protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) {
  6. return new MainMethodRunner(mainClass, args);
  7. }

</>复制代码

  1. public class MainMethodRunner {
  2. private final String mainClassName;
  3. private final String[] args;
  4. public MainMethodRunner(String mainClass, String[] args) {
  5. this.mainClassName = mainClass;
  6. this.args = (args != null) ? args.clone() : null;
  7. }
  8. public void run() throws Exception {
  9. Class mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName);
  10. Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
  11. mainMethod.invoke(null, new Object[] { this.args });
  12. }
  13. }

launch方法分析:

将ClassLoader放入当前线程里面的ClassLoader里面

创建MainMethodRunner对象,调用里面的run()方法。

run()方法先获取到之前设定的ClassLoader。

利用ClassLoader加载Start-Class之类的类,也就是我们自己的主类。

获取主类里面的main方法,通过反射执行。

总结

通过分析,我们可以看出,Spring Boot Loader在调用我们自己的主类之前,主要做了三件事

扩展JDK默认的支持JAR对应的协议,因为Spring Boot启动不仅仅需要JDK半身的JAR文件,还需要BOOT-INF/lib这个目录下的文件。默认实现无法将BOOT-INF/lib这个目录当作ClassPath,故需要替换实现。

判断当前的介质,是java -jar启动,还是java org.springframework.boot.loader.JarLauncher启动。以便获取对应的ClassLoader。

获取MANIFEST.MF文件中的Start-Class属性,也就是我们自定义的主类。通过第二步获取的ClassLoader加载获取到Class文件,通过反射调用main方法,启动应用。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/75442.html

相关文章

  • 从零开始实现一个简易的Java MVC框架(八)--制作Starter

    摘要:服务器相关配置启动类资源目录目录静态文件目录端口号目录目录实现内嵌服务器在上一章文章从零开始实现一个简易的框架七实现已经在文件中引入了依赖,所以这里就不用引用了。 spring-boot的Starter 一个项目总是要有一个启动的地方,当项目部署在tomcat中的时候,经常就会用tomcat的startup.sh(startup.bat)的启动脚本来启动web项目 而在spring-b...

    AprilJ 评论0 收藏0
  • Spring-Boot学习笔记

    摘要:学习笔记使用很容易创建一个独立运行运行内嵌容器准生产级别的基于框架的项目,使用你可以不用或者只需要很少的配置。异常消息如果这个错误是由异常引起的。错误发生时请求的路径。 Spring-Boot 1.5 学习笔记 使用Spring Boot很容易创建一个独立运行(运行jar,内嵌Servlet容器)、准生产级别的基于Spring框架的项目,使用Spring Boot你可以不用或者只需要很...

    curlyCheng 评论0 收藏0
  • 三.spring-boot:简述springboot启动流程

    摘要:如下页面模版的配置启动简单流程当我们运行的方法时调用静态方法首先是实例化初始化的时候主要做主要做三件事根据下是否存在判断是否要启动一个。将配置环境加入到监听器对象中。方法将等重要组件与上下文对象关联。自此的简单流程到此结束。 正文 说springboot的启动流程当然少不了springboot启动入口类 @SpringBootApplication public class Sprin...

    masturbator 评论0 收藏0
  • 使用Docker部署Spring-Boot+Vue博客系统

    摘要:先来看一下容器的文件中间一些操作省略这里用了多阶段构建容器,如果直接通过设置环境变量只会在后面一个阶段生效,但是是在第一个阶段执行的,所以环境变量不能应用到当中。 在今年年初的时候,完成了自己的个Fame博客系统的实现,当时也做了一篇博文Spring-boot+Vue = Fame 写blog的一次小结作为记录和介绍。从完成实现到现在,也断断续续的根据实际的使用情况进行更新。 只不过每...

    Eirunye 评论0 收藏0
  • Spring Boot启动过程及回调接口汇总

    摘要:创建及准备创建。目前已知关心这个事件的有要注意的是在这个阶段,里只有,是的加载工作的起点。原因是注入这些回调接口本身没有什么意义。在其构造函数内部间接的给注册了几个与相关注解的处理器。 相关代码在: https://github.com/chanjarster/spring-boot-all-callbacks 注:本文基于spring-boot 1.4.1.RELEASE, spri...

    Taonce 评论0 收藏0

发表评论

0条评论

susheng

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<