Spring Boot源码之旅一SpringApplication启动流程一

本文由技术专家Smart哥分享,详细讲述了SpringBoot源码的学习路径,包括多线程、设计模式、JUC源码、JVM源码等,强调理解原理的重要性,指出看源码是提升编程技能的关键环节。
摘要由CSDN通过智能技术生成

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析


阶段4、深入jdk其余源码解析


阶段5、深入jvm源码解析

 

码哥源码部分

码哥讲源码-原理源码篇【2024年最新大厂关于线程池使用的场景题】

码哥讲源码【炸雷啦!炸雷啦!黄光头他终于跑路啦!】

码哥讲源码-【jvm课程前置知识及c/c++调试环境搭建】

 

​​​​​​码哥讲源码-原理源码篇【揭秘join方法的唤醒本质上决定于jvm的底层析构函数】

码哥源码-原理源码篇【Doug Lea为什么要将成员变量赋值给局部变量后再操作?】

码哥讲源码【你水不是你的错,但是你胡说八道就是你不对了!】

 

码哥讲源码【谁再说Spring不支持多线程事务,你给我抽他!】

终结B站没人能讲清楚红黑树的历史,不服等你来踢馆!

打脸系列【020-3小时讲解MESI协议和volatile之间的关系,那些将x86下的验证结果当作最终结果的水货们请闭嘴】

前言

前面分析了spring的源码,就是为了分析Spring Boot做铺垫,Spring Boot之所以能那么强大,扩展性那么好,底层还是依赖spring,他的注解,他的处理器,他的监听器,可以有无数的扩展,废话不多说,当然还是老习惯,这次要分析Spring Boot 2.2.6.RELEASE版本的,要搞懂Spring Boot的原理,得先从他的初始化开始,然后层层深入,各个击破。其实很多人说看源码比较累,这个不可否认,又是英语,又是代码,又不知道作者想表达什么,只能边看边猜边调试,这也是个学习的过程啊,一个锻炼你学习能力的过程,一开始是很难,但是事件久了你看到别人的源码,你自己就能猜到他可能要做点什么,是怎么做的,这些是你不看源码不可能有的反应,这个就跟小时候为什么经常看小说的人写作都比不看的人要厉害,我是深有体会的。我看到有人说我们不需要看源码,只要会用就好了,那我想说,你不明白原理,你能用的好么,然后又有人会反驳,你开车不用理解车的原理吧,你照样可以开的溜,我想说是的,开车的时候我是个消费者,但是我们程序员写代码的时候是生成者,角色定位要清楚,你写的东西,你自己都不了解,出了问题找别人?4S店给修BUG么,还不是自己得来,你不看源码,你都不知道内部机制是怎么样,你就用上了,关键你肯定用不好啊。Spring Boot够简单了吧,跟开车一样,基本不需要什么经验吧,但是我可以说稍微报个错,你根本不知道要怎么办,因为不了解原理,然后你可能就百度啊,猜啊,这不浪费时间么,源码里面都写的很明明白白的,说句不好听的,3年经验,可能就是百度,CRUD,写业务逻辑了3年,表面上好像自己踩过很多坑,解决了很多问题,其实很多可能就是源码里,你不熟悉,导致你用不好,问题就多,其实你花一个月看源码,里面都有你要的答案和可能暴露的问题。

当然我只是这么说说,如果你觉得源码不重要,也没关系,你能用的好也行,但是这个貌似不符合客观规律,或许我的认知有局限吧,但是我还是认为源码很重要,或许你不能成为框架的设计者,但是你可以通过源码去跟他们交流,去体会,去学习,终有一天也可以有机会封神,我想大神门也不可能一开始脑子里什么设计模式,架构全都在了,就写出框架了,他们也是模仿,尝试,修改,创新,成神的吧。从投资的角度讲,我觉得看源码属于长线投资,比如spring源码,你搞懂70%,基本其他的源码问题都不大了,我就是在netty的源码上看spring的,虽然spring相对复杂,但是很多东西还是通的,其实你会发现netty的处理器,就是springaop思想的一种映射。spring的反应式的web框架我没看过,但是我猜应该跟nettyIO模型应该有很大相似之处,异步事件通知,IO多路复用。看过netty你会觉得他把性能压榨到爆了要,连一个轮询算法也要用位操作优化,里面能用位操作的基本都用上了,位图的一系列位操作更是经典,jemalloc改进版的内存分配算法提升了多处理器的性能,最大程度的避免了线程之间的竞争,还有各种池化缓存,改进版ThredLocalselectKeys等等,基本都是为了提高性能,内存的使用优化也非常棒。而spring则在另一个可扩展的维度上登峰造极了,你可以任意扩展,甚至可以把它内部的处理器全替换了,自己来实现解析,来掌管bean的生命周期。Spring Boot又更加进了一步,使用更加简单,约定大于配置。

好了,不瞎扯了,研究源码的目的其实是为了更好的使用,和更好的扩展,当然是为了实际项目服务,解决问题,不是为了看而看,只有真正了解了原理,你才可以用的得心应手,出了问题也不怕,万变不离其宗。

初始化基本流程

SpringApplication.run

我们从最简单的例子开始,就这些东西,我们看看SpringApplication.run到底做了什么事。


内部还有run


这次是关键:

SpringApplication构造方法


首先创建一个集合存放我们传进去的类,然后推断应用程序的类型,是SERVLET,还是REACTIVE,然后获取很多jar包下的META-INF/spring.factories中的org.springframework.context.ApplicationContextInitializer属性的值和org.springframework.context.ApplicationListener属性的值,其实他们是接口,他们的值就是其实就是实现类,也就是说要获取这些接口的实现类,来做一些初始化工作,当然里面会做一些筛选,去重。然后推断出main方法的类,他用了一种很巧妙的方式,抛出一个异常,然后再方法栈里找有main方法的那个类,具体的细节后面会说。

    	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    		this.resourceLoader = resourceLoader;
    		Assert.notNull(primarySources, "PrimarySources must not be null");
    		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    		this.webApplicationType = WebApplicationType.deduceFromClasspath();
    		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    		this.mainApplicationClass = deduceMainApplicationClass();
    	}

WebApplicationType的deduceFromClasspath推断Web应用程序类型

要对照这些类看:


这个逻辑我就不讲了,就是排他的,要么REACTIVE,要么SERVLET,要么就普通的。具体是根据Class.forName反射的,而且如果一次不行,还会进行内部类的反射,否没有的话才捕获异常,返回false。可以看到如果同时有REACTIVESERVLET的相关类,会判定是SERVLET

    static WebApplicationType deduceFromClasspath() {
    		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
    				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
    			return WebApplicationType.REACTIVE;
    		}
    		for (String className : SERVLET_INDICATOR_CLASSES) {
    			if (!ClassUtils.isPresent(className, null)) {
    				return WebApplicationType.NONE;
    			}
    		}
    		return WebApplicationType.SERVLET;
    	}

ClassUtils的isPresent是否存在类型

5.2.5的版本这个类有更新,来看下怎么判断有没有某个类型:

    	public static boolean isPresent(String className, @Nullable ClassLoader classLoader) {
    		try {
    			forName(className, classLoader);
    			return true;
    		}
    		catch (IllegalAccessError err) {
    			throw new IllegalStateException("Readability mismatch in inheritance hierarchy of class [" +
    					className + "]: " + err.getMessage(), err);
    		}
    		catch (Throwable ex) {
    			// Typically ClassNotFoundException or NoClassDefFoundError...
    			return false;//没有就返回false
    		}
    	}

ClassUtils的forName

留下了主要的代码,Class.forName了两次,一次是一般类,一次是内部类,都没有就抛异常。

    public static Class<?> forName(String name, @Nullable ClassLoader classLoader)
    			throws ClassNotFoundException, LinkageError {
    
    		...
    		ClassLoader clToUse = classLoader;
    		if (clToUse == null) {
    			clToUse = getDefaultClassLoader();
    		}
    		try {
    			return Class.forName(name, false, clToUse);
    		}
    		catch (ClassNotFoundException ex) {
    			int lastDotIndex = name.lastIndexOf(PACKAGE_SEPARATOR);
    			if (lastDotIndex != -1) {
    				String innerClassName =
    						name.substring(0, lastDotIndex) + INNER_CLASS_SEPARATOR + name.substring(lastDotIndex + 1);
    				try {
    					return Class.forName(innerClassName, false, clToUse);
    				}
    				catch (ClassNotFoundException ex2) {
    					// Swallow - let original exception get through
    				}
    			}
    			throw ex;
    		}
    	}

啰嗦的有点多,不好意思了,下次补上。

好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵,部分图片来自网络,侵删。

  • 30
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值