SpringBoot启动原理

SpringBoot自动装配原理

Spring Factories机制

SpringBoot自动装配是根据Spring Factories机制实现的,Spring Factories机制是SpringBoot的一种服务发现机制,这种扩展机制和Java SPI机制相似。Spring Boot会扫描所有jar包下的META-INFO/spring.factories文件,并读取其中内容,进行实例化,这种机制也是Spring Boot Starter的基础。

spring.factories

该文件本质上和properies文件类似,包含一组或多组键值对(key=value),其中key是接口的完全限定类名,value是接口实现类的完全限定类名,一个接口可以有多个实现类。

org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

Spring Factories实现原理

spring-core包里定义了一个springfactoriesloader类,这个类会扫描所有jar包下的META-INFO/spring.factories文件,并获取指定接口的配置,该类定义了俩对外的方法。

返回值方法描述
ListloadFactories(Class factoryType, @Nullable ClassLoader classLoader)根据接口获取实现类的实例,返回的是实现类对象列表。
ListloadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader)根据接口获取实现类的名称,返回实现类名称列表。

loadFactories()源码如下

public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
		Assert.notNull(factoryType, "'factoryType' must not be null");
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
		//使用loadFactoryNames获取接口的实现类
		List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
		if (logger.isTraceEnabled()) {
			logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
		}
		//遍历loadFactoryNames数组,创建实现类的对象
		List<T> result = new ArrayList<>(factoryImplementationNames.size());
		for (String factoryImplementationName : factoryImplementationNames) {
			result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
		}
		AnnotationAwareOrderComparator.sort(result);
		return result;
	}

loadFactoryNames()源码如下

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
		String factoryTypeName = factoryType.getName();
		//获取自动配置类
		return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
	}

loadSpringFactories()方法能够读取该项目中所有的jar包路径下的META-INFO/spring.factories文件的配置内容,并以map的形式返回。源码如下。

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
		Map<String, List<String>> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		result = new HashMap<>();
		try {
			//扫描所有jar包路径下的META-INFO/spring.faactories文件
			Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				//将扫描到的文件内容包装成properties对象
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					//提取properties对象中的key值
					String factoryTypeName = ((String) entry.getKey()).trim();
					//提取value值
					String[] factoryImplementationNames =
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
					//遍历配置类数组,转换成list集合
					for (String factoryImplementationName : factoryImplementationNames) {
						result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
								.add(factoryImplementationName.trim());
					}
				}
			}

			// Replace all lists with unmodifiable lists containing unique elements
			//将properties对象中的key与实现类集合一一对应存入result
			result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
					.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
			cache.put(classLoader, result);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
		return result;
	}

自动配置的加载
SpringBoot自动化配置也是基于spring factories机制实现,在spring-boot-autoconfigure-xxx.jar类路径下的META-INF/spring.factories中设置了spring boot自动配置内容。配置里面value取值是由多个xxxAutoConfiguration组成,每个xxxAutoConfiguration都是一个自动配置类。SpringBoot启动时,会利用spring-factories机制,将这些配置类实例化到容器,以实现自动配置。

@springbootapplication注解

是一个组合源注解,包含俩核心注解,@SpringBootConfiguration和@EnableAutoConfigration,其中@EnableAutoConfigration是自动配置的核心。

@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 {

@EnableAutoConfigration注解

用于开启springboot自动配置功能,使用@import注解通过AutoConfigrationImportSelector类(选择器)给容器中导入自动配置组件。

@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 {};
}

AutoConfigurationImportSelector类

SpringBoot启动流程

1、进入run方法

@SpringBootApplication
public class BlogApplication {

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

2、进入如下

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
   return new SpringApplication(sources).run(args);
}

3、看看SpringApplication的构造流程

private void initialize(Object[] sources) {
   if (sources != null && sources.length > 0) {
      this.sources.addAll(Arrays.asList(sources));
   }
   //初始化环境。环境分为三种:非web环境、web环境、reactive环境。
   //其判断逻辑就是判断是否存在指定的类,默认是Servlet环境
   this.webEnvironment = deduceWebEnvironment();
   //getSpringFactoriesInstances加载spring.factories文件,
   //设置ApplicationContextInitalizer
   setInitializers((Collection) getSpringFactoriesInstances(
         ApplicationContextInitializer.class));
   //获取监听器
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
   //设置启动类信息
   this.mainApplicationClass = deduceMainApplicationClass();
}

4、看看run方法

public ConfigurableApplicationContext run(String... args) {
   //开启关于启动时间的信息监控
   long startTime = System.nanoTime();
   //创建一个引导容器,并在此时(容器从未使用前)从spring.factories扫描一些实现了bootstrapper接口的类,
   //来引导容器里注册一些需要的东西
   DefaultBootstrapContext bootstrapContext = createBootstrapContext();
   //准备ApplicationContext
   ConfigurableApplicationContext context = null;
   //java.awt.headless是j2se的一种模式用于在缺少显示屏、键盘或鼠标时的系统配置
   //很多监控工具如jconsole需要将该值设为true 系统变量默认为true
   configureHeadlessProperty();
   //获取spring的监听器类,从spring.factories中获取,
   //默认的是以 org.springframework.boot.SpringApplicationRunListener为key,
   //获取到的监听器类型为EventPublishingRunListener
   SpringApplicationRunListeners listeners = getRunListeners(args);
   //监听器发送启动事件,触发所有存入SpringApplicationRunListeners的satrting事件
   listeners.starting(bootstrapContext, this.mainApplicationClass);
   try {
      //将args内容中的参数(类似 --spring.port=9999)解析成键值对存放到applicationArguments里
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      //创建一个environment对象,添加了好几个功能各异的PropertySource,
      //触发所有存入SpringApplicationRunListeners的environmentPrepared事件
      //将environment中的spring.main开头的配置数据,一一对应绑定到SpringApplication(即this)字段上
      ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
      //将environment的spring.beaninfo.ignore配置复制到System.Property去(若不存在)
      configureIgnoreBeanInfo(environment);	
      //根据this.bannerMode判断是否打印Banner,以及打印在哪里,根据this.banner判断打印什么样的banner
      Banner printedBanner = printBanner(environment);
      //根据web类型创建不同的ApplicationContext,这里的创建仅实例化而已,
      //没有从构造方法调用loadBeanDefinitions和refresh的逻辑
      context = createApplicationContext();
      //将ApplicationStartup(步骤记录器)复制给context
      context.setApplicationStartup(this.applicationStartup);
      //对context做一些配置,执行initializers的initialize()
      //发布prepareContext事件
      //关闭引导容器,
      //使用BeanDefinilitionLoader根据sources和run方法参数加载BeanDefinition到容器中
      //发布contextLoaded事件
      prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
      //注册一个钩子,当jvm关闭时,相应的关闭context,然后调用容器的refresh()方法
      refreshContext(context);
      //留给子类扩展
      afterRefresh(context, applicationArguments);
      //计时结束
      Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
      //打印计时数据
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
      }
      //发布started事件
      listeners.started(context, timeTakenToStartup);
      //从容器中取出ApplicationRunner/CommandLineRunner两类bean,并调用它们的run方法
      callRunners(context, applicationArguments);
   }
   catch (Throwable ex) {
      //发布failed事件
      handleRunFailure(context, ex, listeners);
      throw new IllegalStateException(ex);
   }
   try {
       //发布ready方法
      Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
      listeners.ready(context, timeTakenToReady);
   }
   catch (Throwable ex) {
       //发布failed事件
      handleRunFailure(context, ex, null);
      throw new IllegalStateException(ex);
   }
   return context;
}

5、重点步骤分析

5.1、获取监听器

就是从spring.factories文件中获取监听器集合,当有事件发生时调用监听器对应事件的方法。
默认是以 org.springframework.boot.SpringApplicationRunListener为key,获取到的监听器为EventPublishingRunListenener。

SpringApplicationRunListeners listeners = getRunListeners(args);

总结:Spring在启动是会从spring.factories加载监听器集合,默认类型是EventPublishingRunListenener,在事件发生时,EventPublishingRunListenener会去容器中寻找ApplicationListener对应的bean,并进行事件通知。

5.2、环境变量构造

加载一些配置文件的内容

ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);

进入

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
      DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
   // Create and configure the environment
   //获取或创建environment
   ConfigurableEnvironment environment = getOrCreateEnvironment();
   //将入参配置到环境配置中
   configureEnvironment(environment, applicationArguments.getSourceArgs());
   ConfigurationPropertySources.attach(environment);
   //发布环境准备事件
   listeners.environmentPrepared(bootstrapContext, environment);
   DefaultPropertiesPropertySource.moveToEnd(environment);
   Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
         "Environment prefix cannot be set via properties.");
   bindToSpringApplication(environment);
   if (!this.isCustomEnvironment) {
      environment = convertEnvironment(environment);
   }
   ConfigurationPropertySources.attach(environment);
   return environment;
}

在准备环境事件时,加载springboot配置文件,

5.3、创建上下文
5.4、SpringApplication#refreshContext

对容器进行一个刷新的工作,在此进行了大量的工作,这里的处理工作由springboot交给spring处理
最终进入到org.springframework.context.support.AbstractApplicationContext#refresh

主程序类(主入口类)
我们现在来分析一下主程序类中的相关基本注解。

@SpringBootApplication
@SpringBootApplication是Springboot应用标注在某个类上,用来说明这个类是Springboot的入口类,springboot就应该运行在这个类的main方法来启动Springboot应用,我们按住Command键点进去,我们主要看里面的注解

@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 {

	/**
	 * Exclude specific auto-configuration classes such that they will never be applied.
	 * @return the classes to exclude
	 */
	@AliasFor(annotation = EnableAutoConfiguration.class, attribute = "exclude")
	Class<?>[] exclude() default {};

	/**
	 * Exclude specific auto-configuration class names such that they will never be
	 * applied.
	 * @return the class names to exclude
	 * @since 1.3.0
	 */
	@AliasFor(annotation = EnableAutoConfiguration.class, attribute = "excludeName")
	String[] excludeName() default {};

	/**
	 * Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}
	 * for a type-safe alternative to String-based package names.
	 * @return base packages to scan
	 * @since 1.3.0
	 */
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
	String[] scanBasePackages() default {};

	/**
	 * Type-safe alternative to {@link #scanBasePackages} for specifying the packages to
	 * scan for annotated components. The package of each class specified will be scanned.
	 * <p>
	 * Consider creating a special no-op marker class or interface in each package that
	 * serves no purpose other than being referenced by this attribute.
	 * @return base packages to scan
	 * @since 1.3.0
	 */
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
	Class<?>[] scanBasePackageClasses() default {};

}

@SpringBootApplication
在@SpringBootApplication中,一个比较关键的注解就是@SpringBootApplication,@SpringBootConfiguration是Springboot的配置类,标注在某个类上表示,这个类是一个Springboot的配置,同样按住Ctrl键点进去,我们我们会看到@SpringBootConfiguration中有一个@Configuration,这个注解就是标注各种配置类,配置文件的,如果我们对AOP有一定理解的话,其实就知道配置类也是容器中的一个组件,所以在@Configuration中,我们是通过@Component进行标注的。

@EnableAutoConfiguration
回到@SpringBootApplication中,我会可以看到另一个配置类注解@EnableAutoConfiguration,这个注解是标注着开启自动配置功能,这样才使得我们不需要像以前在Spring项目中那样,配置繁琐的东西,而是Springboot会帮我们自动配置,@EnableAutoConfiguration告诉SpringBoot开启自动配置功能,这样启动配置才能生效,这个注解里面的东西,我们要好好讲一下,因为这样我们就会知道,SpringBoot是怎么帮我们自动加载相关依赖,并放置在哪里的,内容如下。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	/**
	 * Exclude specific auto-configuration classes such that they will never be applied.
	 * @return the classes to exclude
	 */
	Class<?>[] exclude() default {};

	/**
	 * Exclude specific auto-configuration class names such that they will never be
	 * applied.
	 * @return the class names to exclude
	 * @since 1.3.0
	 */
	String[] excludeName() default {};

}

@AutoConfigurationPackage自动配置包,Spring的底层注解@import,给容器导入一个组件,导入的组件由AutoConfigurationPackage.Registrar.class进行注册,其实AutoConfigurationPackage就是将主配置类(@SpringBootApplication标注的类)的所在包及下面所有的子包里面所有的组件扫描到Spring容器,如果你以前写过SpringMVC项目之类的会,就会知道,我们在写一些Controller或者Service的时候,我们需要在配置文件中,配置bean扫描controller的包名,而我们在SpringBoot就不需要了

AutoConfigurationImportSelector
在@AutoConfigurationPackage下面,我们会发现我们通过import导入了一个类,我们进入到这个类中,然后在下图位置debug一下这个类,如下图
在这里插入图片描述

就会发现,其实AutoConfigurationImportSelector就是给容器中导入非常多的自动配置类,也就是给容器中导入这个场景需要的所有组件,并配置好这些组件,有了自动配置类,就免去了我们手动编写配置注入功能组件等工作了。

Springboot在启动的时候,从类路径下的META-INF/Spring.factories中获取EnableAutoConfiguration指定的值,将这些值作为自动配置类导入到容器中,自动配置类就生效,帮我们进行自动配置的工作,如下图。
在这里插入图片描述

快速创建SpringBoot项目
前面我们创建一个项目是,先创建一个Maven项目,然后导入相关的Springboot依赖,但是其实我们可以在idea中,利用Idea创建项目,帮我们自动创建我们需要的相关依赖的springboot项目

————————————————
版权声明:本文为CSDN博主「BoCong-Deng」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/DBC_121/article/details/104429074

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

汉东哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值