• SpringBoot应用启动过程分析


    真的好奇害死猫!之前写过几个SpringBoot应用,但是一直没搞明白应用到底是怎么启动的,心里一直有点膈应。好吧,趁有空去看了下源码,写下这篇博客作为学习记录吧!
    个人拙见,若哪里有理解不对的地方,请各路大神指正,小弟不胜感激!

    一.应用启动类

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

    开发SpirngBoot应用时,入口类就这简单的几行。但是却完成了N多服务的初始化、加载和发布。那么这几行代码究竟干了什么呢,SpringBoot应用到底是怎么启动的。

    二.@SpringBootApplication注解

    2.1.SpringBootApplication注解

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

    @SpringBootApplication=@SpringBootConfiguration+@EnableAutoConfiguration+@ComponentScan

    2.2.@SpringBootConfiguration

    /**
     * Indicates that a class Spring Boot application
     * {@link Configuration @Configuration}. Can be used as an alternative to the Spring's
     * standard {@code @Configuration} annotation so that configuration can be found
     * automatically (for example in tests).
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Configuration
    public @interface SpringBootConfiguration {
    
    }
    

    SpringBootConfiguration注解和Spring的@Configuration注解作用一样。标注当前类是配置类,并会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到spring容器中。比如容器加载时,会生成Hello的Bean加载到IOC容器中。

    @SpringBootConfiguration
    public class ExampleConfig {
    	@Bean
    	public void Hello(){
    		System.out.println("hello");
    	}
    }
    

    2.3.@EnableAutoConfiguration

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

    这个注解是SpringBoot能进行自动配置的关键。@Import注解用于导入配置类,我们看下导入类EnableAutoConfigurationImportSelector。容器刷新时,会调用AutoConfigurationImportSelector类的selectImports方法,扫描META-INF/spring.factories文件自动配置类(key为EnableAutoConfiguration),然后Spring容器处理配置类。(对Spring的一些加载过程不清晰,我是相当的迷啊)

    2.4.@ComponentScan

    @ComponentScan(excludeFilters = {
          @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
          @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    /**
     * Configures component scanning directives for use with @{@link Configuration} classes.
     * Provides support parallel with Spring XML's {@code <context:component-scan>} element.
     *
     * <p>Either {@link #basePackageClasses} or {@link #basePackages} (or its alias
     * {@link #value}) may be specified to define specific packages to scan. If specific
     * packages are not defined, scanning will occur from the package of the
     * class that declares this annotation.
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Documented
    @Repeatable(ComponentScans.class)
    public @interface ComponentScan 
    

    @ComponentScan扫描指定的包路径,若未指定包路径,则以声明这个注解的类作为基本包路径。比如@SpringBootApplication就没有指定包路径,则DemoApplication的包路径将作为扫描的基本包路径,因此强烈建议将主类放在顶层目录下。

    excludeFilters属性指定哪些类型不符合组件扫描的条件,会在扫描的时候过滤掉。

    @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)
    

    比如上面这段代码。@Filter声明了过滤器类型类为自定义类型(需要实现TypeFilter接口),过滤器为AutoConfigurationExcludeFilter。当match方法为true,返回扫描类对象,否则过滤掉。但是要注意@ComponentScan的key为excludeFilters,因此这些类型将在包扫描的时候过滤掉,也就是说,ComponentScan在扫描时,发现当前扫描类满足macth的条件(match返回true),是不会将该类加载到容器的。

    	//metadataReader  表示读取到的当前正在扫描的类的信息
    	//metadataReaderFactory 表示可以获得到其他任何类的信息
    	@Override
    	public boolean match(MetadataReader metadataReader,
    			MetadataReaderFactory metadataReaderFactory) throws IOException {
    		return isConfiguration(metadataReader) && isAutoConfiguration(metadataReader);
    	}
    	//该类是带有Configuration注解的配置类
    	private boolean isConfiguration(MetadataReader metadataReader) {
    		return metadataReader.getAnnotationMetadata()
    				.isAnnotated(Configuration.class.getName());
    	}
    	//该类是否为spring.factory配置的自动配置类
    	private boolean isAutoConfiguration(MetadataReader metadataReader) {
    		return getAutoConfigurations()
    				.contains(metadataReader.getClassMetadata().getClassName());
    	}
    

    三.run(DemoApplication.class, args)解析

    3.1.进入SpringApplication

    public static ConfigurableApplicationContext run(Class<?>[] primarySources,
          String[] args) {
       return new SpringApplication(primarySources).run(args);
    }
    

    我们根据DemoApplication跟进代码,发现其调用的SpringApplication类的run方法。这个方法就干了2件事:一是创建SpringApplication对象,二是启动SpringApplication。

    3.2.SpringApplication构造器分析

    1.构造器

    public SpringApplication(Class<?>... primarySources) {
    	this(null, primarySources);
    }
    /**
    * Create a new {@link SpringApplication} instance. The application context will load
    * beans from the specified primary sources
    */
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
       this.resourceLoader = resourceLoader;
       Assert.notNull(primarySources, "PrimarySources must not be null");
       this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
      //根据应用是否存在某些类推断应用类型,分为响应式web应用,servlet类型web应用和非web应用,在后面用于确定实例化applicationContext的类型
       this.webApplicationType = WebApplicationType.deduceFromClasspath();
       //设置初始化器,读取spring.factories文件key ApplicationContextInitializer对应的value并实例化
       //ApplicationContextInitializer接口用于在Spring上下文被刷新之前进行初始化的操作
       setInitializers((Collection) getSpringFactoriesInstances(
             ApplicationContextInitializer.class));
    
       //设置监听器,读取spring.factories文件key ApplicationListener对应的value并实例化
       // interface ApplicationListener<E extends ApplicationEvent> extends EventListener
       //ApplicationListener继承EventListener,实现了观察者模式。对于Spring框架的观察者模式实现,它限定感兴趣的事件类型需要是ApplicationEvent类型事件
    
       setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
       //没啥特别作用,仅用于获取入口类class对象
       this.mainApplicationClass = deduceMainApplicationClass();
    }
    

    在构造器里主要干了2件事,一个设置初始化器,二是设置监听器。

    2.设置初始化器

    setInitializers((Collection) getSpringFactoriesInstances(
          ApplicationContextInitializer.class));
    
    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
       return getSpringFactoriesInstances(type, new Class<?>[] {});
    }
    
    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
          Class<?>[] parameterTypes, Object... args) {
       ClassLoader classLoader = getClassLoader();
       // Use names and ensure unique to protect against duplicates
       Set<String> names = new LinkedHashSet<>(
        //从类路径的META-INF处读取相应配置文件spring.factories,然后进行遍历,读取配置文件中Key(type)对应的value
             SpringFactoriesLoader.loadFactoryNames(type, classLoader));
       //将names的对象实例化
       List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
             classLoader, args, names);
       AnnotationAwareOrderComparator.sort(instances);
       return instances;
    }
    

    根据入参type类型ApplicationContextInitializer.class从类路径的META-INF处读取相应配置文件spring.factories并实例化对应Initializer。上面这2个函数后面会反复用到。

    org.springframework.context.ApplicationContextInitializer=
    org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,
    org.springframework.boot.context.ContextIdApplicationContextInitializer,
    org.springframework.boot.context.config.DelegatingApplicationContextInitializer,
    org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
    

    3.设置监听器

    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    

    和设置初始化器一个套路,通过getSpringFactoriesInstances函数实例化监听器。

    org.springframework.context.ApplicationListener=
    org.springframework.boot.ClearCachesApplicationListener,
    org.springframework.boot.builder.ParentContextCloserApplicationListener,
    org.springframework.boot.context.FileEncodingApplicationListener,
    org.springframework.boot.context.config.AnsiOutputApplicationListener,
    org.springframework.boot.context.config.ConfigFileApplicationListener,
    org.springframework.boot.context.config.DelegatingApplicationListener,
    org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,
    org.springframework.boot.context.logging.LoggingApplicationListener,
    org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
    

    3.3.run(String... args)解析

    1.run函数

    /**
    * Run the Spring application, creating and refreshing a new ApplicationContext
    */
    
    public ConfigurableApplicationContext run(String... args) {
       //计时器
       StopWatch stopWatch = new StopWatch();
       stopWatch.start();
    
       ConfigurableApplicationContext context = null;
       Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    
       //设置java.awt.headless系统属性为true,Headless模式是系统的一种配置模式。
       // 在该模式下,系统缺少了显示设备、键盘或鼠标。但是服务器生成的数据需要提供给显示设备等使用。
       // 因此使用headless模式,一般是在程序开始激活headless模式,告诉程序,现在你要工作在Headless  	  mode下,依靠系统的计算能力模拟出这些特性来
       configureHeadlessProperty();
    
       //获取监听器集合对象
       SpringApplicationRunListeners listeners = getRunListeners(args);
    
       //发出开始执行的事件。
       listeners.starting();
    
       try {
          //根据main函数传入的参数,创建DefaultApplicationArguments对象
          ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
          //根据扫描到的监听器对象和函数传入参数,进行环境准备。
          ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
    
          configureIgnoreBeanInfo(environment);
          Banner printedBanner = printBanner(environment);
    
          context = createApplicationContext();
    
          //和上面套路一样,读取spring.factories文件key SpringBootExceptionReporter对应的value
          exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
    
          prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);
    
          //和上面的一样,context准备完成之后,将触发SpringApplicationRunListener的contextPrepared执行
          refreshContext(context);
    
          //其实啥也没干。但是老版本的callRunners好像是在这里执行的。
          afterRefresh(context, applicationArguments);
    
          stopWatch.stop();
          if (this.logStartupInfo) {
             new StartupInfoLogger(this.mainApplicationClass)
                   .logStarted(getApplicationLog(), stopWatch);
          }
          //发布ApplicationStartedEvent事件,发出结束执行的事件
          listeners.started(context);
          //在某些情况下,我们希望在容器bean加载完成后执行一些操作,会实现ApplicationRunner或者CommandLineRunner接口
          //后置操作,就是在容器完成刷新后,依次调用注册的Runners,还可以通过@Order注解设置各runner的执行顺序。
          callRunners(context, applicationArguments);
       }
       catch (Throwable ex) {
          handleRunFailure(context, ex, exceptionReporters, listeners);
          throw new IllegalStateException(ex);
       }
    
       try {
          listeners.running(context);
       }
       catch (Throwable ex) {
          handleRunFailure(context, ex, exceptionReporters, null);
          throw new IllegalStateException(ex);
       }
       return context;
    }
    

    2.获取run listeners

    SpringApplicationRunListeners listeners = getRunListeners(args);
    

    和构造器设置初始化器一个套路,根据传入type SpringApplicationRunListener去扫描spring.factories文件,读取type对应的value并实例化。然后利用实例化对象创建SpringApplicationRunListeners对象。

    org.springframework.boot.SpringApplicationRunListener=
    org.springframework.boot.context.event.EventPublishingRunListener
    

    EventPublishingRunListener的作用是发布SpringApplicationEvent事件。

    EventPublishingRunListener更像是被监听对象,这个命名让我有点迷。

    public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
    	......
       @Override
       public void starting() {
          this.initialMulticaster.multicastEvent(
                new ApplicationStartingEvent(this.application, this.args));
       }
    
       @Override
       public void environmentPrepared(ConfigurableEnvironment environment) {
          this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
                this.application, this.args, environment));
       }
    	........
        
    }
    

    3.发出开始执行的事件

    listeners.starting();
    

    继续跟进starting函数,

    public void starting() {
       this.initialMulticaster.multicastEvent(
             new ApplicationStartingEvent(this.application, this.args));
    }
    //获取ApplicationStartingEvent类型的事件后,发布事件
    	@Override
    	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    		for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
    			Executor executor = getTaskExecutor();
    			if (executor != null) {
    				executor.execute(() -> invokeListener(listener, event));
    			}
    			else {
    				invokeListener(listener, event);
    			}
    		}
    	}
    //继续跟进invokeListener方法,最后调用ApplicationListener监听者的onApplicationEvent处理事件
    	private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
    		try {
    			listener.onApplicationEvent(event);
    		}
    		catch (ClassCastException ex) {
    			.....
    		}
    	}
    
    

    这个后面也会反复遇到,比如listeners.running(context)。

    这里是典型的观察者模式。

    //观察者:监听<E extends ApplicationEvent>类型事件
    ApplicationListener<E extends ApplicationEvent> extends EventListener
    
    //事件类型:
    Event extends SpringApplicationEvent  extends ApplicationEvent extends EventObject
    
    //被观察者:发布事件
    EventPublishingRunListener implements SpringApplicationRunListener
    

    SpringApplication根据当前事件Event类型,比如ApplicationStartingEvent,查找到监听ApplicationStartingEvent的观察者EventPublishingRunListener,调用观察者的onApplicationEvent处理事件。

    4.环境准备

    //根据main函数传入的参数,创建DefaultApplicationArguments对象
    ApplicationArguments applicationArguments = new DefaultApplicationArguments(
          args);
    //根据扫描到的listeners对象和函数传入参数,进行环境准备。
    ConfigurableEnvironment environment = prepareEnvironment(listeners,
          applicationArguments);
    

    ApplicationArguments提供运行application的参数,后面会作为一个Bean注入到容器。这里重点说下prepareEnvironment方法做了些什么。

    private ConfigurableEnvironment prepareEnvironment(
          SpringApplicationRunListeners listeners,
          ApplicationArguments applicationArguments) {
    
       // Create and configure the environment
       ConfigurableEnvironment environment = getOrCreateEnvironment();
    
       configureEnvironment(environment, applicationArguments.getSourceArgs());
    
        //和listeners.starting一样的流程
       listeners.environmentPrepared(environment);
    
       //上述完成了环境的创建和配置,传入的参数和资源加载到environment
    
       //绑定环境到SpringApplication
       bindToSpringApplication(environment);
       if (!this.isCustomEnvironment) {
          environment = new EnvironmentConverter(getClassLoader())
                .convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
       }
       ConfigurationPropertySources.attach(environment);
       return environment;
    }
    

    这段代码核心有3个。

    1. configureEnvironment,用于基本运行环境的配置。
    2. 发布事件ApplicationEnvironmentPreparedEvent。和发布ApplicationStartingEvent事件的流程一样。
    3. 绑定环境到SpringApplication

    5.创建ApplicationContext

    context = createApplicationContext();
    

    传说中的IOC容器终于来了。

    在实例化context之前,首先需要确定context的类型,这个是根据应用类型确定的。应用类型webApplicationType在构造器已经推断出来了。

    protected ConfigurableApplicationContext createApplicationContext() {
       Class<?> contextClass = this.applicationContextClass;
       if (contextClass == null) {
          try {
             switch (this.webApplicationType) {
             case SERVLET:
                //应用为servlet类型的web应用
                contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                break;
             case REACTIVE:
                //应用为响应式web应用
                contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                break;
             default:
                //应用为非web类型的应用
                contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
             }
          }
          catch (ClassNotFoundException ex) {
             throw new IllegalStateException(
                   "Unable create a default ApplicationContext, "
                         + "please specify an ApplicationContextClass",
                   ex);
          }
       }
       return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
    }
    

    获取context类型后,进行实例化,这里根据class类型获取无参构造器进行实例化。

    public static <T> T instantiateClass(Class<T> clazz) throws BeanInstantiationException {
       Assert.notNull(clazz, "Class must not be null");
       if (clazz.isInterface()) {
          throw new BeanInstantiationException(clazz, "Specified class is an interface");
       }
       try {
           //clazz.getDeclaredConstructor()获取无参的构造器,然后进行实例化
          return instantiateClass(clazz.getDeclaredConstructor());
       }
       catch (NoSuchMethodException ex) {
    	.......
    }
    

    比如web类型为servlet类型,就会实例化org.springframework.boot.web.servlet.context.

    AnnotationConfigServletWebServerApplicationContext类型的context。

    6.context前置处理阶段

    private void prepareContext(ConfigurableApplicationContext context,
          ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
          ApplicationArguments applicationArguments, Banner printedBanner) {
       //关联环境
       context.setEnvironment(environment);
    
       //ApplicationContext预处理,主要配置Bean生成器以及资源加载器
       postProcessApplicationContext(context);
        
       //调用初始化器,执行initialize方法,前面set的初始化器终于用上了
       applyInitializers(context);
       //发布contextPrepared事件,和发布starting事件一样,不多说
       listeners.contextPrepared(context);
       if (this.logStartupInfo) {
          logStartupInfo(context.getParent() == null);
          logStartupProfileInfo(context);
       }
    
       // Add boot specific singleton beans
       ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
       //bean, springApplicationArguments,用于获取启动application所需的参数
       beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
        
       //加载打印Banner的Bean
       if (printedBanner != null) {
          beanFactory.registerSingleton("springBootBanner", printedBanner);
       }
       
       if (beanFactory instanceof DefaultListableBeanFactory) {
          ((DefaultListableBeanFactory) beanFactory)
                .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
       }
       // Load the sources,根据primarySources加载resource。primarySources:一般为主类的class对象
       Set<Object> sources = getAllSources();
       Assert.notEmpty(sources, "Sources must not be empty");
       //构造BeanDefinitionLoader并完成定义的Bean的加载
       load(context, sources.toArray(new Object[0]));
       //发布ApplicationPreparedEvent事件,表示application已准备完成
       listeners.contextLoaded(context);
    }
    

    7.刷新容器

    private void refreshContext(ConfigurableApplicationContext context) {
       refresh(context);
       // 注册一个关闭容器时的钩子函数,在jvm关闭时调用
       if (this.registerShutdownHook) {
          try {
             context.registerShutdownHook();
          }
          catch (AccessControlException ex) {
             // Not allowed in some environments.
          }
       }
    }
    

    调用父类AbstractApplicationContext刷新容器的操作,具体的还没看。

    @Override
    public void refresh() throws BeansException, IllegalStateException {
       synchronized (this.startupShutdownMonitor) {
          // Prepare this context for refreshing.
          prepareRefresh();
    
          // Tell the subclass to refresh the internal bean factory.
          ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    
          // Prepare the bean factory for use in this context.
          prepareBeanFactory(beanFactory);
    
          try {
             // Allows post-processing of the bean factory in context subclasses.
             postProcessBeanFactory(beanFactory);
    
             // Invoke factory processors registered as beans in the context.
             invokeBeanFactoryPostProcessors(beanFactory);
    
             // Register bean processors that intercept bean creation.
             registerBeanPostProcessors(beanFactory);
    
             // Initialize message source for this context.
             initMessageSource();
    
             // Initialize event multicaster for this context.
             initApplicationEventMulticaster();
    
             // Initialize other special beans in specific context subclasses.
             onRefresh();
    
             // Check for listener beans and register them.
             registerListeners();
    
             // Instantiate all remaining (non-lazy-init) singletons.
             finishBeanFactoryInitialization(beanFactory);
    
             // Last step: publish corresponding event.
             finishRefresh();
          }
           ......
    }
    

    8.后置操作,调用Runners

    后置操作,就是在容器完成刷新后,依次调用注册的Runners,还可以通过@Order注解设置各runner的执行顺序。

    Runner可以通过实现ApplicationRunner或者CommandLineRunner接口。

    	private void callRunners(ApplicationContext context, ApplicationArguments args) {
    		List<Object> runners = new ArrayList<>();
    		runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    		runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    		AnnotationAwareOrderComparator.sort(runners);
    		for (Object runner : new LinkedHashSet<>(runners)) {
    			if (runner instanceof ApplicationRunner) {
    				callRunner((ApplicationRunner) runner, args);
    			}
    			if (runner instanceof CommandLineRunner) {
    				callRunner((CommandLineRunner) runner, args);
    			}
    		}
    	}
    

    根据源码可知,runners收集从容器获取的ApplicationRunner和CommandLineRunner类型的Bean,然后依次执行。

    9.发布ApplicationReadyEvent事件

    listeners.running(context);
    

    应用启动完成,可以对外提供服务了,在这里发布ApplicationReadyEvent事件。流程还是和starting时一样。

  • 相关阅读:
    javascript 详解数组
    javascript ES5 Object对象
    JavaScript的检测属性、属性特性、枚举属性
    javascript之值传递与引用传递
    数据分析--数据可视化
    Mysql基础知识
    Excel-数据透视表
    如何做一份好的分析报告?
    面对问题,如何去分析?(流失问题)
    面对问题,如何去分析?(日报问题)
  • 原文地址:https://www.cnblogs.com/ilovena/p/10161129.html
Copyright © 2020-2023  润新知