springboot启动流程源码分析(二)

springboot启动流程源码分析(二)

前言:前面和大家一起学习了springboot启动流程源码中如何从springboot过度到spring以及springboot如何内置tomcat,如何还不了解的童鞋可以去看一下之前的文章(springboot启动流程源码分析(1))。今天和大家一起学习的是springboot如何加载第三方的starter,只有熟悉了这个原理我们才会发现自定义springboot的starter也是非常容易。

一、引入思考的问题

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。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/111212.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Java基础之—反射(非常重要)

    Java基础之—反射(非常重要)反射是框架设计的灵魂(使用的前提条件:必须先得到代表的字节码的Class,Class类用于表示.class文件(字节码))一、反射的概述JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。要想解剖一个类,必须先要获取到该类的

    2022年4月30日
    57
  • [已发表,转载勘误]Android upx脱壳「建议收藏」

    [已发表,转载勘误]Android upx脱壳「建议收藏」已发在https://www.anquanke.com/post/id/197643不过有部分内容发布之后无法编辑,勘误后如下。Androidupx脱壳写在前面因为我不是pc平台过来的,而是直接从Android入门的,所以upx壳其实一开始并不了解,后来接触到,但是可以直接动态调试或者做个内存快照,对我来说加没加upx其实对我逆向分析影响不大。另一方面upx壳因为开源且其实有很多脱壳的教…

    2022年7月19日
    18
  • 用计算机制作动画,如何使用制作工具制作一个简单的Flash动画-电脑自学网

    用计算机制作动画,如何使用制作工具制作一个简单的Flash动画-电脑自学网怎么制作Flash动画?通过AdobeFlash我们可以制作出非常有趣好看的动画,也可以制作一键简单的小动画,下面给大家介绍如何使用制作工具制作一个简单的Flash动画。操作方法:1、打开flash软件,如下图所示。2、选择新建一个flash文档,如下图所示。3、下图所示是flash的工作区域,对于各个区域的名称,我会在后续课程中进行讲解。4、先将舞台改为黑色:如下图所示。5、在舞台上利用举行工…

    2022年5月20日
    30
  • python ip池(python 连接池)

    ,都说标题是文章的灵魂,想了半天没想到什么比较有创意的标题,只好拿一个去年的爆款标题套一下。啊哈哈哈哈哈哈,朕真是太机智了这是一篇介绍如何使用python搭建IP池的文章,如果爱卿对此不感兴趣,那很抱歉,标题耽误了你宝贵的时间。事情的起因是这样,前段时间我写了一篇介绍如何爬取小说的blog【python那些事.No2】,在爬取的过程中,发现同一个IP连续只能获取前几页小说内容,原本是想搭建…

    2022年4月11日
    39
  • mybatis update没有打印影响行数[通俗易懂]

    今天在排除问题的时候,发现有一个mybatisupdate语句没有打印出影响行数,原因是mapper.xml文件中update语句的标签是<select>…..</sele

    2022年2月16日
    39
  • git基本使用(超详细)[通俗易懂]

    git基本使用(超详细)[通俗易懂]git基本使用一:Git是什么?Git是目前世界上最先进的分布式版本控制系统。二:SVN与Git的最主要的区别?1.SVN是集中式版本控制系统,版本库是集中放在中央服务器的,而干活的时候,用的都是自己的电脑,所以首先要从中央服务器哪里得到最新的版本,然后干活,干完后,需要把自己做完的活推送到中央服务器。集中式版本控制系统是必须联网才能工作,如果在局域网还可以,带宽够大,速度够快,如果在互联网下,如果网速慢的话,就纳闷了。2.Git是分布式版本控制系统,那么它就没有中央服务器的,每个人的电脑就是一个

    2022年9月21日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号