Spring Boot启动源码分析

启动过程

三个要点:

  1. 启动类(SpringbootdemoApplication)上面的注解
  2. 创建SpringApplication对象
  3. run方法

注解:

@Target(ElementType.TYPE)            // 注解的适用范围,其中TYPE用于描述类、接口(包括包注解类型)或enum声明
@Retention(RetentionPolicy.RUNTIME)  // 注解的生命周期,保留到class文件中(三个生命周期)
@Documented                          // 表明这个注解应该被javadoc记录
@Inherited                           // 子类可以继承该注解
@SpringBootConfiguration             // 继承了Configuration,表示当前是注解类
@EnableAutoConfiguration             // 开启springboot的注解功能,springboot的四大神器之一,其借助@import的帮助
@ComponentScan(excludeFilters = {    // 扫描路径设置
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}

@SpringbootConfiguration:

表示这是一个配置类,可以被@ComponentScan扫描到,程序中只能有一个,并会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到spring容器中,并且实例名就是方法名。(springboot的注解,spring的注解是Configuration)

@ComponentScan:

@ComponentScan的功能其实就是自动扫描并加载符合条件的组件(比如@Component和@Repository等)或者bean定义,最终将这些bean定义加载到IoC容器中。

@Component下的子注解:@Service,@Repository,@Controller;默认会扫描当前包和所有子包。

@EnableAutoConfiguration:

  • AutoConfigurationPackage
    • @Import(AutoConfigurationPackages.Registrar.class)
      • AutoConfigurationPackages类中的getPackageName()返回了当前主程序类的同级以及子级的包组件,这也就是为什么,我们要把DemoApplication放在项目的最高级中
      • Registrar将主配置类(即@SpringBootApplication标注的类)的所在包及子包里面所有组件扫描加载到Spring容器
  • @Import(AutoConfigurationImportSelector.class)
    • AutoConfigurationImportSelector继承了 ImportSelector,ImportSelector中的selectImports方法,该方法的返回值通过反射会被纳入到spring容器管理(其返回值是全限定java类名(字符串))。其核心功能是获取spring.factories中EnableAutoConfiguration所对应的Configuration类列表,经过删选之后(去重—List转Set再转List,从注解的exclude/excludeName属性中获取排除项进行排除),最终得到用于Import的configuration和exclusion
      • selectImports方法中List configurations = getCandidateConfigurations(annotationMetadata,attributes)就会调用SpringFactoriesLoader的方法,可以从classpath中搜索META-INF/spring.factories配置文件,并读取配置(获得之后进行删除选)

创建SpringApplication对象

https://segmentfault.com/a/1190000019560001

  1. 推断应用程序的类型,进而根据应用程序的类型创建恰当的ApplicationContext,默认的应用类型有三种:非web环境、web环境、reactive环境三种

  2. 初始化ApplicationContextinitializer(初始化器)和ApplicationListener(监听器),都是借助于SpringFactoriesLoder的方式完成初始化。

    SpringFactoriesLoader会读取META-INF/spring.factories文件中的配置。一个工程项目中可以同时有多个META-INF/spring.factories文件(每个jar中都能有一个)。

    例如在spring-boot-autoconfigure和spring-boot两个jar的META-INF/spring.factories文件中,均有针对ApplicationContextInitializer的配置:

  3. 推断应用入口类,通过遍历异常堆栈找到方法名称是main的类,将其作为主类

run方法

https://blog.youkuaiyun.com/woshilijiuyi/article/details/82350057

public ConfigurableApplicationContext run(String... args) {
		// 统计时间用的工具类,用于记录当前springboot应用启动的开始时间,并且判断当前任务名是否存在,用于保证springboot应用不重复启动
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
  	
  	//初始化应用上下文和异常报告集合
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
  
  	//设置系统属性,java。awt.headless的值,默认为true,(没有图形化界面),目的是保证即使没有检测到显示器(服务器不需要显示器),也允许程序启动
		configureHeadlessProperty();
  
  	//获取并启动监听器
  	//创建所有spring运行监听器并发处开始执行事件
		// 获取实现了SpringApplicationRunListener接口的实现类,通过SPI机制加载
		// META-INF/spring.factories文件下的类
  	//通过SpringFactoriesLoader的机制加载所有的SpringApplicationRunListener。从名字就可以看出,			 SpringApplicationRunListener的作用就是监听SpringApplication.run方法的各个执行阶段,也可以理解成SpringApplication运行的生命周期。加载完所有的SpringApplicationRunListener后,会将其包装在SpringApplicationRunListeners
		SpringApplicationRunListeners listeners = getRunListeners(args);
		// 首先调用SpringApplicationRunListener的starting方法
  	//listeners.starting()这句代码通知所有的SpringApplicationRunListener:“注意啦、注意啦,SpringBoot应用要开始启动啦”。
		listeners.starting();
  
 
		try {
       // 参数封装,也就是在命令行下启动应用带的参数,如--server.port=9000
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

      //构造容器环境
			//根据springApplicationRunListeners和应用参数来准备spring环境
      //prepareEnvironment的作用:加载外部化配置资源到environment,包括命令行参数、servletConfigInitParams、servletContextInitParams、systemProperties、sytemEnvironment、random、application.yml(.yaml/.xml/.properties)等;初始化日志系统。
      //创建环境完成后回调SpringApplicationRunListener.environmentPrepared()方法,表示环境准备完成
      //listeners.environmentPrepared(environment)代码再次通知所有的SpringApplicationRunListener:“注意啦、注意啦,SpringBoot应用使用的Environment准备好啦”。
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			configureIgnoreBeanInfo(environment);

			// 启动时打印banner
			Banner printedBanner = printBanner(environment);

      //创建容器
			// 创建上下文对象,创建IOC容器:ApplicationContext;决定创建web IOC还是普通的IOC容器
      //根据当前应用的类型webApplicationType来匹配对应的ApplicationContext,是servlet、reactive或者非web应用
      //创建web应用上下文,对其部分属性:reader、scanner、beanFactory进行了实例化;reader中实例化了属性conditionEvaluator;scanner中添加了两个AnnotationTypeFilter:一个针对@Component,一个针对@ManagedBean;beanFactory中注册了8个注解配置处理器
			context = createApplicationContext();
			
      //用来支持报告关于启动的错误
			// 获取SpringBootExceptionReporter接口的类,异常报告
      //这一步的逻辑和实例化初始化器和监听器的一样,都是通过调用 getSpringFactoriesInstances 方法来获取配置的异常类名称并实例化所有的异常处理类。ConfigurableApplicationContext
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);

      //准备容器:加载环境,加载自动装配的bean定义,回调监听器:springboot应用要使用到的bean都已经加载进application了
      //Spring上下文前置处理
      //将启动类注入容器,为后续开启自动化配置奠定基础
      //prepareContext类里面有一个load方法(最终会执行BeanDefinitionLoader的load):load将bean(springApplication)包装成BeanDefinition,参数即为我们项目启动时传递的参数:SpringApplication.run(SpringBootApplication.class, args);由于我们指定了启动类,所以上面也就是加载启动类到容器。,此时只再是将SpringApplication(一个bean)包装成BeanDefinition,count为1
      /*
         * 准备上下文环境,将environment保存到IOC容器中; 而且applyInitializers(),
         * 回调之前保存的所有的applicationContextInitializer的initialize()方法,完成ApplicationContext的初始化;
         * 回调所有的SpringApplicationRunListener的contextPrepareded(),通知所有的SpringApplicationRunListener,springboot应用使用的ApplicationContext准备好了
         *向ApplicationContext中加载所有的bean,bean的定义可以来自多个source,每个source既可以是XML文件形式的bean配置,也可以Java注解形式的bean配置。在加载bean的过程中,如果配置了@EnableAutoConfiguration注解的话将会识别哪些是需要自动装配。加载bean定义的代码是load方法;
         * 最后回调所有的SpringApplicationRunListener的contextLoaded()方法,通知所有的SpringApplicationRunListener,springboot应用要使用到的bean都已经加载进Application拉
			*/
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);

      //刷新容器:启动spring容器,bean对象的创建,开始自动装配
			// 核心方法,启动spring容器,刷新Spring上下文,里面有refresh方法,自动配置
      //@Configuration、AutoConfigurationImportSelector、AutoConfigurationPackage.Registrar
      //Registrar:这个注解的作用就是将主配置类所在的包作为自动配置包进行管理,为后期SpringBoot加载资源提供一个扫描的包路径
      //AutoConfigurationImportSelector:用来筛选被@Import的Configuration类,selectImports()加载factories中的全路径,并进行筛选,删选的是xxxAutoConfiguration
      //doProcessConfigurationClass:扫描@Component等bean对象
      /* 
      	刷新容器:IOC容器的初始化(扫描所有的配置类、@Bean等,加载并创建IOC容器中所有的组件。如果是web应用,
      	还会创建嵌入式的tomcat),当执行完refreshContext()后,IOC容器即创建完毕
      	上一个方法是知道哪些需要自动装配,现在这个方法是开始自动装配
			*/
			refreshContext(context);
      
      //Spring上下文后置处理,是预留的,可根据需求做一些定制化操作
       /*
         * 从IOC容器中获取所有的ApplicationRunner和CommandLineRunner,
         * 然后先回调ApplicationRunner 再回调 CommandLineRunner
        */
			afterRefresh(context, applicationArguments);

			// 统计结束,停止计时监控类,统计一些任务执行虚拟系
			stopWatch.stop();
      
      //输出日志记录执行主类名,事件信息
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
      
			// listeners.started(context)通知所有的SpringApplicationRunListener:“注意啦、注意啦,SpringBoot应用的ApplicationContext已经启动啦”。
			listeners.started(context);

			// ApplicationRunner
			// CommandLineRunner
			// 获取这两个接口的实现类,并调用其run方法
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			// listeners.running(context)通知所有的SpringApplicationRunListener:“注意啦、注意啦,SpringBoot应用已经开始运行啦”。
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值