springboot启动流程源码分析

news2024/11/25 16:23:39

一、引入思考的问题

1、springboot未出现之前,我们在在spring项目中如果要使用数据源(比如我们使用druid),需要做哪些事情呢?

(1)引入druid的jar包

(2)配置数据源的参数

(2)在xml文件中配置数据源的bean,或者使用注解的方式@bean注入到spring容器中

2、当我们使用springboot项目时,需要做的事情有哪些?

(1)引入相应的druid的starter的包

(2)在yml或者properties中配置数据源参数

对比上面两个步骤,我们发现springboot中,我们并没有显示的向spring容器中注入相应的datasource的bean,但是我们为什么能够直接使用数据源呢(比如使用事务的时候)

今天咱们要学习的内容,就是解释下上面的问题,如果你还不了解上述的原理,那咱们开始吧。在学习之前如果大家了解SPI(service provider interface)那就更好了,因为这里面其实就是用到了SPI的机制,SPI引用还是非常广泛的,比如spring、dubbo中都有广泛使用

二、springboot启动加载starter

我们还是从启动类开始分析

@SpringBootApplication
public class HellobootApplication {
   

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

}

我们进入@SpingBootApplication的注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
//关注这里
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
    @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
   ....}

只需要继续看上面的注解就行,这次我们关注@EnableAutoConfiguration这个注解类,继续跟

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
//关注这里
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
   ...}

我们关注这一行注解@Import(AutoConfigurationImportSelector.class)这里需要spring的知识,如果想要了解这一行注解的原理,可以自行在网上查取(其实原理就是ConfigurationClassPostProcessor这个处理器起的作用),暂时不了解也没关系,在这里先解释一下,他会注入AutoConfigurationImportSelector的bean到spring容器,然而这个类又实现了ImportSelector,所以会调用selectImports,并且该方法返回的String[]的内容全部会注入到spring容器中。我们看下这个类的selectImports()方法

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
   
   if (!isEnabled(annotationMetadata)) {
   
      return NO_IMPORTS;
   }
   AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
         .loadMetadata(this.beanClassLoader);
    //重点看这里
   AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
         annotationMetadata);
   return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

到了这里,我们差不多已经见到了胜利的曙光了,springboot启动流程的原理还是相对比较容易的,当然前提是你需要对spring的知识有一定的了解。上述方法很简单,我们只要关注它需要向spring容器里面注入哪些bean(不要跑偏了,我们之前带着问题,就是为什么我们没有显示向spring容器中注入datasource,但是在springboot中我们能直接拿到),所以我们接着看getAutoConfigurationEntry()会给我们返回哪些string

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
      AnnotationMetadata annotationMetadata) {
   
   if (!isEnabled(annotationMetadata)) {
   
      return EMPTY_ENTRY;
   }
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
    //拿到所有候选的需要注入到spring容器的bean的全路径
   List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    //删除重复,其实就是用set转存了一下
   configurations = removeDuplicates(configurations);
    //排除一些我们不需要的...
   Set<String> exclusions = getExclusions(annotationMetadata, attributes);
   checkExcludedClasses(configurations, exclusions);
   configurations.removeAll(exclusions);
   configurations = filter(configurations, autoConfigurationMetadata);
   fireAutoConfigurationImportEvents(configurations, exclusions);
   return new AutoConfigurationEntry(configurations, exclusions);
}

上面这个方法也挺好理解的,从每一行代码的名字,我们大概就知道是做了哪些事情,为了简单梳理主要的流程,我这里只看getCandidateConfigurations(),看看springboot如何帮我们找到需要注入到spring容器中的对象

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
   
   List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
         getBeanClassLoader());
   Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
         + "are using a custom packaging, make sure that file is correct.");
   return configurations;
}

你如果留意的话,会看到一个校验的信息”…META-INF/spring.factories…”,暂时先不说,这个方法很简单,我们直接看第一行代码跟下一个方法SpringFactoriesLoader.loadFactoryNames()

String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
   
    //先从缓存里面拿,拿到了直接返回,提高性能,因为后面的扫描比较耗时
   MultiValueMap<String, String> result = cache.get(classLoader);
   if (result != null) {
   
      return result;
   }

   try {
   
       //这里就是通过类加载器去扫描所有的"META-INF/spring.factories"文件
      Enumeration<URL> urls = (classLoader != null ?
            classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
            ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
      result = new LinkedMultiValueMap<>();
       //下面就不用了,扫描了很多spring.factories,需要一个个处理
      while (urls.hasMoreElements()) {
   
         URL url = urls.nextElement();
         UrlResource resource = new UrlResource(url);
         Properties properties = PropertiesLoaderUtils.loadProperties(resource);
         for (Map.Entry<?, ?> entry : properties.entrySet()) {
   
            String factoryTypeName = ((String) entry.getKey()).trim();
            for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
   
               result.add(factoryTypeName, factoryImplementationName.trim());
            }
         }
      }
       //加入缓存
      cache.put(classLoader, result);
      return result;
   }
   catch (IOException ex) {
   
      throw new IllegalArgumentException("Unable to load factories from location [" +
            FACTORIES_RESOURCE_LOCATION + "]", ex);
   }
}

这里我将两个方法写一起了,第一个方法太简单了,我们直接看第二个吧,第二个方法的主要的核心代码已经注释了,下面总结一下:

(1)首先从判断缓存里面拿,拿不到就扫描

(2)扫描所有jar包中的META-INF/spring.factories文件,并处理里面的内容(SPI)

(3)加入缓存,方便下次扫描

也就是说,前面跟了那么多步骤,其实springboot要做的事情就是获取每个jar包中的META-INF/spring.factories文件,然后将里面的内容通过反射的方式创建对象,放入到spring容器中管理

那我们看看这些META-INF/spring.factories文件的内容

(1)springboot自带的

(2)druid-start里面的

所以通过上面的分析,我们的spring容器会自动注册”com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure”这个bean,看下这个类吧

@Configuration
@ConditionalOnClass(DruidDataSource.class)
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
@EnableConfigurationProperties({
   DruidStatProperties.class, DataSourceProperties.class})
@Import({
   DruidSpringAopConfiguration.class,
    DruidStatViewServletConfiguration.class,
    DruidWebStatFilterConfiguration.class,
    DruidFilterConfiguration.class})
public class DruidDataSourceAutoConfigure {
   

    private static final Logger LOGGER = LoggerFactory.getLogger(DruidDataSourceAutoConfigure.class);

    @Bean(initMethod = "init")
    @ConditionalOnMissingBean
    public DataSource dataSource() {
   
        LOGGER.info("Init DruidDataSource");
        return new DruidDataSourceWrapper();
    }
}

到这里就很明显了,我们看到这个类用@Configuration注释了,并且有@Bean注释了datasource,所以相当在druid里面里经帮我们自动注入了datasource(当然这里有一些springboot的注解,比如@COnditionOnclass等条件注入,大家可以自己网上查找资料或研究),另外大家可以自行研究druid如何将yml或者properties文件中的配置信息注入到datasource中,我就不跟大家一起了。分析到这里,相信大家对springboot加载流程有一个整理的理解,同时也可以自定义starter启动器,如果还是有一些问题,可以自己再跟一遍源码,并且学习他人的自定义starter。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/92815.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

微服务调用工具

微服务调用工具目录概述需求&#xff1a;设计思路实现思路分析1.A2.B3.C参考资料和推荐阅读Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,make a better result,wait for change,challenge Survive…

Postman API测试工具 - 初认知 基本使用(一)

Postman - API测试工具 初认知&#xff08;一&#xff09; 文章目录Postman - API测试工具 初认知&#xff08;一&#xff09;一、什么是Postman&#xff1f;二、如何下载Postman&#xff1f;三、Postman的使用四、处理GET请求&#xff1a;五、处理POST请求总结一、什么是Postm…

Python 缩进语法的起源:上世纪 60-70 年代的大胆创意

上个月&#xff0c;Python 之父 Guido van Rossum 在推特上转发了一篇文章《The Origins of Python》&#xff0c;引起了我的强烈兴趣。 众所周知&#xff0c;Guido 在 1989 年圣诞节期间开始创造 Python&#xff0c;当时他就职于荷兰数学和计算机科学研究学会&#xff08;简称…

MySQL之聚合查询和联合查询

一、聚合查询&#xff08;行与行之间的计算&#xff09; 1.常见的聚合函数有&#xff1a; 函数 说明 count 查询到的数据的数量 sum 查询到的数据的总和&#xff08;针对数值&#xff0c;否则无意义&#xff09; avg 查询到的数据的平均值&#xff08;针对数值&#xf…

北京智和信通 | 无人值守的IDC机房动环综合监控运维

随着信息技术的发展和全面应用&#xff0c;数据中心机房已成为各大企事业单位维持业务正常运营的重要组成部分&#xff0c;网络设备、系统、业务应用数量与日俱增&#xff0c;规模逐渐扩大&#xff0c;一旦机房内的设备出现故障&#xff0c;将对数据处理、传输、存储以及整个业…

极光笔记 | 以静制动:行为触发营销助力用户转化

01、营销人&#xff0c;你是否饱受困扰&#xff1f; 作为营销人的你&#xff0c;从996到007&#xff0c;每天从早忙到晚&#xff0c;但还是没办法把访客转化成客户&#xff1f; 作为营销人的你&#xff0c;想通过APP通知、短信、邮件、公众号消息等方式&#xff0c;把所有能想…

牛客题霸sql入门篇之条件查询(二)

牛客题霸sql入门篇之条件查询(二) 2 基础操作符 2.1 查找学生是北大的学生信息 2.1.1 题目内容 2.1.2 示例代码 SELECT device_id,university FROM user_profile WHERE university北京大学2.1.3 运行结果 2.1.4 考察知识点 WHERE子句中可以写查询的条件,用于筛选出符合的…

java SPI机制的使用及原理

本片文章是针对dubbo SPI机制深入分析的平滑过渡的作用。当然咱们主要是学习优秀的思想&#xff0c;SPI就是一种解耦非常优秀的思想&#xff0c;我们可以思考在我们项目开发中是否可以使用、是否可以帮助我们解决某些问题、或者能够更加提升项目的框架等 一、SPI是什么 SPI&a…

新冠病毒:KN95(GB2626类型口罩)是否有效阻挡?

点击上方“青年码农”关注回复“源码”可获取各种资料​今天刷新闻&#xff0c;看到很多官方账号发布&#xff0c;只有五种编码口罩能防疫&#xff0c;分别是医用防护口罩&#xff08;GB19083-2010&#xff09;医用外科口罩&#xff08;YY0469-2011&#xff09;一次性使用医用口…

华纳音乐集团 Game Jam 来啦!

为了给 2022 年画上一个完美的句点&#xff0c;The Sandbox 与华纳音乐集团合作&#xff0c;为你们带来本年度的最后一次 Game Jam&#xff01; 我们邀请 The Sandbox 用户以音乐为题创建游戏体验。你们可以自由地创造社交体验&#xff0c;但也可以创造具有故事情节的游戏。请给…

云原生|kubernetes|CKA模拟测试-2022(1---10题)(一)

第一题&#xff1a; Task weight: 1% You have access to multiple clusters from your main terminal through kubectl contexts. Write all those context names into /opt/course/1/contexts. Next write a command to display the current context into /opt/course/1/c…

【反外挂】内存加密与监视

前言 手游防破解防外挂技术方案&#xff08;一&#xff09;客户端篇 各种作弊方案中&#xff0c;其中一种是直接修改内存数据&#xff0c;如下。 若要修改玩家当前的金币数&#xff0c;先用工具在内存中搜索当前的金币数值&#xff0c;会搜出来很多内存地址。然后消耗一些金币…

Java集合概述

集合概述 集合是一个容器,是一个载体,可以一次容纳多个对象。前面学习的数组其实就是一个集合。 集合不能直接存储基本数据类型&#xff0c;基本数据类型都是经过自动装箱后变成包装类型存放的&#xff1b; 集合也不能直接存储Java对象&#xff0c;集合中存储的是Java对象的内…

扫码点餐小程序源码 多商户外卖点餐自助扫码预约源码

智慧餐厅扫码点餐小程序系统源码&#xff0c;二维码点餐&#xff0c;微信支付宝点餐系统源码&#xff0c;外卖点餐源码 1. 开发语言&#xff1a;JAVA 2. 数据库&#xff1a;MySQL 3. 原生小程序 4. Sass 模式 5. 带调试视频 6. 可付费调试服务 私信了解更多&#xff01;…

VCS3 debug的基础

1、基础知识 使用命令行进行debug。 使用VCS进行debug的三种方式&#xff1a;专门做debug的工具目前最好的是Verdi 1、系统函数的调用 2、通过命令行的方式 3、使用DVE(GUI) debug需要注意的因素&#xff1a; 1、仿真速度&#xff08;开关选项&#xff08;command_time\ru…

超长距离CDN类视频直播延时估算

超长距离RTMP视频直播延时估算值。 摘录内容如下&#xff1a; 简单估算一下大概的网络延时。众所周知&#xff0c;光在真空中的速度约为300,000km/s&#xff0c;而在其他介质中光速会大大降低&#xff0c;所以在普通光纤中&#xff0c;工程上一般认为传输速度是200,000km/s。…

jsp+ssm计算机毕业设计ssm新冠疫苗预约接种信息管理【附源码】

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; JSPSSM mybatis Maven等等组成&#xff0c;B/S模式 Mave…

Pytest框架测试用例规则和运行方式

目录 一、默认的测试用例规则 二、测试用例执行顺序 三、测试用例运行方式 3.1.主函数模式 3.1.1.主函数模式&#xff1a;4种运行方式 3.1.2.文件框架如下图 3.2.命令行模式 3.2.1.命令行模式&#xff1a;4种运行方式 3.2.2.第2种运行方式框架 3.3.通过读取配置文…

【JAVA】抽象类和接口

&#x1f3c6;今日学习目标&#xff1a;抽象类和接口 &#x1f603;创作者&#xff1a;颜颜yan_ ✨个人主页&#xff1a;颜颜yan_的个人主页 ⏰本期期数&#xff1a;第二期 &#x1f389;专栏系列&#xff1a;JAVA 文章目录一、抽象类抽象类的定义规则示例二、接口接口定义与语…

Volatile关键字简述

Volatile关键字前言前置知识程序、进程、线程程序进程线程并发所涉及的一些特性线程安全原子性可见性Volatile案例环境代码展示可见性测试原子性测试前言 最近在看《Java并发编程实战》&#xff0c;期望对一些并发的知识点做一些总结。最好有一定的Java基础、并发的基础。 前…