main run方法没用_Springboot之main函数详解

此前一直好奇,为什么Springboot的main函数只有简单的一行,它的资源加载,bean的加载和初始化究竟是怎么执行的?今天跟大家一块分析下源码,一探究竟。

如下图所示:

175c5e8f198765abb90b8676b961b2ca.png

图1 main函数

如图所示, 整个初始启动类中,只有一个@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、

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值