此前一直好奇,为什么Springboot的main函数只有简单的一行,它的资源加载,bean的加载和初始化究竟是怎么执行的?今天跟大家一块分析下源码,一探究竟。
如下图所示:
如图所示, 整个初始启动类中,只有一个@SpringBootApplication的注解,用于标识这个是系统的入口类。它其实是一个组合注解,包含了EnableAutoConfiguration(排除自动configuration注解类或者名字)和ComponentScan(基本包和基本类的扫描)。具体作用是扫描并注入@Configuration 和@Companent修饰的类,到Bean容器中。
main方法中的SpringApplication类用于从main方法中启动和加载Spring应用,它包括以下几个步骤去启动我们的应用程序:
1、创建一个应用上下文实例。这依赖于我们配置的classpath信息。
2、注册一个CommandLinePropertySource,以暴露命令行参数作为Spring的属性。
3、更新应用上下文,以单例的形式加载所有的bean.
4、触发CommandLineRunner的beans
SpringApplication 可以从各种不同来源加载bean,不过它通常推荐一@Configuration注解的方式去配置一个单例的bean。除此之外,还可以通过设置getSources 方法的方式从:
1、通过AnnotatedBeanDefinitionReader加载完全限定类名。
2、通过XmlBeanDefinitionReader bean加载器加载制定未知的xml源类信息,或者GroovyBeanDefinitionReader类加载器加载groovy脚本中的类信息。
3、通过ClassPathBeanDefinitionScanner 类扫描器扫描制定包下的类。
在该类下,还包含一些比较重要的静态属性,如:DEFAULT_CONTEXT_CLASS 默认的上下文类路径,以及 DEFAULT_SERVLET_WEB_CONTEXT_CLASS默认的servlet web类路径等。run方法是它的一个静态方法,用于实际执行spring的初始化和启动任务。它最终调用的是如下方法:
/** * Static helper that can be used to run a {@link SpringApplication} from the * specified sources using default settings and user supplied arguments. * @param primarySources the primary sources to load * @param args the application arguments (usually passed from a Java main method) * @return the running {@link ApplicationContext} */public static ConfigurableApplicationContext run(Class>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args);}而SpringApplication的初始化函数实际上执行了一些配置文件的加载、bean的加载和监听器的配置操作。如下所示:public SpringApplication(ResourceLoader resourceLoader, Class>... primarySources) { this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); this.webApplicationType = WebApplicationType.deduceFromClasspath(); setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass();}
在这段代码中,我们重点关注deduceFromClasspath方法和 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class))。
1)deduceFromClasspath的作用是根据classpath信息推断当前的web类型。目前springboot的类型一共分为三种: None, Servlet和Reactive(即webflux)。默认是servlet.
2)getSpringFactoriesInstances()方法的作用是调用 List loadFactoryNames(Class> factoryClass, @Nullable ClassLoader classLoader) 方法,根据工厂类名信息和类加载器,将bean容器(实质上是一个ConcurrentReferrencedMap)中的类信息,获取并存放在一个set集合中,如果没有获取
到,则会重新加载一次类路径下的所有的bean信息。setListeners()方法,则将容器中加载的所有bean设置为监听的对象。
3)deduceMainApplicationClass() 方法,则根据我们运行时的栈信息,推断我们的main方法所在类信息。
再回到之前的new SpringApplication(primarySources).run(args)方法,它是我们整个程序的主流程和方法,它的主要作用是创建我们的spring 应用并更新spring容器。 代码详情如下:
public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection exceptionReporters = new ArrayList<>(); configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } listeners.started(context); 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;}
其中,StopWatch 的作用是监听每个任务执行的时间信息。
prepareEnvironment(listeners,applicationArguments)的作用是根据当前程序的web类型,加载相应的环境信息。如servlet 或者reactive。
prepareContext(context, environment, listeners, applicationArguments,printedBanner) 的作用是初始化容器和上下文环境,然后将其添加到待监听列表。
refreshContext(context); 它的作用是更新spring的上下文信息,并添加shutdown钩子。
afterRefresh(context, applicationArguments);一个没有任何实现的 protected方法,可以添加一些我们自定义的实现。
至此,springboot 的main方法的执行过程已经全部讲完了,由于水平有限,有些地方难免有些纰漏和粗糙,不足之处,希望大家多多指教。
1、