1.简述
SpringBoot因为内置了tomcat或jetty服务器,不需要直接部署War文件,所以SpringBoot的程序起点是一个普通的主函数。
主函数如下:
@SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
SpringBoot的启动过程都是通过@SpringBootApplication注解和SpringApplication.run方法来实现的。
启动的过程可以概括为:
- 通过SpringFactoriesLoader加载META-INF/spring.factories文件,获取并创建SpringApplicationRunListener对象。
- 然后由SpringApplicationRunListener 来发出starting消息。
- 创建参数,并配置当前SpringBoot 应用将要使用的Environment。
- 完成之后,依然由SpringApplicationRunListener来发出environmentPrepared 消息。
- 创建ApplicationContext。
- 初始化ApplicationContext,并设置Environment,加载相关配置等。
- 由SpringApplicationRunListener来发出contextPrepared消息,告知SpringBoot应用使用的ApplicationContext已准备OK。
- 将各种beans装载入ApplicationContext,继续由SpringApplicationRunListener来发出contextLoaded消息,告知SpringBoot应用使用的ApplicationContext已装填OK。
- refresh ApplicationContext,完成IoC容器可用的最后一步
- 由SpringApplicationRunListener来发出started消息。
- 完成最终的程序的启动。
- 由SpringApplicationRunListener来发出running消息,告知程序已运行起来了。
2.源码分析
(1)@SpringBootApplication注解
@SpringBootApplication注解的源码如下:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { //这两个排除过滤器TypeExcludeFilter和AutoConfigurationExcludeFilter不知道用来干什么的 @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { //等同于EnableAutoConfiguration注解的exclude属性 @AliasFor(annotation = EnableAutoConfiguration.class) Class<?>[] exclude() default {}; //等同于EnableAutoConfiguration注解的excludeName属性 @AliasFor(annotation = EnableAutoConfiguration.class) String[] excludeName() default {}; //等同于ComponentScan注解的basePackages属性 @AliasFor(annotation = ComponentScan.class, attribute = "basePackages") String[] scanBasePackages() default {}; //等同于ComponentScan注解的basePackageClasses属性 @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses") Class<?>[] scanBasePackageClasses() default {}; }
可以看到@SpringBootApplication注解实际上是SpringBoot提供的一个复合注解,由以下三个注解组成:
- @SpringBootConfiguration:来源于 @Configuration,二者功能都是将当前类标注为配置类,并将当前类里以@Bean 注解标记的方法的实例注入到srping容器中。
- @EnableAutoConfiguration:启用自动配置其可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前 IoC 容器之中。
- @ComponentScan:对应于XML配置形式中的 context:component-scan,用于将一些标注了特定注解的bean定义批量采集注册到Spring的IoC容器之中,这些特定的注解大致包括:@Controller @Entity @Component @Service @Repository。
因此@SpringBootApplication注解主要作为一个配置类,能够触发包扫描和自动配置的逻辑,从而使得SpringBoot的相关bean被注册进Spring容器。
(2)创建SpringApplication对象
SpringApplication类的run方法,这个方法就做了2件事:一是创建SpringApplication对象,二是启动SpringApplication。
SpringApplication构造器源码如下:
public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) { this.sources = new LinkedHashSet(); this.bannerMode = Mode.CONSOLE; this.logStartupInfo = true; this.addCommandLineProperties = true; this.addConversionService = true; this.headless = true; this.registerShutdownHook = true; this.additionalProfiles = Collections.emptySet(); this.isCustomEnvironment = false; this.lazyInitialization = false; this.applicationContextFactory = ApplicationContextFactory.DEFAULT; this.applicationStartup = ApplicationStartup.DEFAULT; this.resourceLoader = resourceLoader; //断言primarySources不能为null,如果为null,抛出异常提示 Assert.notNull(primarySources, "PrimarySources must not be null"); //启动类传入的Class this.primarySources = new LinkedHashSet(Arrays.asList(primarySources)); this.webApplicationType = WebApplicationType.deduceFromClasspath(); //判断当前项目类型,有三种:NONE、SERVLET、REACTIVE this.bootstrappers = new ArrayList(this.getSpringFactoriesInstances(Bootstrapper.class)); //设置ApplicationContextInitializer this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class)); //设置监听器 this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class)); //判断主类,初始化入口类 this.mainApplicationClass = this.deduceMainApplicationClass(); } //判断主类 private Class<?> deduceMainApplicationClass() { try { StackTraceElement[] stackTrace = (new RuntimeException()).getStackTrace(); StackTraceElement[] var2 = stackTrace; int var3 = stackTrace.length; for(int var4 = 0; var4 < var3; ++var4) { StackTraceElement stackTraceElement = var2[var4]; if ("main".equals(stackTraceElement.getMethodName())) { return Class.forName(stackTraceElement.getClassName()); } } } catch (ClassNotFoundException var6) { } return null; }
在构造器里主要就做了2件事,1是设置初始化器,2是设置监听器。
设置初始化器的源码如下:
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) { return this.getSpringFactoriesInstances(type, new Class[0]); } private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = this.getClassLoader(); //从类路径的META-INF处读取相应配置文件spring.factories,然后进行遍历,读取配置文件中Key(type)对应的value Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); //将names的对象实例化 List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; }
ApplicationContextInitializer.class从类路径的META-INF处读取相应配置文件spring.factories并实例化对应Initializer。
设置监听器:和设置初始化器一个样,都是通过getSpringFactoriesInstances函数实例化监听器。
创建了SpringApplication实例之后,就完成了SpringApplication类的初始化工作。
(3)run方法
得到SpringApplication实例后,接下来就调用实例方法run()。
run方法源码如下:
public ConfigurableApplicationContext run(String... args) { //创建计时器 StopWatch stopWatch = new StopWatch(); //开始计时 stopWatch.start(); DefaultBootstrapContext bootstrapContext = this.createBootstrapContext(); //定义上下文对象 ConfigurableApplicationContext context = null; //Headless模式设置 this.configureHeadlessProperty(); //加载SpringApplicationRunListeners监听器 SpringApplicationRunListeners listeners = this.getRunListeners(args); //发送ApplicationStartingEvent事件 listeners.starting(bootstrapContext, this.mainApplicationClass); try { //封装ApplicationArguments对象 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); //配置环境模块 ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments); //根据环境信息配置要忽略的bean信息 this.configureIgnoreBeanInfo(environment); //打印Banner标志 Banner printedBanner = this.printBanner(environment); //创建ApplicationContext应用上下文 context = this.createApplicationContext(); context.setApplicationStartup(this.applicationStartup); //ApplicationContext基本属性配置 this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); //刷新上下文 this.refreshContext(context); //刷新后的操作,由子类去扩展 this.afterRefresh(context, applicationArguments); //计时结束 stopWatch.stop(); //打印日志 if (this.logStartupInfo) { (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch); } //发送ApplicationStartedEvent事件,标志spring容器已经刷新,此时所有的bean实例都已经加载完毕 listeners.started(context); //查找容器中注册有CommandLineRunner或者ApplicationRunner的bean,遍历并执行run方法 this.callRunners(context, applicationArguments); } catch (Throwable var10) { //发送ApplicationFailedEvent事件,标志SpringBoot启动失败 this.handleRunFailure(context, var10, listeners); throw new IllegalStateException(var10); } try { //发送ApplicationReadyEvent事件,标志SpringApplication已经正在运行,即已经成功启动,可以接收服务请求。 listeners.running(context); return context; } catch (Throwable var9) { //报告异常,但是不发送任何事件 this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null); throw new IllegalStateException(var9); } }
执行完run方法后,SpringBoot就启动完成了。
3.总结
启动类看起来就一个@SpringBootApplication注解,一个run()方法。其实是经过高度封装后的。从这个分析中学到很多东西。例如使用了spring.factories文件来完成自动配置,提高了扩展性。在启动时使用观察者模式,以事件发布的形式通知,降低耦合,易于扩展等等。