交流
商城
MCN
登入
注册
首页
提问
分享
讨论
建议
公告
动态
发表新帖
发表新帖
第1 章:SpringApplication springboot的启动
分享
未结
0
911
李延
LV6
2021-05-19
悬赏:20积分
# 说明 本章内容主要讲解springboot的主函数为入口,讲解springboot的启动过程。 # 入口 ```java @SpringBootApplication public class Main { public static void main(String[] args) { final ConfigurableApplicationContext context = SpringApplication.run(Main.class, args); } } ``` 在springboot项目中。我们通过静态方法SpringApplication.run方法启动springboot。同时需要在启动类上添加注解SpringBootApplication。 其中run方法需要2个参数: 1. class类型,一般为我们启动类的class。代表我们springboot的加载源,一般springboot会扫描指定类下面的class文件。 2. 第二个参数是:变长参数,是我们需要传给spring的参数。 # 代码详细说明 进入run方法查看 ```java public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) { return run(new Class<?>[] { primarySource }, args); } public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); } ``` 我们看到先是将class分装为数组格式,调用重载的方法。最终创建SpringApplication对象并调用对象内的run方法。下面我们先解析SpringApplication的构造函数,之后再对run方法解析。 ## 构造函数 ```java public SpringApplication(Class<?>... primarySources) { this(null, primarySources); } 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(); this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class)); setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass(); } ``` 我们看到上面代码主要做了6件事情: 1. 将我们传入的class类型保持到成员变量primarySources中。 2. 创建webApplicationType对象。 3. 创建bootstrappers对象。 4. 获取initializers对象,并保存到成员变量中。 5. 获取listeners对象,并保存到成员变量中。 6. 创建mainApplicationClass对象。 下面我们将分别介绍这几步内容 ### webApplicationType 通过上面章节。我们看到通过WebApplicationType.deduceFromClasspath() 获取webApplicationType,我们跟进这部分代码: ```java private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext" }; private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet"; private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler"; private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer"; private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext"; private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext"; static WebApplicationType deduceFromClasspath() { if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null) && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) { return WebApplicationType.REACTIVE; } for (String className : SERVLET_INDICATOR_CLASSES) { if (!ClassUtils.isPresent(className, null)) { return WebApplicationType.NONE; } } return WebApplicationType.SERVLET; } ``` 我们看到通过判断是否包含指定的类,来确定应该启动不同spring。一共可分为3类:1、非web类型;2、反应式web类型;3、web类型。 ### initializers #### initializers作用 通过名字我们知道是一组初始化器。其接口代码如下: ```java @FunctionalInterface public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> { /** * Initialize the given application context. * @param applicationContext the application to configure */ void initialize(C applicationContext); } ``` 定义了唯一个方法initialize,参数为ConfigurableApplicationContext的子类。这个方法在springboot刚创建完ApplicationContext对象时被调用。允许我们对ApplicationContext做自己的配置。具体调用地方我们将在下面说明。 #### 创建 在构造函数中,我们看到如下代码: ```java setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); ``` 一方面通过getSpringFactoriesInstances 方法获取到initializers 对象,另一方面通过setInitializers方法设置setInitializers对象。 对于setInitializers方法,只是将获取到数据保存到成员变量中。我们将主要分析getSpringFactoriesInstances方法。 ```java 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(); // 通过SpringFactoriesLoader.loadFactoryNames 获取到我们需要的class名称 Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); //以反射的方式创建对象 List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); //排序 AnnotationAwareOrderComparator.sort(instances); return instances; } ``` 上面代码中一共分为3步。 1. 通过SpringFactoriesLoader.loadFactoryNames 获取到我们需要的class名称 2. 通过反射获取对象 3. 对数组进行排序 其中第二步与第三步比较简单,不做详细说明。我们主要看SpringFactoriesLoader.loadFactoryNames 方法 SpringFactoriesLoader.loadFactoryNames ```java public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { ClassLoader classLoaderToUse = classLoader; if (classLoaderToUse == null) { classLoaderToUse = SpringFactoriesLoader.class.getClassLoader(); } String factoryTypeName = factoryType.getName(); return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList()); } private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) { //先读取缓存 Map<String, List<String>> result = cache.get(classLoader); if (result != null) { return result; } result = new HashMap<>(); try { //获取所有的META-INF/spring.factories文件 Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION); while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); //解析键值对 Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { String factoryTypeName = ((String) entry.getKey()).trim(); String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String) entry.getValue()); for (String factoryImplementationName : factoryImplementationNames) { result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>()) .add(factoryImplementationName.trim()); } } } // Replace all lists with unmodifiable lists containing unique elements result.replaceAll((factoryType, implementations) -> implementations.stream().distinct() .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList))); cache.put(classLoader, result); } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } return result; } ``` 我们看到spring是加载了所有的META-INF/spring.factories文件。而这个文件的内容如下:  key为某个类的全路径。值为这个子类的数组以 , 分割。 而我们调用时传参数是ApplicationContextInitializer.class。 也就是说我们最终获取到的是在META-INF/spring.factories文件中,key为ApplicationContextInitializer类的全路径的值。 ### 获取listeners对象 获取类型为ApplicationListener的监听器,在我们spring不同阶段,而在不同的阶段,会触发不同的事件,这些监听器就是监听这些事件的。 其中获取方式与initializers相同。 ### deduceMainApplicationClass 这个方法就是获取到具体的启动类,内容也很简单,获取当前线程的栈信息。当方法为main的就说明是启动类。 ```java private Class<?> deduceMainApplicationClass() { try { StackTraceElement[] stackTrace = new RuntimeException().getStackTrace(); for (StackTraceElement stackTraceElement : stackTrace) { if ("main".equals(stackTraceElement.getMethodName())) { return Class.forName(stackTraceElement.getClassName()); } } } catch (ClassNotFoundException ex) { // Swallow and continue } return null; } ``` ## run方法 上面说明了在构造函数的主要内容,下面详细说明run方法的具体内容。 ```java public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); DefaultBootstrapContext bootstrapContext = createBootstrapContext(); ConfigurableApplicationContext context = null; configureHeadlessProperty(); //创建监听器 SpringApplicationRunListeners listeners = getRunListeners(args); //发送starting事件 listeners.starting(bootstrapContext, this.mainApplicationClass); try { //封装args参数 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); //准备环境变量 ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments); //配置环境变量 configureIgnoreBeanInfo(environment); //打印日志 Banner printedBanner = printBanner(environment); //创建上下文 context = createApplicationContext(); //设置applicationStartup属性 context.setApplicationStartup(this.applicationStartup); //准备上下文 prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); //刷新上下文 refreshContext(context); //刷新完成 afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); } //发送started事件 listeners.started(context); callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, listeners); throw new IllegalStateException(ex); } try { //发生running事件 listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, null); throw new IllegalStateException(ex); } return context; } ``` 其实这个方法的逻辑很明白具体可分为以下几步: 1. 创建监听器 2. 发送starting事件 3. 封装args参数 4. 准备环境变量 5. 配置环境变量 6. 打印日志(不重要,忽略) 7. 创建上下文 8. 准备上下文 9. 刷新上下文 10. 刷新完成 11. 发送started事件 我们可以看到基本所有的步骤都是围绕environment 和context 这两个对象来进行的。对environment 和context 做一定的配置,并发送响应的事件。最后在一切准备完成后调用refreshContext。刷新上下文。 我们下面详细解析每一步的内容 ## createBootstrapContext ```java private DefaultBootstrapContext createBootstrapContext() { DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext(); this.bootstrappers.forEach((initializer) -> initializer.intitialize(bootstrapContext)); return bootstrapContext; } ``` 我们看到这个方法时直接创建了DefaultBootstrapContext 对象。 但对于具体作用暂时未知,后续补充。 ## getRunListeners ```java private SpringApplicationRunListeners getRunListeners(String[] args) { Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class }; return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args), this.applicationStartup); } ``` 我们看到SpringApplicationRunListeners对象是创建出来的,而SpringApplicationRunListener对象是通过我们上面介绍的方法通过META-INF/spring.factories加载。 到目前为止关于监听器我们一个使用了 3个不同的类:SpringApplicationRunListeners、SpringApplicationRunListener、ApplicationListener 对于这3个类我们在ApplicationListener文档中详细说明。 这里我们只需要知道listener。 就是在我们启动spring的不同阶段,发生响应的事件,让我们可以在合适的时候对spring进行干预。 ## listeners.starting 发生开始事件,这一步向相应的监听器发送starting事件。 主要有:log配置的初始化工作。 ## 封装args参数 ```java ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ``` 这一步中我们将参数进行分装,我们在日常使用中一般有:--xxx=xxx 这种格式的参数,对于这种键值对的参数解析也是在这个类中进行的,具体代码解析见ApplicationArguments文章。 ## prepareEnvironment 通过方法名称就可以知道是准备 环境Environment对象,对于Environment对象。我们在spring核心相关文章中对这个类进行了详细的说明。这里主要解析springboot是怎么创建和使用它的。 ```java private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) { // 创建或获取environment对象 ConfigurableEnvironment environment = getOrCreateEnvironment(); // 配置environment,主要包括将参数配置到evirronment中 configureEnvironment(environment, applicationArguments.getSourceArgs()); // 添加configurationProperties 空间。 ConfigurationPropertySources.attach(environment); //发生环境准备完成事件 listeners.environmentPrepared(bootstrapContext, environment); //将defaultProperties 空间后移动到最后一个 DefaultPropertiesPropertySource.moveToEnd(environment); //设置additionalProfiles configureAdditionalProfiles(environment); bindToSpringApplication(environment); if (!this.isCustomEnvironment) { environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass()); } ConfigurationPropertySources.attach(environment); return environment; } ``` ### getOrCreateEnvironment getOrCreateEnvironment 我们可以直接通过名称就知道创建或者获取environment对象。 内容也很简单,根据不同的webApplicationType类型创建响应的environment对象。 ```java private ConfigurableEnvironment getOrCreateEnvironment() { if (this.environment != null) { return this.environment; } switch (this.webApplicationType) { case SERVLET: return new StandardServletEnvironment(); case REACTIVE: return new StandardReactiveWebEnvironment(); default: return new StandardEnvironment(); } } ``` ### configureEnvironment 下面继续解析configureEnvironment ```java protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) { if (this.addConversionService) { ConversionService conversionService = ApplicationConversionService.getSharedInstance(); environment.setConversionService((ConfigurableConversionService) conversionService); } configurePropertySources(environment, args); configureProfiles(environment, args); } protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) { MutablePropertySources sources = environment.getPropertySources(); DefaultPropertiesPropertySource.ifNotEmpty(this.defaultProperties, sources::addLast); if (this.addCommandLineProperties && args.length > 0) { String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME; if (sources.contains(name)) { PropertySource<?> source = sources.get(name); CompositePropertySource composite = new CompositePropertySource(name); composite.addPropertySource( new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args)); composite.addPropertySource(source); sources.replace(name, composite); } else { sources.addFirst(new SimpleCommandLinePropertySource(args)); } } } protected void configureProfiles(ConfigurableEnvironment environment, String[] args) { } ``` 通过方法名字我们知道是对于environment的配置。首先就是设置了conversionService对象,对于conversionService对象。我们在spring核心篇中进行了解析。而这里调用的是ApplicationConversionService.getSharedInstance()方法,返回的是一个单例对象。 下面是调用了configurePropertySources方法,将我们传入的参数封装为SimpleCommandLinePropertySource对象。加入到了environment对象中。 最后是预留一个空方法configureProfiles。交给子类扩展。 ### ConfigurationPropertySources.attach 在这一步代码如下: ```java public static void attach(Environment environment) { Assert.isInstanceOf(ConfigurableEnvironment.class, environment); MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources(); PropertySource<?> attached = sources.get(ATTACHED_PROPERTY_SOURCE_NAME); if (attached != null && attached.getSource() != sources) { sources.remove(ATTACHED_PROPERTY_SOURCE_NAME); attached = null; } if (attached == null) { sources.addFirst(new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME, new SpringConfigurationPropertySources(sources))); } } ``` 不是特别懂为什么加configurationProperties。 后面遇到使用的地方再解析。 ### listeners.environmentPrepared 这一步中发送了environmentPrepared事件。在这个事件中,主要是对于配置文件的解析。通过debug看到:   在发送事件前,propertySourceList对象只有3个值。在发送后:   propertySourceList的值变为了 5个,而且明显最后一个就是加载的application.properties文件 我们知道可以通过--spring.profiles.active可以指定环境,加载对应的文件。这块逻辑也是在这里进行的具体为: 我们在开始指定spring.profiles.active  下面是debug情况   我们看到activeProfiles 多了一个dev环境。 并且在propertySourceList中,也多了application-dev.properties文件的加载。 同时我们需要注意的是不管是否指定环境,application.properties文件都会加载,并且权重最低。 关于这部分源码解析我们会在ApplicationListener的文章中说明 ### configureAdditionalProfiles 这一步将additionalProfiles属性指定的环境配置到environment的ActiveProfiles中。 与spring.profiles.active 效果相同 ```java private void configureAdditionalProfiles(ConfigurableEnvironment environment) { if (!CollectionUtils.isEmpty(this.additionalProfiles)) { Set<String> profiles = new LinkedHashSet<>(Arrays.asList(environment.getActiveProfiles())); if (!profiles.containsAll(this.additionalProfiles)) { profiles.addAll(this.additionalProfiles); environment.setActiveProfiles(StringUtils.toStringArray(profiles)); } } } ``` ### prepareEnvironment总结 通过上面分析。我们知道:首先是创建environment对象,并进行了一定的配置,主要包括将传入参数加载到environment,和设置conversionService对象。并且最后通过environmentPrepared事件,完成都配置文件的解析和加载。 ## configureIgnoreBeanInfo ```java private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) { if (System.getProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) { Boolean ignore = environment.getProperty("spring.beaninfo.ignore", Boolean.class, Boolean.TRUE); System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME, ignore.toString()); } } ``` 设置了spring.beaninfo.ignore 属性,但不清楚使用地方,遇到再跟新。 ## createApplicationContext ```java protected ConfigurableApplicationContext createApplicationContext() { return this.applicationContextFactory.create(this.webApplicationType); } ApplicationContextFactory DEFAULT = (webApplicationType) -> { try { switch (webApplicationType) { case SERVLET: return new AnnotationConfigServletWebServerApplicationContext(); case REACTIVE: return new AnnotationConfigReactiveWebServerApplicationContext(); default: return new AnnotationConfigApplicationContext(); } } catch (Exception ex) { throw new IllegalStateException("Unable create a default ApplicationContext instance, " + "you may need a custom ApplicationContextFactory", ex); } }; ``` 我们看到spring根据不同的webApplicationType创建上下文。我们这里是普通项目创建的是AnnotationConfigApplicationContext ## prepareContext 准备上下文: ```java private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { //向上下文中设置environment对象 context.setEnvironment(environment); //后置处理ApplicationContext。也是向上下文中设置相关属性 postProcessApplicationContext(context); //调用Initializers方法 applyInitializers(context); //发送contextPrepared事件 listeners.contextPrepared(context); bootstrapContext.close(context); if (this.logStartupInfo) { logStartupInfo(context.getParent() == null); logStartupProfileInfo(context); } //注册单利对象 ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); beanFactory.registerSingleton("springApplicationArguments", applicationArguments); if (printedBanner != null) { beanFactory.registerSingleton("springBootBanner", printedBanner); } if (beanFactory instanceof DefaultListableBeanFactory) { ((DefaultListableBeanFactory) beanFactory) .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); } if (this.lazyInitialization) { context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor()); } // Load the sources Set<Object> sources = getAllSources(); Assert.notEmpty(sources, "Sources must not be empty"); //加载sources(就是我们参数传进来的class对象) load(context, sources.toArray(new Object[0])); //发送contextLoaded listeners.contextLoaded(context); } ``` 这里主要是对上下文进行了一些属性的设置,具体设置的值。我们在使用时进行说明。 同时也触发了2个事件contextPrepared和contextLoaded。对于我们整体的解析没有影响,具体在响应的监听器类的文章中解析。 最后就剩下load 方法,参数为context,与我们最先传入的class对象。我们都知道springboot会自动扫描我们run方法传入的class对象下,所有包内容。而这一步就是将指定的class注册到BeanDefinitionRegistry中 ## refreshContext ```java private void refreshContext(ConfigurableApplicationContext context) { if (this.registerShutdownHook) { try { context.registerShutdownHook(); } catch (AccessControlException ex) { // Not allowed in some environments. } } refresh((ApplicationContext) context); } @Deprecated protected void refresh(ApplicationContext applicationContext) { Assert.isInstanceOf(ConfigurableApplicationContext.class, applicationContext); refresh((ConfigurableApplicationContext) applicationContext); } /** * Refresh the underlying {@link ApplicationContext}. * @param applicationContext the application context to refresh */ protected void refresh(ConfigurableApplicationContext applicationContext) { applicationContext.refresh(); } ``` 刷新上下文就是调用context的refresh 方法。所以的bean 就是在这一步进行加载的。具体我们在响应的文章中说明 # 总结 在springboot的启动过程中。整体过程就是对于environment和context对象的配置,最后通过context的refresh方法 刷新所有的bean。 在整个过程中,通过listener发送不同事件去最响应的配置。 所以我们下面主要的关注点将在listener与refresh中。
回帖
消灭零回复
提交回复
热议榜
java 相关知识分享
8
好的程序员与不好的程序员
6
写给工程师的十条精进原则
5
spring boot以jar包运行配置的logback日志文件没生成
5
一步一步分析SpringBoot启动源码(一)
5
MockMvc测试
5
【吐槽向】是不是有个吐槽的板块比较好玩
4
logstash jdbc同步mysql多表数据到elasticsearch
3
IntelliJ IDEA 优质License Server
3
.gitignore忽略规则
3
SpringBoot启动源码分析
3
一步一步分析SpringBoot启动源码(三)
3
2
一步一步分析SpringBoot启动源码(二)
2
积分不够将无法发表新帖
2
官方产品
Meta-Boot - 基于MCN
MCN - 快速构建SpringBoot应用
微信扫码关注公众号