为什么会可以直接运行

需要classloader
java 没有提供标准加载jar文件中的jar
Main-class:自定义的类加载器,加载jar包中jar

Java -jar会后会做什么?

java -jar会去找jar中的manifest文件,在那里面找到真正的启动类;

jar包的打包插件,和打包方法

maven-plugin插件
在spring-boot-tools中,
有五个goal

  1. repackage
  2. run
  3. start
  4. stop
  5. build-info
  1. <build>
  2. <plugins>
  3. <plugin>
  4. <groupId>org.springframework.boot</groupId>
  5. <artifactId>spring-boot-maven-plugin</artifactId>
  6. </plugin>
  7. </plugins>
  8. </build>

生成MANIFEST.MF 把依赖的jar都打包进去了

执行maven clean package 后生成两个文件

-SNAPSHOT.jar -SNAPSHOT.jar.original

打包调用的是:RepackageMojo的execute,依赖repackage方法

  1. private void repackage() throws MojoExecutionException {
  2. // maven生成的jar,最终的命名将加上.original后缀
  3. Artifact source = getSourceArtifact();
  4. // 最终为可执行jar,即fat jar
  5. File target = getTargetFile();
  6. // 获取重新打包器,将maven生成的jar重新打包成可执行jar
  7. Repackager repackager = getRepackager(source.getFile());
  8. // 查找并过滤项目运行时依赖的jar
  9. Set<Artifact> artifacts = filterDependencies(this.project.getArtifacts(),
  10. getFilters(getAdditionalFilters()));
  11. // 将artifacts转换成libraries
  12. Libraries libraries = new ArtifactsLibraries(artifacts, this.requiresUnpack,
  13. getLog());
  14. try {
  15. // 获得Spring Boot启动脚本
  16. LaunchScript launchScript = getLaunchScript();
  17. // 执行重新打包,生成fat jar
  18. repackager.repackage(target, libraries, launchScript);
  19. }catch (IOException ex) {
  20. throw new MojoExecutionException(ex.getMessage(), ex);
  21. }
  22. // 将maven生成的jar更新成.original文件
  23. updateArtifact(source, target, repackager.getBackupFile());
  24. }

jar包的目录结构

spring-boot-learn-0.0.1-SNAPSHOT ├── META-INF │ └── MANIFEST.MF ├── BOOT-INF │ ├── classes │ │ └── 应用程序类 │ └── lib │ └── 第三方依赖jar └── org └── springframework └── boot └── loader └── springboot启动程序

MEAT-INF内容

记录jar包信息,程序入口

Manifest-Version: 1.0 Implementation-Title: spring-learn Implementation-Version: 0.0.1-SNAPSHOT Start-Class: com.tulingxueyuan.Application Spring-Boot-Classes: BOOT-INF/classes/ Spring-Boot-Lib: BOOT-INF/lib/ Build-Jdk-Spec: 1.8 Spring-Boot-Version: 2.1.5.RELEASE Created-By: Maven Archiver 3.4.0 Main-Class: org.springframework.boot.loader.JarLauncher

Archive概念

归档文件:tar/zip格式 jar也是zip格式 一个archive可以是一个jar,也可以是一个文件目录。可以抽象为统一的逻辑层

  1. public interface Archive extends Iterable<Archive.Entry> {
  2. // 获取该归档的url
  3. URL getUrl() throws MalformedURLException;
  4. // 获取jar!/META-INF/MANIFEST.MF或[ArchiveDir]/META-INF/MANIFEST.MF
  5. Manifest getManifest() throws IOException;
  6. // 获取jar!/BOOT-INF/lib/*.jar或[ArchiveDir]/BOOT-INF/lib/*.jar
  7. List<Archive> getNestedArchives(EntryFilter filter) throws IOException;
  8. }

org.springframework.boot.loader.archive.Archive ——-org.springframework.boot.loader.archive.ExplodedArchive 用来在文件夹目录下寻找资源 ———org.springframework.boot.loader.archive.JarFileArchive 用来在jar包环境下寻找资源,springboot打包的fatjar

jarFile:对jar包的封装,会解析内部结构,获取jar包中的文件,文件夹,加载封装到Entry,

jarFileArchive内部依赖jar对应的URL
其实就是对应的jar的路径,解析





JarLauncher

jarLauncher类集成结构
加载jar,类的
加载内/boot-inf/lib下的jar 已经class/下的class

主要做的事情

  1. 构建资源目标,获取资源URL,构建一个classLoader
  2. 加载MANIFEST.MF文件的start-class资源,执行main方法 ```properties public abstract class ExecutableArchiveLauncher extends Launcher { private final Archive archive; public ExecutableArchiveLauncher() {
    1. try {
    2. // 找到自己所在的jar,并创建Archive
    3. this.archive = createArchive();
    4. }
    5. catch (Exception ex) {
    6. throw new IllegalStateException(ex);
    7. }
    } }

public abstract class Launcher { 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)); } }

  1. <a name="Kg1PR"></a>
  2. #### URLStreamHandler:
  3. 描述资源只用的URL<br />比如,jar,file http 都算是一种资源<br />三个方法
  4. 1. URL.setURLStreamHandlerFactory设置。该属性是一个静态属性,且只能被设置一次
  5. 1. URLStreamHandler的子类<br />子类名称规范<br />必须是Handler比如: xx.http.Handler;
  6. <a name="k42dk"></a>
  7. #### SpringBoot 的jar启动的流程总结
  8. 1. 打包后生成一个fat jar ;包含所有依赖jar
  9. 1. jarLanucher 加载/lib下的jar,启动main函数
  10. <a name="I5Rmi"></a>
  11. #### 资源定义URL
  12. launchedURLClassLoader,<br />jdk如果知道读取的资源内容
  13. - LaunchedURLClassLoader.loadClass
  14. - URL.getContent()
  15. - URL.openConnection()
  16. - Handler.openConnection(URL)
  17. <a name="OsGSm"></a>
  18. #### IDE目录启动Spring boot项目
  19. 因为依赖的jar放在了classPath中了,就可以加载的到
  20. **总结 **<br />JarLauncher通过加载BOOT-INF/classes目录及BOOT-INF/lib目录下jar文件,实现了fat jar的启动。<br />SpringBoot通过扩展JarFile、JarURLConnection及URLStreamHandler,实现了jar in jar中资源的加载。<br />SpringBoot通过扩展URLClassLoader–LauncherURLClassLoader,实现了jar in jar中class文件的加载。<br />WarLauncher通过加载WEB-INF/classes目录及WEB-INF/lib和WEB-INF/lib-provided目录下的jar文件,实现了war文件的直接启动及web容器中的启动。
  21. <a name="Vi8h4"></a>
  22. # Springboot如何启动Spring容器
  23. <a name="mEssI"></a>
  24. #### 通过内置Tomcat启动的方式
  25. 1: ![image.png](https://cdn.nlark.com/yuque/0/2022/png/1275320/1656684806713-fb24c8d2-deb1-43f4-bffd-2f9b9a659c86.png#clientId=u0f66233e-fc00-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=89&id=u36b6471c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=111&originWidth=308&originalType=binary&ratio=1&rotation=0&showTitle=false&size=6865&status=done&style=none&taskId=u82c9feac-a16b-4724-bbbf-6ea0daf7425&title=&width=246.4)<br />2:![image.png](https://cdn.nlark.com/yuque/0/2022/png/1275320/1656684825478-ccdf2108-e2cd-4255-a02a-fe9fb04b4009.png#clientId=u0f66233e-fc00-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=162&id=u3289ba4f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=202&originWidth=817&originalType=binary&ratio=1&rotation=0&showTitle=false&size=24407&status=done&style=none&taskId=u7aa38e65-98bd-43ca-b2c3-d14f91dc25e&title=&width=653.6)
  26. 3: spring.run方法<br />1:监听器运行 <br />ApplicationStartingEvent <br />ApplicationEnvironmentPreparedEvent
  27. 2:读环境配置信息<br />3:实例化上下文<br />context = createApplicationContext();
  28. - new SpringApplication(primarySources)
  29. 4:设置上下文<br />5:refresh方法<br />6:发去start事件<br />7:发布ready时间,”
  30. ApplicationContextInitializer :扩展点可以实现这个扩展自己的需求<br />ApplicationListener 可以设计解耦
  31. > 1.ApplicationStartingEvent在运行开始时发送,但在进行任何处理之前(侦听器和初始化程序的注册除外)发送。 、
  32. > 2.在创建上下文之前,将发送ApplicationEnvironmentPreparedEvent。
  33. > 3.准备ApplicationContext并调用ApplicationContextInitializers之后,将发送ApplicationContextInitializedEvent。 4.读取完配置类后发送ApplicationPreparedEvent。 5.在刷新上下文之后但在调用任何应用程序和命令行运行程序之前,将发送ApplicationStartedEvent。 6.紧随其后发送带有LivenessState.CORRECT的AvailabilityChangeEvent,以指示该应用程序被视为处于活动状态。 7.在调用任何应用程序和命令行运行程序之后,将发送ApplicationReadyEvent。 8.紧随其后发送ReadabilityState.ACCEPTING_TRAFFIC的AvailabilityChangeEvent,以指示应用程序已准备就绪,可以处理请求。 如果启动时发生异常,则发送ApplicationFailedEvent。
  34. <a name="mCrZy"></a>
  35. ##### run 启动
  36. ```java
  37. public ConfigurableApplicationContext run(String... args) {
  38. // 用来记录当前springboot启动耗时
  39. StopWatch stopWatch = new StopWatch();
  40. // 就是记录了启动开始时间
  41. stopWatch.start();
  42. // 它是任何spring上下文的接口, 所以可以接收任何ApplicationContext实现
  43. ConfigurableApplicationContext context = null;
  44. Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
  45. // 开启了Headless模式:
  46. configureHeadlessProperty();
  47. // 去spring.factroies中读取了SpringApplicationRunListener 的组件, 就是用来发布事件或者运行监听器
  48. SpringApplicationRunListeners listeners = getRunListeners(args);
  49. // 发布1.ApplicationStartingEvent事件,在运行开始时发送
  50. listeners.starting();
  51. try {
  52. // 根据命令行参数 实例化一个ApplicationArguments
  53. ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
  54. // 预初始化环境: 读取环境变量,读取配置文件信息(基于监听器)
  55. ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
  56. // 忽略beaninfo的bean
  57. configureIgnoreBeanInfo(environment);
  58. // 打印Banner 横幅
  59. Banner printedBanner = printBanner(environment);
  60. // 根据webApplicationType创建Spring上下文
  61. context = createApplicationContext();
  62. exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
  63. new Class[] { ConfigurableApplicationContext.class }, context);
  64. //预初始化spring上下文
  65. prepareContext(context, environment, listeners, applicationArguments, printedBanner);
  66. // 加载spring ioc 容器 **相当重要 由于是使用AnnotationConfigServletWebServerApplicationContext 启动的spring容器所以springboot对它做了扩展:
  67. // 加载自动配置类:invokeBeanFactoryPostProcessors , 创建servlet容器onRefresh
  68. refreshContext(context);
  69. afterRefresh(context, applicationArguments);
  70. stopWatch.stop();
  71. if (this.logStartupInfo) {
  72. new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
  73. }
  74. listeners.started(context);
  75. callRunners(context, applicationArguments);
  76. }
  77. catch (Throwable ex) {
  78. handleRunFailure(context, ex, exceptionReporters, listeners);
  79. throw new IllegalStateException(ex);
  80. }
  81. try {
  82. listeners.running(context);
  83. }
  84. catch (Throwable ex) {
  85. handleRunFailure(context, ex, exceptionReporters, null);
  86. throw new IllegalStateException(ex);
  87. }
  88. return context;
  89. }
  • prepareEnvironment
    1. private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
    2. ApplicationArguments applicationArguments) {
    3. // 根据webApplicationType 创建Environment 创建就会读取: java环境变量和系统环境变量
    4. ConfigurableEnvironment environment = getOrCreateEnvironment();
    5. // 将命令行参数读取环境变量中
    6. configureEnvironment(environment, applicationArguments.getSourceArgs());
    7. // 将@PropertieSource的配置信息 放在第一位, 因为读取配置文件@PropertieSource优先级是最低的
    8. ConfigurationPropertySources.attach(environment);
    9. // 发布了ApplicationEnvironmentPreparedEvent 的监听器 读取了全局配置文件
    10. listeners.environmentPrepared(environment);
    11. // 将所有spring.main 开头的配置信息绑定SpringApplication
    12. bindToSpringApplication(environment);
    13. if (!this.isCustomEnvironment) {
    14. environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
    15. deduceEnvironmentClass());
    16. }
    17. //更新PropertySources
    18. ConfigurationPropertySources.attach(environment);
    19. return environment;
    20. }

    外置tomcat启动

    启动原理

    tomcat—-> web.xml—filter servlet listener 3.0+

tomcat启动的时候肯定调用了SpringBootServletInitializer的SpringApplicationBuilder , 就会启动springboot

servlet3.0 规范官方文档: 8.2.4

SPI

service provider interface 服务提供者接口,服务发现机制
通过MATE-inf/services文件查找, 加载自定义类
核心类:
javax.servlet.ServletContainerInitializer
启动时selvet去找ServletContainerInitializer的实现类
创建调用 onStartUp方法

  1. @Override
  2. public void onStartup(ServletContext servletContext) throws ServletException {
  3. // Logger initialization is deferred in case an ordered
  4. // LogServletContextInitializer is being used
  5. this.logger = LogFactory.getLog(getClass());
  6. WebApplicationContext rootApplicationContext = createRootApplicationContext(servletContext);
  7. if (rootApplicationContext != null) {
  8. servletContext.addListener(new SpringBootContextLoaderListener(rootApplicationContext, servletContext));
  9. }
  10. else {
  11. this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not "
  12. + "return an application context");
  13. }
  14. }

SPI 用到的地方,包括JDBC 也用到了
未命名文件 (1).png