SpringBoot 原理深入及源码剖析

SpringBoot 项目间接继承 spring-boot-dependencies,该文件对常用技术框架进行了统一版本管理,所以在SpringBoot 项目 pom.xml 引入spring-boot-dependencies管理的依赖文件不需要标注依赖文件版本号。引入 starter 就可以实现对应场景开发,而不需要额外导入相关依赖文件。

自动配置(启动流程)

SpringBoot 应用启动入口是 @SpringBootApplication 注解标注类中的 main() 方法, @SpringBootApplication 能够扫描 Spring 组件并自动配置 SpringBoot

@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 {
...
}
复制代码

1. @SpringBootConfiguration 注解

@SpringBootConfiguration 注解表示 Spring Boot 配置类,该注解仅仅是对 @Configuration 注解的简单封装,与 @Configuration 注解作用相同。

2. @EnableAutoConfiguration 注解

@EnableAutoConfiguration 注解表示开启自动配置功能。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 自动配置包
@AutoConfigurationPackage
// 自动配置类扫描导入
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
	Class<?>[] exclude() default {};
    String[] excludeName() default {};
}
复制代码
  • @AutoConfigurationPackage 注解

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    // 导入 Registrar 组件类
    @Import(AutoConfigurationPackages.Registrar.class)
    public @interface AutoConfigurationPackage {
    }
    复制代码
    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    	// 这个方法是导入组件类的具体实现
        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            // 将主程序类所在包及其子包下的组件扫描到 Spring 容器中
            register(registry, new PackageImport(metadata).getPackageName());
        }
    
        @Override
        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new PackageImport(metadata));
        }
    }
    
    private static final class PackageImport {
        private final String packageName;
        PackageImport(AnnotationMetadata metadata) {
            // 获取注解包名
            this.packageName = ClassUtils.getPackageName(metadata.getClassName());
        }
        String getPackageName() {
            return this.packageName;
    	}
    }
    复制代码
  • @Import(AutoConfigurationImportSelector.class)

    AutoConfigurationImportSelector 这个类导入到 Spring 容器中, AutoConfigurationImportSelector 可以帮助 SpringBoot 应用将所有符合条件的配置都加载到当前 SpringBoot 创建并使用的 IoC 容器(ApplicationContext) 中。

    通过源码分析这个类中是通过 selectImports() 这个方法告诉 SpringBoot 都需要导入哪些组件

    // 这个方法告诉springboot都需要导入那些组件
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        //判断 enableautoconfiguration注解有没有开启,默认开启(是否进行自动装配)
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        //1. 加载配置文件META-INF/spring-autoconfigure-metadata.properties,从中获取所有支持自动配置类的条件
        //作用:SpringBoot使用一个Annotation的处理器来收集一些自动装配的条件,那么这些条件可以在META-INF/spring-autoconfigure-metadata.properties进行配置。
        // SpringBoot会将收集好的@Configuration进行一次过滤进而剔除不满足条件的配置类
        // 自动配置的类全名.条件=值
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
        
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
    复制代码
    • loadMetadata()

      final class AutoConfigurationMetadataLoader {
          //文件中为需要加载的配置类的类路径
          protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties";
      
      	private AutoConfigurationMetadataLoader() {
      	}
          public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
              //重载方法
              return loadMetadata(classLoader, PATH);
      	}
          
          static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
              try {
                  //1.读取spring-boot-autoconfigure.jar包中spring-autoconfigure-metadata.properties的信息生成urls枚举对象
                  Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path) : ClassLoader.getSystemResources(path);
                  // 遍历 URL 数组,读取到 properties 中
                  Properties properties = new Properties();
      
                  //2.解析urls枚举对象中的信息封装成properties对象并加载
                  while (urls.hasMoreElements()) {
                      properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
                  }
                  
      			// 将 properties 转换成 PropertiesAutoConfigurationMetadata 对象
      			//根据封装好的properties对象生成AutoConfigurationMetadata对象返回
      			return loadMetadata(properties);
      		} catch (IOException ex) {
      			throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
      		}
      	}
      
      	static AutoConfigurationMetadata loadMetadata(Properties properties) {
      		return new PropertiesAutoConfigurationMetadata(properties);
      	}
      
      	/**
      	 * {@link AutoConfigurationMetadata} implementation backed by a properties file.
      	 */
      	private static class PropertiesAutoConfigurationMetadata implements AutoConfigurationMetadata {
      
              /**
               * Properties 对象
               */
      		private final Properties properties;
      
      		PropertiesAutoConfigurationMetadata(Properties properties) {
      			this.properties = properties;
      		}
      
      		@Override
      		public boolean wasProcessed(String className) {
      			return this.properties.containsKey(className);
      		}
      
      		@Override
      		public Integer getInteger(String className, String key) {
      			return getInteger(className, key, null);
      		}
      
      		@Override
      		public Integer getInteger(String className, String key, Integer defaultValue) {
      			String value = get(className, key);
      			return (value != null) ? Integer.valueOf(value) : defaultValue;
      		}
      
      		@Override
      		public Set<String> getSet(String className, String key) {
      			return getSet(className, key, null);
      		}
      
      		@Override
      		public Set<String> getSet(String className, String key, Set<String> defaultValue) {
      			String value = get(className, key);
      			return (value != null) ? StringUtils.commaDelimitedListToSet(value) : defaultValue;
      		}
      
      		@Override
      		public String get(String className, String key) {
      			return get(className, key, null);
      		}
      
      		@Override
      		public String get(String className, String key, String defaultValue) {
      			String value = this.properties.getProperty(className + "." + key);
      			return (value != null) ? value : defaultValue;
      		}
      	}
      }
      复制代码
    • getAutoConfigurationEntry()

      protected AutoConfigurationEntry getAutoConfigurationEntry( AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
          // 1. 判断是否开启注解。如未开启,返回空串
          if (!isEnabled(annotationMetadata)) {
              return EMPTY_ENTRY;
          }
          // 2. 获得注解的属性
          AnnotationAttributes attributes = getAttributes(annotationMetadata);
      
          // 3. getCandidateConfigurations()用来获取默认支持的自动配置类名列表
          // spring Boot在启动的时候,使用内部工具类SpringFactoriesLoader,查找classpath上所有jar包中的META-INF/spring.factories,
          // 找出其中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的属性定义的工厂类名称,
          // 将这些值作为自动配置类导入到容器中,自动配置类就生效了
          List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
      
      
          // 3.1 //去除重复的配置类,若我们自己写的starter 可能存在重复的
          configurations = removeDuplicates(configurations);
          // 4. 如果项目中某些自动配置类,我们不希望其自动配置,我们可以通过EnableAutoConfiguration的exclude或excludeName属性进行配置,
          // 或者也可以在配置文件里通过配置项“spring.autoconfigure.exclude”进行配置。
          //找到不希望自动配置的配置类(根据EnableAutoConfiguration注解的一个exclusions属性)
          Set<String> exclusions = getExclusions(annotationMetadata, attributes);
          // 4.1 校验排除类(exclusions指定的类必须是自动配置类,否则抛出异常)
          checkExcludedClasses(configurations, exclusions);
          // 4.2 从 configurations 中,移除所有不希望自动配置的配置类
          configurations.removeAll(exclusions);
      
          // 5. 对所有候选的自动配置类进行筛选,根据项目pom.xml文件中加入的依赖文件筛选出最终符合当前项目运行环境对应的自动配置类
      
          //@ConditionalOnClass : 某个class位于类路径上,才会实例化这个Bean。
          //@ConditionalOnMissingClass : classpath中不存在该类时起效
          //@ConditionalOnBean : DI容器中存在该类型Bean时起效
          //@ConditionalOnMissingBean : DI容器中不存在该类型Bean时起效
          //@ConditionalOnSingleCandidate : DI容器中该类型Bean只有一个或@Primary的只有一个时起效
          //@ConditionalOnExpression : SpEL表达式结果为true时
          //@ConditionalOnProperty : 参数设置或者值一致时起效
          //@ConditionalOnResource : 指定的文件存在时起效
          //@ConditionalOnJndi : 指定的JNDI存在时起效
          //@ConditionalOnJava : 指定的Java版本存在时起效
          //@ConditionalOnWebApplication : Web应用环境下起效
          //@ConditionalOnNotWebApplication : 非Web应用环境下起效
      
          //总结一下判断是否要加载某个类的两种方式:
          //根据spring-autoconfigure-metadata.properties进行判断。
          //要判断@Conditional是否满足
          // 如@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })表示需要在类路径中存在SqlSessionFactory.class、SqlSessionFactoryBean.class这两个类才能完成自动注册。
          configurations = filter(configurations, autoConfigurationMetadata);
      
      
          // 6. 将自动配置导入事件通知监听器
          //当AutoConfigurationImportSelector过滤完成后会自动加载类路径下Jar包中META-INF/spring.factories文件中 AutoConfigurationImportListener的实现类,
          // 并触发fireAutoConfigurationImportEvents事件。
          fireAutoConfigurationImportEvents(configurations, exclusions);
          // 7. 创建 AutoConfigurationEntry 对象
          return new AutoConfigurationEntry(configurations, exclusions);
      }
      复制代码
      • getCandidateConfigurations()

        protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
            // 让SpringFactoryLoader去加载一些组件的名字
            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;
        }
        复制代码
        • loadFactoryNames()

          public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
              String factoryClassName = factoryClass.getName();
              return loadSpringFactories(classLoader).getOrDefault( factoryClassName, 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 {
                      // 如果类加载器不为 null,则加载类路径下spring.factories,将其中设置的配置类的全路径信息封装为 Enumeration 类对象
                      // public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
                      Enumeration<URL> urls = (classLoader != null ?
          classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : 
          ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
          			result = new LinkedMultiValueMap<>();
                      // 循环 Enumeration 类对象,根据相应的节点信息生成 Properties 对象,通过传入的键获取值,在将值切割为一个个小的字符串转化为 Array,方法result集合中
          			while (urls.hasMoreElements()) {
                          URL url = urls.nextElement();
                          UrlResource resource = new UrlResource(url);
                          Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                          for (Map.Entry<?, ?> entry : properties.entrySet()) {
                              String factoryClassName = ((String) entry.getKey()).trim();
                              for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                                  result.add(factoryClassName, factoryName.trim());
          					}
          				}
          			}
          			cache.put(classLoader, result);
          			return result;
          		}
          		catch (IOException ex) {
          			throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
          		}
          	}
          复制代码

    @EnableAutoConfiguration 就是从classpath 中搜寻 MATE-INF/spring.factories 配置文件,并将其中 org.springframework.boot.autoconfigure.EnableutoConfiguration 对应的配置项通过反射示例化为对应的标注了 @Configuration 的JavaConfig形式的配置类,并加载到 IoC 容器中。

总结:

Springboot 底层实现自动装配的步骤是:

  1. Springboot 应用启动;
  2. @SpringBootApplication 起作用;
  3. @EnableAutoConfiguration ;
  4. @AutoConfigurationPackage: 这个组合注解主要是 @Import(AutoConfigurationPackages.Registrar.class) , 它通过将 Registrar 类导入到容器中,而 Registrar 类作用是扫描主配置类同级目录及其子包,并将相应的组件导入到 Springboot创建管理容器中
  5. @Import(AutoConfigurationImportSelector.class): 它通过将 AutoConfigurationImportSelector 类导入到容器中, AutoConfigurationImportSelector 类作用是通过 selectImports 方法只想的过程中,会使用内部工具类 SpringFactoriesLoader 查找 classpath 上所用 jar 包中的 MATE-INF/spring.factories 进行加载, 实现将配置类信息交给 SpringFactory 加载器进行一系列的容器创建过程。

3. @ComponentScan 注解

@ComponentScan 注解具体扫描的包的根路径由 Spring Boot 项目主程序启动类所在包的位置决定,在扫描过程中由前面介绍的 @AutoConfigurationPackage 注解进行解析,从而得到 Springboot 项目主程序启动类所在包的具体位置。

总结:

|- @SpringBootConfiguration
	|- @Configuration //通过javaConfig的方式来添加组件到 IoC 容器中
|- @EnableAutoConfiguration
    |- @AutoConfigurationPackage //自动配置包,与@ComponentScan扫描到的添加到IOC
    |- @Import(AutoConfigurationImportSelector.class) //到META-INF/spring.factories中定义的bean 添加到 IoC 容器中
|- @ComponentScan //包扫描
复制代码

自定义Starter

Starter 使得使用某个功能的开发者不需要关注各种依赖库的处理,不需要具体的配置信息,由Springboot 自动通过classpath 路径下的类发现需要的 bean,并织入相应的 Bean。

步骤

  1. 自建工程,命名为 xxx-spring-boot-starter ,导入 spring-boot-autoconfigure 依赖

  2. 编写配置类

    @Configuration
    @ConditionalOnXxx
    
  3. resources 下创建 /META-INF/spring.factories ,在该文件中配置自定义配置类

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=/ com.test.config.MyAutoConfiguration
    复制代码
  4. 测试使用

SpringApplication.run()执行原理

源代码

//调用静态类,参数对应的就是SpringbootDemoApplication.class以及main方法中的args
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return run(new Class<?>[] { primarySource }, args);
}

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    //SpringApplication的启动由两部分组成:
    //1. 实例化SpringApplication对象
    //2. run(args):调用run方法
    return new SpringApplication(primarySources).run(args);
}
复制代码

1. SpringApplication 实例的初始化创建

public SpringApplication(Class<?>... primarySources) {
    this(null, primarySources);
}

@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.sources = new LinkedHashSet();
    // Banner 模式
    this.bannerMode = Mode.CONSOLE;
    this.logStartupInfo = true;
    // 是否添加 JVM 启动参数
    this.addCommandLineProperties = true;
    this.addConversionService = true;
    this.headless = true;
    this.registerShutdownHook = true;
    this.additionalProfiles = new HashSet();
    this.isCustomEnvironment = false;
    // 资源加载器
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");

    //项目启动类 SpringbootDemoApplication.class设置为属性存储起来
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

    //设置应用类型是SERVLET应用(Spring 5之前的传统MVC应用)还是REACTIVE应用(Spring 5开始出现的WebFlux交互式应用)
    // deduceFromClasspath()方法用于查看 ClassPath类路径下是否存在某个特征类,从而判断webApplicationType
    this.webApplicationType = WebApplicationType.deduceFromClasspath();

    // 设置初始化器(Initializer),最后会调用这些初始化器
    //所谓的初始化器就是org.springframework.context.ApplicationContextInitializer的实现类,在Spring上下文被刷新之前进行初始化的操作
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

    // 设置监听器(Listener)
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

    // 初始化 mainApplicationClass 属性:用于推断并设置项目main()方法启动的主程序启动类
    this.mainApplicationClass = deduceMainApplicationClass();
}
复制代码

2. 项目初始化启动

public ConfigurableApplicationContext run(String... args) {
    // 创建 StopWatch 对象,并启动。StopWatch 主要用于简单统计 run 启动过程的时长。
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    // 初始化应用上下文和异常报告集合
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    // 配置 headless 属性
    configureHeadlessProperty();

    //(1)获取并启动监听器
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    try {
        // 创建  ApplicationArguments 对象 初始化默认应用参数类
        // args是启动Spring应用的命令行参数,该参数可以在Spring应用中被访问。如:--server.port=9000
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

        //(2)项目运行环境Environment的预配置
        // 创建并配置当前SpringBoot应用将要使用的Environment
        // 并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        // 排除不需要的运行环境
        configureIgnoreBeanInfo(environment);
        // 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体
        Banner printedBanner = printBanner(environment);

        // (3)创建Spring容器
        // 根据 webApplicationType 进行判断,确定容器类型,如果为 SERVLET 类型,会反射创建相应的字节码,AnnotationConfigServletWebServerApplicationContext, 接着使用之前传世话设置的context、environment、listeners、applicationArgument 进行应用上下文的组装配置
        context = createApplicationContext();
        // 获得异常报告器 SpringBootExceptionReporter 数组
        //这一步的逻辑和实例化初始化器和监听器的一样,
        // 都是通过调用 getSpringFactoriesInstances 方法来获取配置的异常类名称并实例化所有的异常处理类。
        exceptionReporters = getSpringFactoriesInstances(
            SpringBootExceptionReporter.class,
            new Class[] { ConfigurableApplicationContext.class }, context);


        // (4)Spring容器前置处理
        //这一步主要是在容器刷新之前的准备动作。包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础。
        prepareContext(context, environment, listeners, applicationArguments,
                       printedBanner);

        // (5):刷新容器
        // 开启(刷新)Spring 容器,通过refresh方法对整个IoC容器的初始化(包括Bean资源的定位、解析、注册等等),同时向JVM运行时注册一个关机钩子,在JVM关机时关闭这个上下文,除非它当时已经关闭
        refreshContext(context);

        // (6):Spring容器后置处理
        //扩展接口,设计模式中的模板方法,默认为空实现。
        // 如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理
        afterRefresh(context, applicationArguments);
        // 停止 StopWatch 统计时长
        stopWatch.stop();
        // 打印 Spring Boot 启动的时长日志。
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        // (7)发出结束执行的事件通知
        listeners.started(context);

        // (8):执行Runners
        //用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后立即执行一些特定程序
        //Runner 运行器用于在服务启动时进行一些业务初始化操作,这些操作只在服务启动后执行一次。
        //Spring Boot提供了ApplicationRunner和CommandLineRunner两种服务接口
        callRunners(context, applicationArguments);
    } catch (Throwable ex) {
        // 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    //   (9)发布应用上下文就绪事件
    //表示在前面一切初始化启动都没有问题的情况下,使用运行监听器SpringApplicationRunListener持续运行配置好的应用上下文ApplicationContext,
    // 这样整个Spring Boot项目就正式启动完成了。
    try {
        listeners.running(context);
    } catch (Throwable ex) {
        // 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    //返回容器
    return context;
}
复制代码
SpringBoot 原理深入及源码剖析

原文 

https://juejin.im/post/5f1bb0b3e51d4534b1307b51

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » SpringBoot 原理深入及源码剖析

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址