https://blog.csdn.net/hengyunabc/article/details/50120001#comments

一,spring boot quick start

在spring boot里,很吸引人的一个特性是可以直接把应用打包成为一个jar/war,然后这个jar/war是可以直接启动的,不需要另外配置一个Web Server。

如果之前没有使用过spring boot可以通过下面的demo来感受下。 
下面以这个工程为例,演示如何启动Spring boot项目:

git clone git@github.com:hengyunabc/spring-boot-demo.git
mvn spring-boot-demo
java -jar target/demo-0.0.-SNAPSHOT.jar

如果使用的IDE是spring sts或者idea,可以通过向导来创建spring boot项目。

也可以参考官方教程: 
http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#getting-started-first-application

二,对spring boot的两个疑问

刚开始接触spring boot时,通常会有这些疑问

  • spring boot如何启动的?
  • spring boot embed tomcat是如何工作的? 静态文件,jsp,网页模板这些是如何加载到的?

下面来分析spring boot是如何做到的。

打包为单个jar时,spring boot的启动方式

maven打包之后,会生成两个jar文件:

demo-0.0.-SNAPSHOT.jar
demo-0.0.-SNAPSHOT.jar.original

其中demo-0.0.1-SNAPSHOT.jar.original是默认的maven-jar-plugin生成的包。

demo-0.0.1-SNAPSHOT.jar是spring boot maven插件生成的jar包,里面包含了应用的依赖,以及spring boot相关的类。下面称之为fat jar。

2.1  先来查看spring boot打好的包的目录结构(不重要的省略掉):

 ├── META-INF
│ ├── MANIFEST.MF
├── application.properties
├── com
│ └── example
│ └── SpringBootDemoApplication.class
├── lib
│ ├── aopalliance-1.0.jar
│ ├── spring-beans-4.2..RELEASE.jar
│ ├── ...
└── org
└── springframework
└── boot
└── loader
├── ExecutableArchiveLauncher.class
├── JarLauncher.class
├── JavaAgentDetector.class
├── LaunchedURLClassLoader.class
├── Launcher.class
├── MainMethodRunner.class
├── ...

2.1.1 MANIFEST.MF

 Manifest-Version: 1.0
Start-Class: com.example.SpringBootDemoApplication
Implementation-Vendor-Id: com.example
Spring-Boot-Version: 1.3..RELEASE
Created-By: Apache Maven 3.3.
Build-Jdk: 1.8.0_60
Implementation-Vendor: Pivotal Software, Inc.
Main-Class: org.springframework.boot.loader.JarLauncher

可以看到有Main-Class是org.springframework.boot.loader.JarLauncher ,这个是jar启动的Main函数。

还有一个Start-Class是com.example.SpringBootDemoApplication,这个是我们应用自己的Main函数。

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

2.1.2 com/example 目录

这下面放的是应用的.class文件。

2.1.3 lib目录

这里存放的是应用的Maven依赖的jar包文件。 
比如spring-beans,spring-mvc等jar。

2.1.4 org/springframework/boot/loader 目录

这下面存放的是Spring boot loader的.class文件。

2.2 Archive的概念

  • archive即归档文件,这个概念在linux下比较常见
  • 通常就是一个tar/zip格式的压缩包
  • jar是zip格式

在spring boot里,抽象出了Archive的概念。一个archive可以是一个jar(JarFileArchive),也可以是一个文件目录(ExplodedArchive)。可以理解为Spring boot抽象出来的统一访问资源的层。

上面的demo-0.0.1-SNAPSHOT.jar 是一个Archive,然后demo-0.0.1-SNAPSHOT.jar里的/lib目录下面的每一个Jar包,也是一个Archive。

public abstract class Archive {
public abstract URL getUrl();
public String getMainClass();
public abstract Collection<Entry> getEntries();
public abstract List<Archive> getNestedArchives(EntryFilter filter);

可以看到Archive有一个自己的URL,比如:

jar:file:/tmp/target/demo-0.0.-SNAPSHOT.jar!/

还有一个getNestedArchives函数,这个实际返回的是demo-0.0.1-SNAPSHOT.jar/lib下面的jar的Archive列表。它们的URL是:

jar:file:/tmp/target/demo-0.0.-SNAPSHOT.jar!/lib/aopalliance-1.0.jar
jar:file:/tmp/target/demo-0.0.-SNAPSHOT.jar!/lib/spring-beans-4.2..RELEASE.jar

2.3 JarLauncher

从MANIFEST.MF可以看到Main函数是JarLauncher,下面来分析它的工作流程。

JarLauncher类的继承结构是:

class JarLauncher extends ExecutableArchiveLauncher
class ExecutableArchiveLauncher extends Launcher

2.3.1 以demo-0.0.1-SNAPSHOT.jar创建一个Archive:

JarLauncher先找到自己所在的jar,即demo-0.0.1-SNAPSHOT.jar的路径,然后创建了一个Archive。

下面的代码展示了如何从一个类找到它的加载的位置的技巧:

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

2.3.2 获取lib/下面的jar,并创建一个LaunchedURLClassLoader

JarLauncher创建好Archive之后,通过getNestedArchives函数来获取到demo-0.0.1-SNAPSHOT.jar/lib下面的所有jar文件,并创建为List。

注意上面提到,Archive都是有自己的URL的。

获取到这些Archive的URL之后,也就获得了一个URL[]数组,用这个来构造一个自定义的ClassLoader:LaunchedURLClassLoader。

创建好ClassLoader之后,再从MANIFEST.MF里读取到Start-Class,即com.example.SpringBootDemoApplication,然后创建一个新的线程来启动应用的Main函数。

 /**
* Launch the application given the archive file and a fully configured classloader.
*/
protected void launch(String[] args, String mainClass, ClassLoader classLoader)
throws Exception {
Runnable runner = createMainMethodRunner(mainClass, args, classLoader);
Thread runnerThread = new Thread(runner);
runnerThread.setContextClassLoader(classLoader);
runnerThread.setName(Thread.currentThread().getName());
runnerThread.start();
} /**
* Create the {@code MainMethodRunner} used to launch the application.
*/
protected Runnable createMainMethodRunner(String mainClass, String[] args,
ClassLoader classLoader) throws Exception {
Class<?> runnerClass = classLoader.loadClass(RUNNER_CLASS);
Constructor<?> constructor = runnerClass.getConstructor(String.class,
String[].class);
return (Runnable) constructor.newInstance(mainClass, args);
}

2.3.3  LaunchedURLClassLoader

LaunchedURLClassLoader和普通的URLClassLoader的不同之处是,它提供了从Archive里加载.class的能力。

结合Archive提供的getEntries函数,就可以获取到Archive里的Resource。当然里面的细节还是很多的,下面再描述。

2.4 spring boot应用启动流程总结

看到这里,可以总结下Spring Boot应用的启动流程:

  1. spring boot应用打包之后,生成一个fat jar,里面包含了应用依赖的jar包,还有Spring boot loader相关的类
  2. Fat jar的启动Main函数是JarLauncher,它负责创建一个LaunchedURLClassLoader来加载/lib下面的jar,并以一个新线程启动应用的Main函数。

2.5  JarFile URL的扩展

Spring boot能做到以一个fat jar来启动,最重要的一点是它实现了jar in jar的加载方式。

JDK原始的JarFile URL的定义可以参考这里:

jar:file:/tmp/target/demo-0.0.-SNAPSHOT.jar!/

jar包里的资源的URL:

jar:file:/tmp/target/demo-0.0.-SNAPSHOT.jar!/com/example/SpringBootDemoApplication.class

可以看到对于Jar里的资源,定义以’!/’来分隔。原始的JarFile URL只支持一个’!/’。

Spring boot扩展了这个协议,让它支持多个’!/’,就可以表示jar in jar,jar in directory的资源了。

比如下面的URL表示demo-0.0.1-SNAPSHOT.jar这个jar里lib目录下面的spring-beans-4.2.3.RELEASE.jar里面的MANIFEST.MF:

jar:file:/tmp/target/demo-0.0.-SNAPSHOT.jar!/lib/spring-beans-4.2..RELEASE.jar!/META-INF/MANIFEST.MF

2.6  自定义URLStreamHandler,扩展JarFile和JarURLConnection

在构造一个URL时,可以传递一个Handler,而JDK自带有默认的Handler类,应用可以自己注册Handler来处理自定义的URL。

Spring boot通过注册了一个自定义的Handler类来处理多重jar in jar的逻辑。

这个Handler内部会用SoftReference来缓存所有打开过的JarFile。

在处理像下面这样的URL时,会循环处理’!/’分隔符,从最上层出发,先构造出demo-0.0.1-SNAPSHOT.jar这个JarFile,再构造出spring-beans-4.2.3.RELEASE.jar这个JarFile,然后再构造出指向MANIFEST.MF的JarURLConnection。

2.7 ClassLoader如何读取到Resource

对于一个ClassLoader,它需要哪些能力?

  • 查找资源
  • 读取资源

对应的API是:

public URL findResource(String name)
public InputStream getResourceAsStream(String name)

上面提到,Spring boot构造LaunchedURLClassLoader时,传递了一个URL[]数组。数组里是lib目录下面的jar的URL。

对于一个URL,JDK或者ClassLoader如何知道怎么读取到里面的内容的?

实际上流程是这样子的:

  • LaunchedURLClassLoader.loadClass
  • URL.getContent()
  • URL.openConnection()
  • Handler.openConnection(URL)

最终调用的是JarURLConnection的getInputStream()函数。

从一个URL,到最终读取到URL里的内容,整个过程是比较复杂的,总结下:

  • spring boot注册了一个Handler来处理”jar:”这种协议的URL
  • spring boot扩展了JarFile和JarURLConnection,内部处理jar in jar的情况
  • 在处理多重jar in jar的URL时,spring boot会循环处理,并缓存已经加载到的JarFile
  • 对于多重jar in jar,实际上是解压到了临时目录来处理,可以参考JarFileArchive里的代码
  • 在获取URL的InputStream时,最终获取到的是JarFile里的JarEntryData

在IDE/开放目录启动Spring boot应用

在上面只提到在一个fat jar里启动Spring boot应用的过程,下面分析IDE里Spring boot是如何启动的。

在IDE里,直接运行的Main函数是应用自己的Main函数:

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

其实在IDE里启动Spring boot应用是最简单的一种情况,因为依赖的Jar都让IDE放到classpath里了,所以Spring boot直接启动就完事了。

还有一种情况是在一个开放目录下启动Spring boot启动。所谓的开放目录就是把fat jar解压,然后直接启动应用。

java org.springframework.boot.loader.JarLauncher

这时,Spring boot会判断当前是否在一个目录里,如果是的,则构造一个ExplodedArchive(前面在jar里时是JarFileArchive),后面的启动流程类似fat jar的。

spirng boot的一些缺点:

当spring boot应用以一个fat jar方式运行时,会遇到一些问题。以下是个人看法:

  • 日志不知道放哪,默认是输出到stdout的
  • 数据目录不知道放哪, jenkinns的做法是放到 ${user.home}/.jenkins 下面
  • 相对目录API不能使用,servletContext.getRealPath(“/”) 返回的是NULL
  • spring boot应用喜欢把配置都写到代码里,有时会带来混乱。一些简单可以用xml来表达的配置可能会变得难读,而且凌乱。

总结

spring boot通过扩展了jar协议,抽象出Archive概念,和配套的JarFile,JarUrlConnection,LaunchedURLClassLoader,从而实现了上层应用无感知的all in one的开发体验。尽管Executable war并不是spring提出的概念,但spring boot让它发扬光大。

spring boot是一个惊人的项目,可以说是spring的第二春,spring-cloud-config, spring-session, metrics, remote shell等都是深爱开发者喜爱的项目、特性。几乎可以肯定设计者是有丰富的一线开发经验,深知开发人员的痛点。

转载;https://blog.csdn.net/hengyunabc/article/details/50120001#comments

Springboot 应用启动分析的更多相关文章

  1. SpringBoot的启动流程分析(1)

    通过分析我们可以找到 org.springframework.boot.SpringApplication 中如下, public static ConfigurableApplicationCont ...

  2. SpringBoot源码分析之SpringBoot的启动过程

    SpringBoot源码分析之SpringBoot的启动过程 发表于 2017-04-30   |   分类于 springboot  |   0 Comments  |   阅读次数 SpringB ...

  3. SpringBoot的启动流程分析(2)

    我们来分析SpringApplication启动流程中的run()方法,代码如下 public ConfigurableApplicationContext run(String... args) { ...

  4. Springboot 启动分析

    启动类 Spring 启动类主要是 Annotation (@SpringBootApplication) 和 main 方法体中的 SpringApplication.run. 其中注解 @Spri ...

  5. SpringBoot源码解析:tomcat启动分析

    >> spring与tomcat的启动分析:war包形式 tomcat:xml加载规范 1.contex-param: 初始化参数 2.listener-class: contextloa ...

  6. SpringBoot一站式启动流程源码分析

    一.前言 由上篇文章我们得知,SpringBoot启动时,就是有很简单的一行代码.那我们可以很清楚的看到这行代码的主角便是SpringApplication了,本文我们就来聊一聊这货,来探寻Sprin ...

  7. springboot之启动原理解析

    前言 SpringBoot为我们做的自动配置,确实方便快捷,但是对于新手来说,如果不大懂SpringBoot内部启动原理,以后难免会吃亏.所以这次博主就跟你们一起一步步揭开SpringBoot的神秘面 ...

  8. 深入理解SpringBoot之启动探究

    SpringApplication是SpringBoot的启动程序,我们通过它的run方法可以快速启动一个SpringBoot应用.可是这里面到底发生了什么?它是处于什么样的机制简化我们程序启动的?接 ...

  9. springboot之启动原理解析及源码阅读

    前言 SpringBoot为我们做的自动配置,确实方便快捷,但是对于新手来说,如果不大懂SpringBoot内部启动原理,以后难免会吃亏.所以这次博主就跟你们一起一步步揭开SpringBoot的神秘面 ...

随机推荐

  1. ioremap映射函数

    一.ioremap() 函数 Linux在io.h头文件中声明了函数ioremap(),用来将I/O内存资源的物理地址映射到核心虚地址空间(3GB-4GB)中(这里是内核空间),原型如下: 1.ior ...

  2. [SharePoint2010开发入门经典]编译部署SPS WebPart

    本章概要: 1.理解web部件,什么时候需要创建一个 2.理解标准和可视web部件的不同 3.使用VS构建部署web部件

  3. HDU 4454

    想了很久,发现其实就只需要三分枚举圆上的点,到矩形的最短很容易就可以求到了.开始时考虑要不要根据矩形相对圆的方位来划分枚举区间,后来发现一定不能这样做的. 注意题目给的是矩形的对角形,但没说哪一条对角 ...

  4. 【Android 应用开发】 ActionBar 样式具体解释 -- 样式 主题 简单介绍 Actionbar 的 icon logo 标题 菜单样式改动

    作者 : 万境绝尘 (octopus_truth@163.com) 转载请著名出处 : http://blog.csdn.net/shulianghan/article/details/3926916 ...

  5. iOS库--.a与.framework

    一.什么是库? 库是共享程序代码的方式,一般分为静态库和动态库. 二.静态库与动态库的差别? 静态库:链接时完整地拷贝至可运行文件里.被多次使用就有多份冗余拷贝. 动态库:链接时不复制.程序执行时由系 ...

  6. 3D数学读书笔记——矩阵进阶

    本系列文章由birdlove1987编写,转载请注明出处. 文章链接:http://blog.csdn.net/zhurui_idea/article/details/25242725 最终要学习矩阵 ...

  7. R语言基础-数组和列表

    数组(array) 一维数据是向量,二维数据是矩阵,数组是向量和矩阵的直接推广,是由三维或三维以上的数据构成的. 数组函数是array(),语法是:array(dadta, dim),当中data必须 ...

  8. [Android 性能优化系列]内存之基础篇--Android怎样管理内存

    大家假设喜欢我的博客,请关注一下我的微博,请点击这里(http://weibo.com/kifile),谢谢 转载请标明出处(http://blog.csdn.net/kifile),再次感谢 原文地 ...

  9. html5开发手机打电话发短信功能,html5的高级开发,html5开发大全,html手机电话短信功能具体解释

    在非常多的手机站点上,有打电话和发短信的功能,对于这些功能是怎样实现的呢.事实上不难,今天我们就用html5来实现他们. 简单的让你大开眼界.HTML5 非常easy写,但创建网页时,您常常须要反复做 ...

  10. 2019黑马JAVAEE57期基础班就业班(全套)

    黑马java57期 百度网盘 2019黑马JAVAEE57期基础班就业班(全套)百度网盘 下载 Spring全家桶解决方案 - 微服务认证解决方案(JWT) - 微服务网关解决方案(Zuul) 黑马j ...