SpringBoot到Spring源码分析之Environment环境装配

说明:本章在之前章节《SpringBoot 启动流程源码分析》的基础上进行继续源码分析。

前面我们分析到SpringApplication类的run方法,这个方法主要在顶层设计上定义了SpringBoot项目的整个启动过程,同时包括了Spring容器的启动过程。本章继前面的基础上继续分析Spring的Environment环境装配,了解Spring环境装配后可以帮助我们更好地进行后续的Spring核心源码分析。

 

SpringApplication的run方法相关源码:

public ConfigurableApplicationContext run(String... args) {


...省略其它


     try {


      //封装main方法参数
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);


      //根据前面判断的webApplicationType创建环境
      //配置main方法参数到环境信息中,如果有的话
      //回调通知环境创建事件监听器
      //绑定spring.main开头的配置到SpringApplication
      //根据配置推到是否需要转换环境对象
      ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);


...省略其它
     
}

new DefaultApplicationArguments源码分析:

在分析命令行参数封装前,我们先了解下Spring框架对不同环境参数的顶层抽象类PropertySource设计。下面是关键源码:

public abstract class PropertySource<T> {


   protected final String name;


   protected final T source;




   public PropertySource(String name, T source) {
      Assert.hasText(name, "Property source name must contain at least one character");
      Assert.notNull(source, "Property source must not be null");
      this.name = name;
      this.source = source;
   }


   @Override
   public boolean equals(@Nullable Object other) {
      return (this == other || (other instanceof PropertySource &&
            ObjectUtils.nullSafeEquals(this.name, ((PropertySource<?>) other).name)));
   }


 
   @Override
   public int hashCode() {
      return ObjectUtils.nullSafeHashCode(this.name);
   }


...省略其它
}

Spring框架需要处理来自各种环境的配置参数信息,比如System Property(系统属性)、Properties Property(配置文件属性)、 CommandLine Property(命令行属性)、Servlet Property(Servlet属性)等。这个带泛型参数接口的作用就是为不同环境参数定义的,其中name属性就是环境的key,source就是对应环境的具体参数信息对象。注意这个抽象类实现了equals和hashCode方法,关键属性是name,当保存到集合中时,就会以name作为主元素进行匹配和查找,这点后面就可以看到,这可能就是选择定义为抽象类而不是接口的原因之一吧。

下面是new DefaultApplicationArguments关键源码:

 

public class DefaultApplicationArguments implements ApplicationArguments {


//封装成Spring环境参数对象,下面有Source类结构图说明
   private final Source source;
  //原始参数信息
   private final String[] args;


   public DefaultApplicationArguments(String... args) {
      Assert.notNull(args, "Args must not be null");
//封装成Spring环境参数对象
      this.source = new Source(args);
      this.args = args;
   }


private static class Source extends SimpleCommandLinePropertySource {


       Source(String[] args) {
          super(args);
       }


    ...省略其它
    }




...省略其它
}




public SimpleCommandLinePropertySource(String... args) {
   super(new SimpleCommandLineArgsParser().parse(args));
}


public abstract class CommandLinePropertySource<T> extends EnumerablePropertySource<T> {


public static final String COMMAND_LINE_PROPERTY_SOURCE_NAME = "commandLineArgs";


public CommandLinePropertySource(T source) {
        super(COMMAND_LINE_PROPERTY_SOURCE_NAME, source);
    }




}


public abstract class EnumerablePropertySource<T> extends PropertySource<T> {


   public EnumerablePropertySource(String name, T source) {
//最终调的就是PropertySource接口构造器
      super(name, source);
   }


...省略其它
}


class SimpleCommandLineArgsParser {


 
   public CommandLineArgs parse(String... args) {
      CommandLineArgs commandLineArgs = new CommandLineArgs();
      for (String arg : args) {
        //通过--符号区分选项类型还是非选项类型
        //将不同类型参数保存在CommandLineArgs类的不同属性Map中
        //其中CommandLineArgs类是PropertySource接口实现类的
        //实际泛型参数对象,也就是说它最终被保存在PropertySource
        //接口的T source属性中
         if (arg.startsWith("--")) {
            String optionText = arg.substring(2, arg.length());
            String optionName;
            String optionValue = null;
            if (optionText.contains("=")) {
               optionName = optionText.substring(0, optionText.indexOf('='));
               optionValue = optionText.substring(optionText.indexOf('=')+1, optionText.length());
            }
            else {
               optionName = optionText;
            }
            if (optionName.isEmpty() || (optionValue != null && optionValue.isEmpty())) {
               throw new IllegalArgumentException("Invalid argument syntax: " + arg);
            }
             //选项类型参数
            commandLineArgs.addOptionArg(optionName, optionValue);
         }
         else {
             //非选项类型参数
            commandLineArgs.addNonOptionArg(arg);
         }
      }
      return commandLineArgs;
   }


}

其中Source类实现的顶层接口就是PropertySource。下面是Source类结构图:

 

从类结构图可以很容易看出它是命令行相关的实现。至此,我们就又多认识了一个Spring的基础设计PropertySource,以后源码分析过程中凡是遇到实现这个接口时就要留点心了,可能就是准备要处理某类环境的配置信息。比如Environment接口实现类对象就是组合了PropertySource类型集合,当从Environment对象获取属性(getProperty(key))时实际就是遍历PropertySource集合进行检索的,不同PropertySource实现类的getProperty有不同的实现逻辑。除此之外还可以进行同一个参数不同读取优先级的处理。

这里多提醒一下,因为我们后面会看到Spring任何工作类都是继承很多个抽象类以及实现很多个接口,关于抽象类和接口设计思想在学习Java基础时可能并没有太多讲解,这里给出一个参考原则:接口的作用主要是对某个功能或模块进行规范和边界的定义,而抽象类的作用是抽取一个或多个功能接口通用部分,将不通用的部分通过定义抽象方法留给不同子类自己去实现定制化逻辑。充分理解这条原则可以帮助你在阅读源码过程有的放矢。

 

prepareEnvironment源码分析:

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
                                       ApplicationArguments applicationArguments) {


   //根据前面的webApplicationType决定实例化哪个环境实现类
   //这里实例化的是StandardServletEnvironment,实例化过程调用抽象父类AbstractEnvironment构造器
   //构造器中调用了customizePropertySources方法,该方法由子类实现,在这个方法中主要初始化
   //Servlet上下文属性(servletContextInitParams)、Servlet配置(servletConfigInitParams)属性、
   //系统环境变量(systemEnvironment)、JVM系统变量(systemProperties)按顺序添加到List中。
   //每一类都是通过PropertySource子类StubPropertySource封装后保存到MutablePropertySources propertySources属性中
   //StubPropertySource是Property的子类,源码注释中说明StubPropertySource类是个占位符类,因为在一开始不能真正
   //完成所有配置的初始,但是配置信息是有优先级的,所以先用存根来充当一个占位符定义好默认顺序,等容器refresh的时候再进行替换
   ConfigurableEnvironment environment = getOrCreateEnvironment();




   //装配所有默认转换器和格式化组件
   //装配main方法参数
   //装配Profiles配置,实际上存根对象不能使用,返回空
   configureEnvironment(environment, applicationArguments.getSourceArgs());


   //将StandardServletEnvironment类MutablePropertySources propertySources属性封装为
    //迭代器SpringConfigurationPropertySources
   //name为configurationProperties,source为SpringConfigurationPropertySources
   ConfigurationPropertySources.attach(environment);


   //回调通知环境装配完成事件,参考前面事件监听器设计
   //这里我们要注意的是,application.properties文件的配置项解析
   //大部分是通过事件监听器去实现的,可以猜测其它接入的框架的配置解析也是通过实现监听器去处理的
  //后面会简单给出ConfigFileApplicationListener事件监听器部分源码作为例子
   listeners.environmentPrepared(environment);


   //将配置文件中的spring.main开头的配置信息绑定到SpringApplication类对应的属性中
   bindToSpringApplication(environment);


   if (!this.isCustomEnvironment) {
      //根据spring.main.web-application-type配置判断是否需要转换环境
      environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
            deduceEnvironmentClass());
   }


   //解除前面的迭代器封装
   ConfigurationPropertySources.attach(environment);


   return environment;
}


private ConfigurableEnvironment getOrCreateEnvironment() {
   if (this.environment != null) {
      return this.environment;
   }
   switch (this.webApplicationType) {
      case SERVLET:
      //默认是servlet
         return new StandardServletEnvironment();
      case REACTIVE:
         return new StandardReactiveWebEnvironment();
      default:
         return new StandardEnvironment();
   }
}

 

prepareEnvironment方法源码核心逻辑:

1、根据webApplicationType属性创建Environment环境对象StandardServletEnvironment。

2、初始化操作系统系统变量、JDK系统变量、Servlet上下文配置、Servlet配置文件配置等占位符对象,保证各类配置的顺序,保存在StandardServletEnvironment类MutablePropertySources propertySources属性中。

3、装配所有默认转换器、格式化组件、main方法参数。

4、将StandardServletEnvironment类MutablePropertySources propertySources属性封装为迭代器SpringConfigurationPropertySources。

5、回调环境对象准备完成事件,其中事件监听器承担了application.properties配置文件的解析工作,将解析的配置信息保存到StandardServletEnvironment类MutablePropertySources propertySources属性中。可以猜测其它接入的框架的配置解析也是通过实现监听器去处理的。

6、将配置文件中的spring.main开头的配置信息绑定到SpringApplication类对应的属性中。

7、根据spring.main.web-application-type配置判断是否需要转换环境对象。

8、解除StandardServletEnvironment类MutablePropertySources propertySources属性迭代器封装。

小结:Spring环境装配完成后基本拥有了各种环境的配置参数信息以及将近130多个装换器和格式化组件,后面直接从中获取即可。

 

 

初始化系统变量等源码分析:

StandardServletEnvironment类继承图如下:

 

 

其中抽象类AbstractEnvironment构造器调用了初始化系统属性的方法:

 

public AbstractEnvironment() {
   customizePropertySources(this.propertySources);
}

StandardServletEnvironment子类实现方法源码如下:

public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment {




    /** Servlet context init parameters property source name: {@value}. */
    public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams";


    /** Servlet config init parameters property source name: {@value}. */
    public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams";




    @Override
    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
        propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
        if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
            propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
        }
        super.customizePropertySources(propertySources);
    }


}


public class StandardEnvironment extends AbstractEnvironment {


   /** System environment property source name: {@value}. */
   public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";


   /** JVM system properties property source name: {@value}. */
   public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";


   @Override
   protected void customizePropertySources(MutablePropertySources propertySources) {
      propertySources.addLast(
            new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
      propertySources.addLast(
            new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
   }


}

注册默认的转换器、格式化组件源码分析:

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {




   if (this.addConversionService) {
      //实例化ConversionService子类ApplicationConversionService
      //实例化构造器中会注册所有默认类型装换器(Converters)以及默认格式化组件(Formatters)
      ConversionService conversionService = ApplicationConversionService.getSharedInstance();


      //装配默认转换器以及格式化组件
      environment.setConversionService((ConfigurableConversionService) conversionService);
   }


   //装配main方法参数
   configurePropertySources(environment, args);


   //装配Profiles配置
   configureProfiles(environment, args);
}

ApplicationConversionService.getSharedInstance()源码分析:

public static ConversionService getSharedInstance() {
   ApplicationConversionService sharedInstance = ApplicationConversionService.sharedInstance;
   if (sharedInstance == null) {
      synchronized (ApplicationConversionService.class) {
         sharedInstance = ApplicationConversionService.sharedInstance;
         if (sharedInstance == null) {
            //new ApplicationConversionService()
            sharedInstance = new ApplicationConversionService();
            ApplicationConversionService.sharedInstance = sharedInstance;
         }
      }
   }
   return sharedInstance;
}

 

new ApplicationConversionService(...)源码分析:

public ApplicationConversionService() {
   this(null);
}


public ApplicationConversionService(StringValueResolver embeddedValueResolver) {
   if (embeddedValueResolver != null) {
      setEmbeddedValueResolver(embeddedValueResolver);
   }
   //注册装换器、格式化组件
   configure(this);
}


public static void configure(FormatterRegistry registry) {
   //注册默认转换器组件
   DefaultConversionService.addDefaultConverters(registry);
   //注册默认格式化器组件
   DefaultFormattingConversionService.addDefaultFormatters(registry);
   //注册应用程序默认格式化器组件
   addApplicationFormatters(registry);
   //注册应用程序默认转换器组件
   addApplicationConverters(registry);
}


public static void addDefaultConverters(ConverterRegistry converterRegistry) {
   addScalarConverters(converterRegistry);
   addCollectionConverters(converterRegistry);


   converterRegistry.addConverter(new ByteBufferConverter((ConversionService) converterRegistry));
   converterRegistry.addConverter(new StringToTimeZoneConverter());
   converterRegistry.addConverter(new ZoneIdToTimeZoneConverter());
   converterRegistry.addConverter(new ZonedDateTimeToCalendarConverter());


   converterRegistry.addConverter(new ObjectToObjectConverter());
   converterRegistry.addConverter(new IdToEntityConverter((ConversionService) converterRegistry));
   converterRegistry.addConverter(new FallbackObjectToStringConverter());
   converterRegistry.addConverter(new ObjectToOptionalConverter((ConversionService) converterRegistry));
}


public static void addDefaultFormatters(FormatterRegistry formatterRegistry) {
  
   formatterRegistry.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());


 
   if (jsr354Present) {
      formatterRegistry.addFormatter(new CurrencyUnitFormatter());
      formatterRegistry.addFormatter(new MonetaryAmountFormatter());
      formatterRegistry.addFormatterForFieldAnnotation(new Jsr354NumberFormatAnnotationFormatterFactory());
   }


   new DateTimeFormatterRegistrar().registerFormatters(formatterRegistry);


   if (jodaTimePresent) {
      // handles Joda-specific types as well as Date, Calendar, Long
      new JodaTimeFormatterRegistrar().registerFormatters(formatterRegistry);
   }
   else {
      // regular DateFormat-based Date, Calendar, Long converters
      new DateFormatterRegistrar().registerFormatters(formatterRegistry);
   }
}




...其它不再截取

 

configurePropertySources(...)源码分析:

protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {


   //MutablePropertySources propertySources = new MutablePropertySources()
   //MutablePropertySources实现接口PropertySources,PropertySources接口注释说明实现该接口子类功能作为
   //PropertySource接口的容器,关键注释:Holder containing one or more {@link PropertySource} objects.
   //简单说就是PropertySources接口实现类的功能是作为PropertySource接口的容器
   //注意PropertySources和PropertySource的区别,多个了复数s
   MutablePropertySources sources = environment.getPropertySources();


   //如果设置默认属性,则添加进去,这里没有设置
   if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
      sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
   }


   //如果有命令行参数,这里将命令行参数封装后添加到key为springApplicationCommandLineArgs
   //的MutablePropertySources属性List<PropertySource<?>> propertySourceList中
   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 {
         //这里需要注意,通过main方法传入的参数添加到最前面,说明main方法参数优先级最高
         sources.addFirst(new SimpleCommandLinePropertySource(args));
      }
   }
}


configureProfiles(...)源码分析:

protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
   Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
   //由于前面初始化的都是存根对象,所以这里并不能拿到任何值,返回和set的都是空的集合
   profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
   environment.setActiveProfiles(StringUtils.toStringArray(profiles));
}

ConfigurationPropertySources.attach(environment)源码分析:

private static final String ATTACHED_PROPERTY_SOURCE_NAME = "configurationProperties";
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) {
      //封装SpringConfigurationPropertySources迭代器,key为configurationProperties
      sources.addFirst(new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME,
            new SpringConfigurationPropertySources(sources)));
   }
}

listeners.environmentPrepared(environment)源码分析:

参考前面的文章。

 

bindToSpringApplication(environment)源码分析:

这个方法底层逻辑非常多,主要逻辑是处理给定的配置开头以及要绑定的对象进行不同规则解析后进行反射赋值操作。完全是单独的配置项解析组件,后续可以作为单独章节去详细说明。

 

convertEnvironmentIfNecessary(...)源码分析:

 

StandardEnvironment convertEnvironmentIfNecessary(ConfigurableEnvironment environment,
      Class<? extends StandardEnvironment> type) {
   if (type.equals(environment.getClass())) {
      return (StandardEnvironment) environment;
   }
   return convertEnvironment(environment, type);
}


private StandardEnvironment convertEnvironment(ConfigurableEnvironment environment,
      Class<? extends StandardEnvironment> type) {
   //根据类型实例化新的环境对象
   StandardEnvironment result = createEnvironment(type);
   //转移ActiveProfiles属性
   result.setActiveProfiles(environment.getActiveProfiles());
   //转移ConversionService属性
   result.setConversionService(environment.getConversionService());
   //复制PropertySources
   copyPropertySources(environment, result);
   return result;
}


private StandardEnvironment createEnvironment(Class<? extends StandardEnvironment> type) {
   try {
      return type.newInstance();
   }
   catch (Exception ex) {
      return new StandardEnvironment();
   }
}

ConfigFileApplicationListener#onApplicationEvent源码分析:

//可以看到它实现了SmartApplicationListener接口,还记得前面文章描述的通过泛型参数类型进行匹配
//那句话描述不准确,正确的是通过接口去匹配,然后调用对应子类的supportsEventType和supportsSourceType方法进行匹配
public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {


   @Override
   public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
      return ApplicationEnvironmentPreparedEvent.class.isAssignableFrom(eventType)
            || ApplicationPreparedEvent.class.isAssignableFrom(eventType);
   }


   @Override
   public void onApplicationEvent(ApplicationEvent event) {
      if (event instanceof ApplicationEnvironmentPreparedEvent) {
         //环境准备完成事件,开始解析application.properties配置文件
         onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
      }
      if (event instanceof ApplicationPreparedEvent) {
         onApplicationPreparedEvent(event);
      }
   }


   private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
      //从META-INF/spring.factories缓存获取EnvironmentPostProcessor配置的实现类
      List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
      //把当前类对象也添加进去
      postProcessors.add(this);
      //排序
      AnnotationAwareOrderComparator.sort(postProcessors);
      for (EnvironmentPostProcessor postProcessor : postProcessors) {
         //调用postProcessEnvironment方法处理
         postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
      }
   }


   List<EnvironmentPostProcessor> loadPostProcessors() {
      return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, getClass().getClassLoader());
   }


@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
   addPropertySources(environment, application.getResourceLoader());
}


protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
   //添加一个随机信息到systemEnvironment,暂不还不清楚这里作用是啥
   RandomValuePropertySource.addToEnvironment(environment);
   //通过实例化内部类Loader对象进行Profiles解析,具体不再往下跟,有兴趣可以自行分析
   new Loader(environment, resourceLoader).load();
}


void load() {
   FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
         (defaultProperties) -> {
            this.profiles = new LinkedList<>();
            this.processedProfiles = new LinkedList<>();
            this.activatedProfiles = false;
            this.loaded = new LinkedHashMap<>();
            initializeProfiles();
            while (!this.profiles.isEmpty()) {
               Profile profile = this.profiles.poll();
               if (isDefaultProfile(profile)) {
                  addProfileToEnvironment(profile.getName());
               }
               load(profile, this::getPositiveProfileFilter,
                     addToLoaded(MutablePropertySources::addLast, false));
               this.processedProfiles.add(profile);
            }
            load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
            addLoadedPropertySources();
            applyActiveProfiles(defaultProperties);
         });
}




}

 

关注我

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot是一个开源的Java框架,用于构建独立的、可执行的、生产级的Spring应用程序。它提供了一个快速、简单的方式来开发和部署应用程序。而在Spring Boot的启动过程中,有以下几个主要的步骤: 1. 加载启动类:Spring Boot应用程序的启动类通常是一个带有`@SpringBootApplication`注解的Java类。在应用程序启动时,会通过`main`方法加载这个启动类。 2. 创建Spring Application对象:Spring Boot会创建一个`SpringApplication`对象,用于启动应用程序。`SpringApplication`是Spring Boot框架的核心类,它负责管理整个应用程序的生命周期。 3. 解析配置信息:在启动过程中,`SpringApplication`会解析`application.properties`或`application.yaml`文件中的配置信息,并将其加载到Spring环境中。这些配置信息可以用来配置应用程序的各个方面,如数据库连接、日志级别等。 4. 创建并配置Spring容器:Spring Boot使用Spring容器来管理应用程序中的各个Bean。在启动过程中,`SpringApplication`会根据配置信息创建并配置一个Spring容器,该容器负责加载和管理应用程序中的所有Bean。 5. 执行自定义逻辑:在Spring Boot的启动过程中,可以添加自定义的逻辑。例如,可以通过实现`CommandLineRunner`接口来在应用程序启动后执行一些初始化操作。 6. 启动应用程序:完成上述步骤后,`SpringApplication`会启动应用程序,并通过Servlet容器(如Tomcat、Jetty等)监听端口,开始接收和处理HTTP请求。 总体而言,Spring Boot的启动流程是一个通过加载启动类、解析配置信息、创建和配置Spring容器的过程。通过Spring Boot的自动配置和快速启动能力,开发者可以更加方便地构建和部署Spring应用程序。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值