SpringBoot源码解析(五):准备应用环境
SpringBoot源码系列文章
SpringBoot源码解析(一):SpringApplication构造方法
SpringBoot源码解析(二):引导上下文DefaultBootstrapContext
SpringBoot源码解析(三):启动开始阶段
SpringBoot源码解析(四):解析应用参数args
SpringBoot源码解析(五):准备应用环境
目录
- 前言
- 一、入口
- 二、环境实例ApplicationServletEnvironment
- 1、PropertyResolver
- 2、Environment
- 3、ConfigurablePropertyResolver
- 4、ConfigurableEnvironment
- 5、ConfigurableWebEnvironment
- 6、AbstractEnvironment
- 7、StandardEnvironment
- 8、StandardServletEnvironment
- 9、ApplicationServletEnvironment
- 三、配置环境
- 1、命令行参数属性源
- 2、配置属性源
- 四、触发的应用监听器
- 1、EnvironmentPostProcessorApplicationListener
- 1.1、RandomValuePropertySourceEnvironmentPostProcessor
- 1.2、SystemEnvironmentPropertySourceEnvironmentPostProcessor
- 1.3、SpringApplicationJsonEnvironmentPostProcessor
- 1.4、ConfigDataEnvironmentPostProcessor
- 2、AnsiOutputApplicationListener
- 3、BackgroundPreinitializer
- 4、FileEncodingApplicationListener
- 五、默认属性源
- 六、绑定spring.main配置到SpringApplication对象
- 总结
前言
在前文中,我们深入解析了启动类main函数中args参数被解析为选项参数和非选项参数的过程。接下来,我们将探讨SpringBoot启动时应用环境的准备
过程,包括读取配置文件
和设置环境变量
的步骤。
SpringBoot版本2.7.18
SpringApplication的run方法的执行逻辑如下,本文将详细介绍第4小节:准备应用环境
// SpringApplication类方法
public ConfigurableApplicationContext run(String... args) {// 记录应用启动的开始时间long startTime = System.nanoTime();// 1.创建引导上下文,用于管理应用启动时的依赖和资源DefaultBootstrapContext bootstrapContext = createBootstrapContext();ConfigurableApplicationContext context = null;// 配置无头模式属性,以支持在无图形环境下运行// 将系统属性 java.awt.headless 设置为 trueconfigureHeadlessProperty();// 2.获取Spring应用启动监听器,用于在应用启动的各个阶段执行自定义逻辑SpringApplicationRunListeners listeners = getRunListeners(args);// 启动开始方法(发布开始事件、通知应用监听器ApplicationListener)listeners.starting(bootstrapContext, this.mainApplicationClass);try {// 3.解析应用参数ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);// 4.准备应用环境,包括读取配置文件和设置环境变量ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);// 配置是否忽略 BeanInfo,以加快启动速度configureIgnoreBeanInfo(environment);// 5.打印启动BannerBanner printedBanner = printBanner(environment);// 6.创建应用程序上下文context = createApplicationContext();// 设置应用启动的上下文,用于监控和管理启动过程context.setApplicationStartup(this.applicationStartup);// 7.准备应用上下文,包括加载配置、添加 Bean 等prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);// 8.刷新上下文,完成 Bean 的加载和依赖注入refreshContext(context);// 9.刷新后的一些操作,如事件发布等afterRefresh(context, applicationArguments);// 计算启动应用程序的时间,并记录日志Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);}// 10.通知监听器应用启动完成listeners.started(context, timeTakenToStartup);// 11.调用应用程序中的 `CommandLineRunner` 或 `ApplicationRunner`,以便执行自定义的启动逻辑callRunners(context, applicationArguments);}catch (Throwable ex) {// 12.处理启动过程中发生的异常,并通知监听器handleRunFailure(context, ex, listeners);throw new IllegalStateException(ex);}try {// 13.计算应用启动完成至准备就绪的时间,并通知监听器Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);listeners.ready(context, timeTakenToReady);}catch (Throwable ex) {// 处理准备就绪过程中发生的异常handleRunFailure(context, ex, null);throw new IllegalStateException(ex);}// 返回已启动并准备就绪的应用上下文return context;
}
一、入口
// 4.准备应用环境,包括读取配置文件和设置环境变量
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
- 初始化并配置Spring应用环境
// SpringApplication类方法
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {// 4.1.获取环境实例 ApplicationServletEnvironmentConfigurableEnvironment environment = getOrCreateEnvironment();// 4.2.配置环境// 将命令行参数传递给环境配置configureEnvironment(environment, applicationArguments.getSourceArgs());// 添加配置属性源到环境中,使其能够解析相关配置属性ConfigurationPropertySources.attach(environment);// 4.3.通知监听器,环境已准备好listeners.environmentPrepared(bootstrapContext, environment);// 4.4.将默认属性源移到环境属性源列表的末尾DefaultPropertiesPropertySource.moveToEnd(environment);// spring.main.environment-prefix是SpringBoot用于管理上下文环境的一部分// 不应该由用户直接修改,如果被用户定义,则抛错Assert.state(!environment.containsProperty("spring.main.environment-prefix"),"Environment prefix cannot be set via properties.");// 4.5.绑定spring.main环境配置到SpringApplication对象上bindToSpringApplication(environment);// 如果环境不是自定义的,则进行环境转换,确定必要的环境类型if (!this.isCustomEnvironment) {EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());}// 再次设置配置属性源,确保配置属性源在第一位ConfigurationPropertySources.attach(environment);// 返回配置完成的环境对象return environment;
}
二、环境实例ApplicationServletEnvironment
getOrCreateEnvironment()方法的核心是创建一个 ApplicationServletEnvironment
实例,下面将重点研究该类。
先看类图:从上到下逐一分析
1、PropertyResolver
PropertyResolver
是Spring核心框架中的一个接口,提供了解析属性值
的统一方法。它支持从多种配置源(如系统属性
、环境变量
、配置文件
等)获取属性值,广泛用于环境配置、占位符解析等场景。
- 属性检查
- 判断某个属性是否存在
- 方法:containsProperty(String key)
- 获取属性值
- 获取指定键的属性值,支持默认值、类型转换等
- 方法:getProperty(String key)、getProperty(String key, String defaultValue)、getProperty(String key, Class targetType) 等
- 必需属性值
- 获取指定键的属性值,若找不到则抛出异常
- 方法:getRequiredProperty(String key)、getRequiredProperty(String key, Class targetType)
- 占位符解析
- 解析字符串中的 ${…} 占位符,替换为对应的属性值
- 方法:resolvePlaceholders(String text)、resolveRequiredPlaceholders(String text)
// 用于解析属性值的接口,支持从底层源解析属性
public interface PropertyResolver {// 判断指定的属性键是否可用,即该键对应的值是否不为nullboolean containsProperty(String key);// 返回与指定键关联的属性值,如果未找到则返回null@NullableString getProperty(String key);// 返回与指定键关联的属性值,如果未找到则返回默认值String getProperty(String key, String defaultValue);// 返回与指定键关联的属性值,并将其转换为指定的目标类型,如果未找到则返回null@Nullable<T> T getProperty(String key, Class<T> targetType);// 返回与指定键关联的属性值,并将其转换为目标类型,如果未找到则返回默认值<T> T getProperty(String key, Class<T> targetType, T defaultValue);// 返回与指定键关联的属性值(不能为null)String getRequiredProperty(String key) throws IllegalStateException;// 返回与指定键关联的属性值,并将其转换为目标类型(不能为null)<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;// 解析给定文本中的 ${...} 占位符,并用对应的属性值替换。// 未解析的占位符会被忽略并原样返回。String resolvePlaceholders(String text);// 解析给定文本中的 ${...} 占位符,并用对应的属性值替换// 未解析的占位符将抛出 IllegalArgumentException 异常String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
}
2、Environment
Environment
是Spring框架中的一个核心接口,用于表示应用程序的运行环境。它扩展了PropertyResolver接口,既负责属性解析,也负责Profile管理
。在Spring中,它的主要用途是管理配置文件(Profiles)和属性(Properties)
。
// 表示当前应用程序运行环境的接口
public interface Environment extends PropertyResolver {// 返回当前环境中激活的ProfilesString[] getActiveProfiles();// 返回当前环境中默认激活的 ProfilesString[] getDefaultProfiles();// 检查给定的 Profile 表达式是否与当前激活的 Profiles 匹配default boolean matchesProfiles(String... profileExpressions) {return acceptsProfiles(Profiles.of(profileExpressions));}// 检查一个或多个给定的 Profiles 是否被当前环境接受@Deprecatedboolean acceptsProfiles(String... profiles);// 检查给定的 Profiles 谓词是否与当前激活的或默认的 Profiles 匹配boolean acceptsProfiles(Profiles profiles);
}
3、ConfigurablePropertyResolver
ConfigurablePropertyResolver
是Spring中PropertyResolver的扩展接口,为属性解析器添加了额外的配置能力,主要用于高级属性管理和占位符解析。它允许自定义属性解析行为,如类型转换服务
、占位符格式
以及验证必需的属性
。
// 用于在将属性值从一种类型转换为另一种类型时使用
public interface ConfigurablePropertyResolver extends PropertyResolver {// 获取用于属性值类型转换的 ConfigurableConversionService 实例ConfigurableConversionService getConversionService();// 设置类型转换服务void setConversionService(ConfigurableConversionService conversionService);// 设置占位符的前缀,默认为 ${// 示例:若设置为 #{,则占位符形如 #{property}void setPlaceholderPrefix(String placeholderPrefix);// 设置占位符的后缀,默认为 }// 示例:与 setPlaceholderPrefix 配合使用,解析 #{property}void setPlaceholderSuffix(String placeholderSuffix);// 设置占位符和默认值之间的分隔符,默认值为 :// 示例:${property:defaultValue} 表示如果 property 未定义,则返回 defaultValuevoid setValueSeparator(@Nullable String valueSeparator);// 设置是否忽略无法解析的嵌套占位符// true:保留未解析的占位符(如 ${unresolved})// false:遇到未解析的占位符时抛出异常void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders);// 设置必须存在的属性void setRequiredProperties(String... requiredProperties);// 验证所有必需属性是否存在并解析为非 nullvoid validateRequiredProperties() throws MissingRequiredPropertiesException;
}
4、ConfigurableEnvironment
ConfigurableEnvironment
是Spring框架中的一个核心接口,扩展了Environment和ConfigurablePropertyResolver接口,提供了对Profile
和属性源(PropertySources)
的动态管理功能。它主要用于应用启动前的环境配置,允许开发者根据需求自定义属性解析规则和Profile配置。
public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {// 设置当前环境的 Active Profiles(激活的配置)void setActiveProfiles(String... profiles);// 向当前的 Active Profiles 集合中添加一个 Profilevoid addActiveProfile(String profile);// 设置默认激活的 Profile 集合void setDefaultProfiles(String... profiles);// 返回当前环境的属性源集合,允许动态操作属性源MutablePropertySources getPropertySources();// 返回JVM的系统属性(System.getProperties())Map<String, Object> getSystemProperties();// 返回操作系统的环境变量(System.getenv())Map<String, Object> getSystemEnvironment();// 将父环境的 Active Profiles、Default Profiles 和 属性源 合并到当前环境中void merge(ConfigurableEnvironment parent);
}
5、ConfigurableWebEnvironment
ConfigurableWebEnvironment
是Spring框架中ConfigurableEnvironment的特化接口,主要用于基于Servlet环境的Web应用程序。它扩展了 ConfigurableEnvironment,并增加了与Servlet相关的配置功能,确保在ServletContext
和ServletConfig
可用时,能够尽早初始化与 Servlet 环境相关的属性源。
public interface ConfigurableWebEnvironment extends ConfigurableEnvironment {// 使用给定的 ServletContext 和(可选的)ServletConfig 初始化属性源。// 该方法会将占位符性质的属性源替换为实际的 Servlet 上下文或配置属性源。// 调用时机:Spring Boot 中的内嵌容器(如 Tomcat、Jetty)启动时void initPropertySources(@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig);
}
6、AbstractEnvironment
AbstractEnvironment
作为Environment实现的基础类,主要提供对配置文件(Profiles)
和属性源(PropertySources)
的管理。它支持定义和管理激活配置文件(Active Profiles) 以及 默认配置文件(Default Profiles)。
public abstract class AbstractEnvironment implements ConfigurableEnvironment {// 激活配置文件集合private final Set<String> activeProfiles = new LinkedHashSet<>();// 默认配置文件集合 getReservedDefaultProfiles() = 字符串“default”private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());// 属性源集合,上篇文章有介绍,属性源就是一组属性map键值对,这里集合就是多组键值对private final MutablePropertySources propertySources;// 属性解析器private final ConfigurablePropertyResolver propertyResolver;public AbstractEnvironment() {this(new MutablePropertySources());}protected AbstractEnvironment(MutablePropertySources propertySources) {this.propertySources = propertySources;this.propertyResolver = createPropertyResolver(propertySources);customizePropertySources(propertySources);}// 自定义属性源,对属性源增删改查做操作protected void customizePropertySources(MutablePropertySources propertySources) {// 默认无操作,子类可覆盖}// 省略其他方法
}
PropertySource
表示属性来源的抽象概念,每个属性源(PropertySource)封装了一组键值对(key-value)
,并可以根据键解析属性值。用于从各种来源(如系统属性
、环境变量
、配置文件
等)加载属性
public abstract class PropertySource<T> {// 属性源名称,唯一标识protected final String name;// 一般是map键值对protected final T source;...
}
MutablePropertySources
用于管理多个属性源(PropertySource)的集合。它提供了一个可变的数据结构,允许开发者动态地添加、删除、替换或调整属性源的顺序。
addFirst
(PropertySource<?> propertySource):将属性源添加到集合的首位,优先级最高
addLast
(PropertySource<?> propertySource):将属性源添加到集合的末尾,优先级最低
addBefore
(String relativePropertySourceName, PropertySource<?> propertySource):在指定名称的属性源之前插入addAfter
(String relativePropertySourceName, PropertySource<?> propertySource):在指定名称的属性源之后插入
public class MutablePropertySources implements PropertySources {// 属性源集合private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();...
}
7、StandardEnvironment
StandardEnvironment
提供了对系统属性(System Properties)
和环境变量(Environment Variables)
的支持,并在默认情况下将这两个属性源添加到 MutablePropertySources中。
AbstractEnvironment构造方法中会调用customizePropertySources方法,也就是创建ApplicationServletEnvironment实例就是添加两个属性源
,名称为systemProperties
为的JVM系统属性
(如 java.version、java.home 等)和名称为systemEnvironment
的环境变量
(PATH、HOME等操作系统相关变量)。
public class StandardEnvironment extends AbstractEnvironment {// 系统环境变量属性源的名称public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";// JVM 系统属性源的名称public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";public StandardEnvironment() {}protected StandardEnvironment(MutablePropertySources propertySources) {super(propertySources);}// 自定义操作属性源,父类构造中会调用@Overrideprotected void customizePropertySources(MutablePropertySources propertySources) {// addLast将属性放到最后,优先级最低,JVM优先级高于环境变量propertySources.addLast(// 通过 System.getProperties() 提供的 JVM 系统属性new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));propertySources.addLast(// 通过 System.getenv() 提供的环境变量new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));}
}
8、StandardServletEnvironment
StandardServletEnvironment
是Spring框架中的一个类,继承自StandardEnvironment,用于为基于Servlet的Web应用程序提供专门的Environment实现。它扩展了标准环境(StandardEnvironment)的功能,增加了对Servlet相关属性(如ServletConfig
和ServletContext
)的支持。
属性优先级:ServletConfig > ServletContext > JNDI > 系统属性 > 环境变量。
在应用上下文启动时,StandardServletEnvironment会将占位符属性(StubPropertySource)替换为实际的ServletConfig和ServletContext属性源。
public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment {// ServletContext 初始化参数属性源的名称public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams";// ServletConfig 初始化参数属性源的名称public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams";// JNDI 属性源名称public static final String JNDI_PROPERTY_SOURCE_NAME = "jndiProperties";// 防御性地引用 JNDI API,用于 JDK 9+(可选的 java.naming 模块)private static final boolean jndiPresent = ClassUtils.isPresent("javax.naming.InitialContext", StandardServletEnvironment.class.getClassLoader());public StandardServletEnvironment() {}protected StandardServletEnvironment(MutablePropertySources propertySources) {super(propertySources);}// 自定义属性源集合,添加超类贡献的属性源@Overrideprotected void customizePropertySources(MutablePropertySources propertySources) {// 添加 ServletConfig 初始化参数占位符属性源propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));// 添加 ServletContext 初始化参数占位符属性源propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));// 如果 JNDI 可用且默认环境可用,则添加 JNDI 属性源if (jndiPresent && JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));}// 调用父类的自定义属性源方法super.customizePropertySources(propertySources);}// 初始化属性源,将 Servlet 相关的属性源(如 ServletContext 和 ServletConfig)初始化为真实属性源@Overridepublic void initPropertySources(@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);}
}
9、ApplicationServletEnvironment
AbstractEnvironment构造方法中会调用createPropertyResolver方法,也就是创建ApplicationServletEnvironment实例就会创建此自定义属性解析器,重写修改自定义属性解析器为ConfigurationPropertySourcesPropertyResolver
class ApplicationServletEnvironment extends StandardServletEnvironment {// 表示不从任何特定的属性(如 spring.profiles.active)获取活动配置文件@Overrideprotected String doGetActiveProfilesProperty() {return null;}// 表示不从任何特定的属性(如 spring.profiles.default)获取默认配置文件@Overrideprotected String doGetDefaultProfilesProperty() {return null;}// 自定义属性解析器,// 默认使用ConfigurationPropertySources的实现,这里重写实现为ConfigurationPropertySourcesPropertyResolver@Overrideprotected ConfigurablePropertyResolver createPropertyResolver(MutablePropertySources propertySources) {return ConfigurationPropertySources.createPropertyResolver(propertySources);}
}
三、配置环境
1、命令行参数属性源
// 4.2.配置环境
// 将命令行参数传递给环境配置
configureEnvironment(environment, applicationArguments.getSourceArgs());// 配置环境方法
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {// 如果需要添加转换服务,用于在应用程序中管理和执行各种类型之间的转换if (this.addConversionService) {// 设置转换服务为一个新的 ApplicationConversionService 实例environment.setConversionService(new ApplicationConversionService());}// 配置属性源(将命令行参数添加到属性源集合中,放第一位优先级最高)configurePropertySources(environment, args);// 配置活动的配置文件,空实现configureProfiles(environment, args);
}
- 将
命令行参数属性源
添加到环境的属性源集合中,且放第一位
/*** 配置属性源的方法。* @param environment 可配置的环境对象,用于管理属性源* @param args 应用程序启动时传入的命令行参数*/
protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {// 获取当前环境中的属性源集合MutablePropertySources sources = environment.getPropertySources();// 如果存在默认属性(defaultProperties),将其添加或合并到属性源中if (!CollectionUtils.isEmpty(this.defaultProperties)) {DefaultPropertiesPropertySource.addOrMerge(this.defaultProperties, sources);}// 如果启用了命令行属性解析,并且命令行参数非空if (this.addCommandLineProperties && args.length > 0) {// 获取命令行属性源的默认名称 “commandLineArgs”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));}}
}
2、配置属性源
ConfigurationPropertySourcesPropertySource
这个类的作用是将 Spring 配置属性源(如 .properties 文件、.yml 文件、环境变量等)转换为一个统一的PropertySource
,并将这些属性源集成到Spring的Environment
中。具体解析内容后面单独讲
将名称为configurationProperties
,对象为SpringConfigurationPropertySources
的属性源添加到环境的属性源集合中,且放第一位,优先级最高
。
// 4.2.配置环境
// 添加配置属性源到环境中,使其能够解析相关配置属性
ConfigurationPropertySources.attach(environment);public static void attach(Environment environment) {// 确保传入的 environment 是 ConfigurableEnvironment 类型Assert.isInstanceOf(ConfigurableEnvironment.class, environment);// 获取当前环境的属性源集合MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources();// 检查是否已经附加了名为“configurationProperties”的属性源PropertySource<?> attached = getAttached(sources);// 如果尚未附加,或者附加的属性源未正确使用当前属性源集合if (attached == null || !isUsingSources(attached, sources)) {// 创建一个新的 ConfigurationPropertySourcesPropertySource,// 它包装了当前的属性源集合attached = new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME, new SpringConfigurationPropertySources(sources));}// 移除已有的同名属性源(如果存在)sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);// 将新的属性源添加到属性源集合的最前面,确保其具有最高优先级sources.addFirst(attached);
}
四、触发的应用监听器
// 4.3.通知监听器,环境已准备好
listeners.environmentPrepared(bootstrapContext, environment);
在之前文章SpringBoot源码解析(三):启动开始阶段已经介绍过,广播器将特定的事件(之前的应用启动事件,现在这里就是准备环境事件)推给合适的监听器(匹配监听器的事件类型,这里就是匹配准备环境事件的监听器)。下面我们自己所有匹配到监听器的具体内容。
1、EnvironmentPostProcessorApplicationListener
EnvironmentPostProcessorApplicationListener
的作用是监听ApplicationEnvironmentPreparedEvent
事件,加载并执行所有EnvironmentPostProcessor
实现类,用于在SpringBoot应用启动过程中对环境配置 (Environment
) 进行动态调整和扩展,例如加载额外的配置源、设置属性或修改激活的 profiles,确保在应用上下文初始化之前完成环境的定制化操作。
- 环境后置处理器
EnvironmentPostProcessor
的实现类也是从META-INF/spring.factories
文件中加载
// 当应用环境准备事件触发时执行的方法
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {// 获取应用的环境配置ConfigurableEnvironment environment = event.getEnvironment();// 获取 Spring 应用对象SpringApplication application = event.getSpringApplication();// 遍历所有的 EnvironmentPostProcessor(环境后置处理器)for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(application.getResourceLoader(),event.getBootstrapContext())) {// 调用每个后置处理器的 postProcessEnvironment 方法,处理环境配置postProcessor.postProcessEnvironment(environment, application);}
}// 环境后置处理器接口
@FunctionalInterface
public interface EnvironmentPostProcessor {void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application);
}
- 下面看下加载到的所有环境后置处理器
1.1、RandomValuePropertySourceEnvironmentPostProcessor
RandomValuePropertySourceEnvironmentPostProcessor
是SpringBoot提供的一个内置类,用于在Spring应用程序启动时向环境中添加一个RandomValuePropertySource
。
它通过 RandomValuePropertySource
提供生成随机值的功能(如随机字符串、整数或 UUID),供配置文件中使用。
- 生成随机值:
- 支持
random.int
、random.long
、random.uuid
、random.value
等 - 允许在配置文件中动态生成随机值,常用于密钥、端口号等需要唯一性或随机性的场景
- 支持
- 在配置文件中用法
# 动态生成一个 UUID my.secret.key=${random.uuid} # 一个介于 1024 和 65535 之间的随机整数 my.random.port=${random.int[1024,65535]}
RandomValuePropertySource原理很简单,就是讲属性源对象设置为new Random()
1.2、SystemEnvironmentPropertySourceEnvironmentPostProcessor
在上面环境实例ApplicationServletEnvironment实例化
阶段,就添加了环境变量属性源SystemEnvironmentPropertySource
,这里把它替换成OriginAwareSystemEnvironmentPropertySource
,主要目的是增强对配置属性来源的追踪能力
,从而提升可维护性和调试性。
举例:
logging.level.org.springframework.core.env=DEBUG
2024-11-24 12:34:56.123 DEBUG o.s.b.c.c.ConfigFileApplicationListener - Loaded property source 'applicationConfig: [classpath:/application.properties]'
2024-11-24 12:34:56.124 DEBUG o.s.b.c.c.ConfigFileApplicationListener - Loaded property source 'applicationConfig: [classpath:/application.yml]'
2024-11-24 12:34:56.125 DEBUG o.s.b.e.e.PropertySourcesPropertyResolver - Found key 'my.app.port' in 'systemEnvironment' with value '8080'
1.3、SpringApplicationJsonEnvironmentPostProcessor
作用是将环境变量SPRING_APPLICATION_JSON
的JSON
格式内容解析为配置属性。
举例:
export SPRING_APPLICATION_JSON='{"server.port":8081,"spring.datasource.url":"jdbc:mysql://localhost:3306/mydb"}'
java -jar my-app.jar
- 优先级通常高于文件配置(如 application.properties 或 application.yml)
- 在容器化部署(如 Kubernetes、Docker)中,环境变量是常见的配置传递方式
关键代码片段:
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {String json = environment.getProperty("SPRING_APPLICATION_JSON");if (StringUtils.hasText(json)) {try {Map<String, Object> map = parseJson(json);PropertySource<?> propertySource = new MapPropertySource("spring.application.json", map);environment.getPropertySources().addFirst(propertySource);} catch (Exception ex) {throw new IllegalStateException("Invalid SPRING_APPLICATION_JSON: " + json, ex);}}
}
1.4、ConfigDataEnvironmentPostProcessor
ConfigDataEnvironmentPostProcessor是SpringBoot2.4
引入的一部分,作为Spring配置加载机制的新实现的核心组件。它取代了早期的ConfigFileApplicationListener
,专注于从多种来源(如 application.properties
、application.yml
、环境变量等)加载配置数据。具体解析内容后面单独讲
2、AnsiOutputApplicationListener
AnsiOutputApplicationListener
是SpringBoot提供的一个监听器,用于在应用启动时配置ANSI
控制台输出(彩色日志或彩色信息
) 的行为。它可以根据环境配置决定是否启用ANSI颜色支持,并设置相关属性。
3、BackgroundPreinitializer
BackgroundPreinitializer是SpringBoot内置的一个类,用于在后台线程中异步加载某些耗时的初始化操作
,从而减少应用主线程的阻塞时间,提高应用启动性能。
主线程可以专注于初始化Spring上下文,而耗时的操作(如 JUL日志桥接、默认的Validator实例化等)在后台进行,从而加快应用的总体启动速度。
4、FileEncodingApplicationListener
FileEncodingApplicationListener
是SpringBoot的一个监听器,用于检测和验证 JVM 的文件编码(file.encoding)属性,确保其值符合应用程序的要求。如果文件编码未设置为期望的值,可能会引发警告或异常。
五、默认属性源
将DefaultProperties(默认属性源)
移动到Spring环境(Environment)中属性源的最后
面。这通常用于确保用户配置的属性(如文件配置、环境变量、命令行参数等)优先于默认属性,从而允许用户覆盖默认配置。
// 4.4.将默认属性源移到环境属性源列表的末尾
DefaultPropertiesPropertySource.moveToEnd(environment);
- SpringBoot默认情况下
没有添加默认属性源
,用户可以自定义设置默认值
@Configuration
public class DefaultPropertyConfig {@Beanpublic PropertySource<?> defaultPropertySource() {return new MapPropertySource("defaultProperties", Map.of("server.port", "8080"));}
}
六、绑定spring.main配置到SpringApplication对象
// 4.5.绑定spring.main环境配置到SpringApplication对象上
bindToSpringApplication(environment);
它的作用是从ConfigurableEnvironment
中提取所有与spring.main
前缀相关的配置,并将这些配置值赋值给SpringApplication
类中的相应字段。
绑定的属性
属性名 | 默认值 | 描述 |
---|---|---|
spring.main.banner-mode | console | 启动横幅的显示模式,默认输出到控制台 |
spring.main.lazy-initialization | false | 是否启用懒加载模式 |
spring.main.log-startup-info | true | 是否输出启动日志信息 |
spring.main.allow-bean-definition-overriding | 环境决定(true 或 false ) | 是否允许覆盖 Bean 定义 |
spring.main.web-application-type | 自动检测 | 应用类型:none 、servlet 或 reactive |
spring.main.register-shutdown-hook | true | 是否注册 JVM 的关闭钩子(用于资源清理) |
总结
本文深入探讨了SpringBoot启动过程中应用环境的准备阶段
,包括从配置文件
、命令行参数
、系统属性
等多种配置源加载属性
,以及对环境对象进行调整和绑定。通过SpringBoot的灵活机制,开发者可以轻松扩展和调整应用环境,从而满足各种复杂的场景需求。
相关文章:
SpringBoot源码解析(五):准备应用环境
SpringBoot源码系列文章 SpringBoot源码解析(一):SpringApplication构造方法 SpringBoot源码解析(二):引导上下文DefaultBootstrapContext SpringBoot源码解析(三):启动开始阶段 SpringBoot源码解析(四):解析应用参数args Sp…...
JAVA笔记 | 策略模式+枚举Enum简单实现策略模式(可直接套用)
本篇为更为简单的策略模式应用,使用枚举来进行策略分配 上一篇(链接如下)更像是策略工厂模式来分配策略 JAVA笔记 | 实际上用到的策略模式(可直接套用)-CSDN博客 先创建策略相关类 //策略类 public interface PetStrategy {/*** 执行动作 - 跑RUN*/String run(Str…...
SpringBoot集成 Jasypt 实现数据源连接信息进行加密
SpringBoot集成 Jasypt 实现数据源连接信息进行加密 在实际项目中,敏感信息(如数据库连接的 URL、用户名、密码等)直接暴露在配置文件中可能导致安全隐患。为了解决这一问题,可以使用 Jasypt 来加密敏感信息,并在运行…...
大数据新视界 -- Hive 数据桶原理:均匀分布数据的智慧(上)(9/ 30)
💖💖💖亲爱的朋友们,热烈欢迎你们来到 青云交的博客!能与你们在此邂逅,我满心欢喜,深感无比荣幸。在这个瞬息万变的时代,我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…...
优化Docker镜像:提升部署效率与降低资源消耗
目录 1. 最小化镜像层 2. 使用轻量级基础镜像 3. 多阶段构建 4. 清理不必要的文件和依赖 5. 使用.dockerignore文件 6. 压缩和优化文件系统 7. 外部化配置和数据 8. 限制容器资源 9. 定期清理未使用的镜像和容器 结论 在云计算和微服务架构的浪潮中,Docke…...
strupr(arr);模拟实现(c基础)
hi , I am 36 适合对象c语言初学者 strupr(arr);函数是把arr数组变为大写字母,并返回arr 介绍一下strupr(arr);(c基础)-CSDN博客 现在进行My__strupr(arr);模拟实现 #include<stdio.h>//My__strupr(arr); //返回值为arr(地址),于是…...
skywalking es查询整理
索引介绍 sw_records-all 这个索引用于存储所有的采样记录,包括但不限于慢SQL查询、Agent分析得到的数据等。这些记录数据包括Traces、Logs、TopN采样语句和告警信息。它们被用于性能分析和故障排查,帮助开发者和运维团队理解服务的行为和性能特点。 …...
AI时代的软件工程:迎接LLM-DevOps的新纪元
在科技日新月异的今天,GPT的问世无疑为各行各业带来了一场深刻的变革,而软件工程领域更是首当其冲,正式迈入了软件工程3.0的新纪元。2024年,作为软件工程3.0的元年,伴随着软件工程3.0宣言的震撼发布,一个全…...
【机器学习】——卷积与循环的交响曲:神经网络模型在现代科技中的协奏
🎼个人主页:【Y小夜】 😎作者简介:一位双非学校的大二学生,编程爱好者, 专注于基础和实战分享,欢迎私信咨询! 🎆入门专栏:🎇【MySQL࿰…...
详解Servlet的使用
目录 Servlet 定义 动态页面 vs 静态页面 主要功能 Servlet的使用 创建Maven项目 引入依赖 创建目录 编写代码 打war包 部署程序 验证程序 Smart Tomcat 安装Smart Tomcat 配置Smart Tomcat插件 启动Tomcat 访问页面 路径对应关系 Servlet运行原理 Tomcat的…...
使用Java代码操作Kafka(五):Kafka消费 offset API,包含指定 Offset 消费以及指定时间消费
文章目录 1、指定 Offset 消费2、指定时间消费 1、指定 Offset 消费 auto.offset.reset earliest | latest | none 默认是 latest (1)earliest:自动将偏移量重置为最早的偏移量,–from-beginning (2)lates…...
MAC 怎么终端怎么退出和进入Anaconda环境
mac安装完anaconda 后,命令行窗口默认使用conda的,取消默认,用以下一行代码在命令行运行即可,重启终端: conda config --set auto_activate_base false # 将false改为true设置默认环境为conda进入conda环境ÿ…...
如何在 .gitignore 中仅保留特定文件:以忽略文件夹中的所有文件为例
在日常的开发工作中,使用 Git 来管理项目是不可或缺的一部分。项目中的某些文件夹可能包含大量的临时文件、生成文件或不需要版本控制的文件。在这种情况下,我们通常会使用 .gitignore 文件来忽略这些文件夹。然而,有时我们可能希望在忽略整个…...
USRP:B205mini-i
USRP B205mini-i B205mini-i都是采用工业级的FPGA芯片(-I表示industrial-grade),所以价格贵。 这个工业级会让工作温度从原来 0 – 45 C 变为 -40 – 75 C. 温度的扩宽,会让工作的稳定性变好。但是前提是你需要配合NI的外壳才行,你如果只买一…...
Oracle SQL优化②——访问路径
前言 访问路径指的就是通过哪种扫描方式获取数据,比如全表扫描、索引扫描或者直接通过ROWID获取数据。想要完成SQL优化,就必须深入理解各种访问路径。本文章详细介绍常见的访问路径。 一.常见访问路径 1.TABLE ACCESS FULL 表示全表扫描,…...
k8s1.30.0高可用集群部署
负载均衡 nginx负载均衡 两台nginx负载均衡 vim /etc/nginx/nginx.conf stream {upstream kube-apiserver {server 192.168.0.11:6443 max_fails3 fail_timeout30s;#server 192.168.0.12:6443 max_fails3 fail_timeout30s;#server 192.168.0.13:6443 max_fails3…...
Jenkins的环境部署
day22 回顾 Jenkins 简介 官网Jenkins Jenkins Build great things at any scale The leading open source automation server, Jenkins provides hundreds of plugins to support building, deploying and automating any project. 用来构建一切 其实就是用Java写的一个项目…...
Android开发教程案例源码分享-匹配动画多个头像飘动效果
Android开发教程案例源码分享-匹配动画多个头像飘动效果 匹配往往出现多个头像飘动,吸引人点击,有时出现的位置还不固定 一、思路: 用MotionLayout 二、效果图: 看视频更直观点: Android开发教程案例源码分享-匹配…...
微信分账系统供应链分润微信支付 (亲测源码)
搭建环境:nginxphp7.2mysql5.7 1.上传源码到网站根目录并解压 2.导入数据库文件到数据库 3.修改数据库链接文件/.env 4.设置运行目录为/public 5.伪静态设置成tp 6.后台地址:域名/zh9025.php 源码下载:https://download.csdn.net/down…...
C#里怎么样使用BinaryReader和BinaryWriter类?
C#里怎么样使用BinaryReader和BinaryWriter类? 二进制读取,有时候就比较有用。 比如在数据序列化到文件里,再从文件里读取出来。因为写入文件的类型有很多种,比如int/bool/long/byte/bytes等等。 又比如在串口通讯,或者网络通讯里,也需要把数据类型序列化到缓冲区,然后…...
k8s1.31版本最新版本集群使用容器镜像仓库Harbor
虚拟机 rocky9.4 linux master node01 node02 已部署k8s集群版本 1.31 方法 一 使用容器部署harbor (1) wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo -O /etc/yum.repos.d/docker-ce.repo yum -y install docker-ce systemctl enable docker…...
【大数据学习 | Spark-Core】Spark提交及运行流程
spark的集群运行结构 我们要选择第一种使用方式 命令组成结构 spark-submit [选项] jar包 参数 standalone集群能够使用的选项。 --master MASTER_URL #集群地址 --class class_name #jar包中的类 --executor-memory MEM #executor的内存 --executor-cores NUM # executor的…...
Sickos1.1 详细靶机思路 实操笔记
Sickos1.1 详细靶机思路 实操笔记 免责声明 本博客提供的所有信息仅供学习和研究目的,旨在提高读者的网络安全意识和技术能力。请在合法合规的前提下使用本文中提供的任何技术、方法或工具。如果您选择使用本博客中的任何信息进行非法活动,您将独自承担…...
Python 获取微博用户信息及作品(完整版)
在当今的社交媒体时代,微博作为一个热门的社交平台,蕴含着海量的用户信息和丰富多样的内容。今天,我将带大家深入了解一段 Python 代码,它能够帮助我们获取微博用户的基本信息以及下载其微博中的相关素材,比如图片等。…...
使用element-plus el-table中使用el-image层级冲突table表格会覆盖预览的图片等问题
在日常开发项目中 使用element-plus 中表格中使用 el-image的点击图片出现图片预览 会出现以下问题 表格一行会覆盖预览的图片 鼠标滑过也会显示表格 el-image 的预览层级和表格的层级冲突导致的。 解决方法:有两种一种是直接使用样式穿透 第二种推荐方法 使用官网推…...
leetcode_25_k个一组翻转链表
力扣:k个一组翻转链表 链接:https://leetcode.cn/problems/reverse-nodes-in-k-group/ 给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。 k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k…...
Python Selenium:Web自动化测试与爬虫开发
Python Selenium:Web自动化测试与爬虫开发 Python Selenium:Web自动化测试与爬虫开发安装Selenium设置WebDriver基础示例页面元素交互处理JavaScript和Cookies浏览器控制屏幕截图Headless Mode结束会话错误处理与调试 ***本文由AI辅助生成*** Python Se…...
C语言菜鸟入门·关键字·int的用法
目录 1. int关键字 1.1 取值范围 1.2 符号类型 1.3 运算 1.3.1 加法运算() 1.3.2 减法运算(-) 1.3.3 乘法运算(*) 1.3.4 除法运算(/) 1.3.5 取余运算(%) 1.3.6 自增()与自减(--) 1.3.7 位运算 2. 更多关键字 1. int关键字 int 是一个关键字࿰…...
Java语言编程,通过阿里云mongo数据库监控实现数据库的连接池优化
一、背景 线上程序连接mongos超时,mongo监控显示连接数已使用100%。 java程序报错信息: org.mongodb.driver.connection: Closed connection [connectionId{localValue:1480}] to 192.168.10.16:3717 because there was a socket exception raised by…...
论文阅读--Evidence for the utility of quantum computing before fault tolerance
量子计算有望在某些问题上提供比传统计算更快的速度。然而,实现其全部潜力的最大障碍是这些系统固有的噪声。这一挑战被广泛接受的解决方案是实现容错量子电路,而这超出了当前处理器的能力范围。我们在此报告了在嘈杂的127 量子比特处理器上进行的实验&a…...
Maven的安装——给Idea配置Maven
一、什么是Maven? Maven是一个开源的项目管理工具,它主要用于Java项目的构建、依赖管理和项目生命周期管理。 二、准备环境 maven安装之前,我们要先安装jdk,确保你已经安装了jdk环境。可以通过【win】【r】打开任务管理器,输入…...
【虚拟机】VMWare的CentOS虚拟机断电或强制关机出现问题
VMware 虚拟机因为笔记本突然断电故障了,开机提示“Entering emergency mode. Exit the shell to continue.”,如下图所示: 解决方法:输入命令: xfs_repair -v -L /dev/dm-0 注:报 no such file or direct…...
如何在WPF中嵌入其它程序
在WPF中嵌入其它程序,这里提供两种方案 一、使用WindowsFormHost 使用步骤如下 1、添加WindowsFormsIntegration和System.Windows.Forms引用 2、在界面上放置WindowsFormHost和System.Windows.Forms.Panel 1 <Grid> 2 <WindowsFormsHost> 3…...
集合Queue、Deque、LinkedList、ArrayDeque、PriorityQueue详解
1、 Queue与Deque的区别 在研究java集合源码的时候,发现了一个很少用但是很有趣的点:Queue以及Deque; 平常在写leetcode经常用LinkedList向上转型Deque作为栈或者队列使用,但是一直都不知道Queue的作用,于是就直接官方…...
实战 | C#中使用YoloV8和OpenCvSharp实现目标检测 (步骤 + 源码)
导 读 本文主要介绍在C#中使用YoloV8实现目标检测,并给详细步骤和代码。 详细步骤 【1】环境和依赖项。 需先安装VS2022最新版,.NetFramework8.0,然后新建项目,nuget安装 YoloSharp,YoloSharp介绍: https://github.com/dme-compunet/YoloSharp 最新版6.0.1,本文…...
OpenCV基本图像处理操作(六)——直方图与模版匹配
直方图 cv2.calcHist(images,channels,mask,histSize,ranges) images: 原图像图像格式为 uint8 或 float32。当传入函数时应 用中括号 [] 括来例如[img]channels: 同样用中括号括来它会告函数我们统幅图 像的直方图。如果入图像是灰度图它的值就是 [0]如果是彩色图像 的传入的…...
Hive离线数仓结构分析
Hive离线数仓结构 首先,在数据源部分,包括源业务库、用户日志、爬虫数据和系统日志,这些都是数据的源头。这些数据通过Sqoop、DataX或 Flume 工具进行提取和导入操作。这些工具负责将不同来源的数据传输到基于 Hive 的离线数据仓库中。 在离线…...
《macOS 开发环境配置与应用开发》
一、引言 macOS 作为一款强大而流行的操作系统,为开发者提供了丰富的开发机会和优秀的开发环境。无论是开发原生的 macOS 应用,还是进行跨平台开发,了解和掌握 macOS 开发环境的配置以及应用开发的方法至关重要。本文将详细介绍 macOS 开发环…...
DataGear 5.2.0 发布,数据可视化分析平台
DataGear 企业版 1.3.0 已发布,欢迎体验! http://datagear.tech/pro/ DataGear 5.2.0 发布,图表插件支持定义依赖库、严重 BUG 修复、功能改进、安全增强,具体更新内容如下: 重构:各模块管理功能访问路径…...
Knife4j快速入门
1 概述 Knife4j是一个用于生成和展示API文档的工具,同时它还提供了在线调试的功能,下图是其工作界面。 了解: Knife4j有多个版本,最新版的Knife4j基于开源项目springdoc-openapi,这个开源项目的核心功能就是根据Sprin…...
pcap_set_buffer_size()函数
功能简介 pcap_set_buffer_size()函数主要用于设置数据包捕获的内核缓冲区大小。这个缓冲区是操作系统内核用于临时存储捕获到的数据包的区域。通过调整缓冲区大小,可以在一定程度上优化数据包捕获的性能,特别是在高流量网络环境或者需要长时间捕获数据包…...
Ubuntu24.04——软件包系统已损坏
如果你在使用 Ubuntu 时遇到“软件包系统已损坏”的问题,可以尝试以下步骤来修复它: 更新软件包列表: 打开终端,运行以下命令以更新软件包列表: sudo apt update修复损坏的软件包: 运行以下命令来修复损坏的…...
项目实战:Vue3开发一个购物车
这段HTML代码实现了一个简单的购物车实战小项目的前端页面,结合了Vue.js框架来实现数据响应式和交互逻辑。页面展示了购物车中的商品项,每个商品项有增减数量的按钮,并且能显示商品总数以及目前固定为0元的商品总价和总价计算。 【运用响应式…...
OpenOCD之J-Link下载
1.下载USB Dirver Tool.exe,选择J-Link dirver,替换成WinUSB驱动。(⭐USB Dirver Tool工具可将J-Link从WinUSB驱动恢复为默认驱动⭐) 下载方式 ①官方网址:https://visualgdb.com/UsbDriverTool/ ②笔者的CSDN链接&…...
C++中定义类型名的方法
什么是 C 中的类型别名和 using 声明? 类型别名与using都是为了提高代码的可读性。 有两种方法可以定义类型别名 一种是使用关键字typedef起别名使用别名声明来定义类型的别名,即使用using. typedef 关键字typedef作为声明语句中的基本数据类型的一…...
241124学习日志——[CSDIY] [ByteDance] 后端训练营 [14]
CSDIY:这是一个非科班学生的努力之路,从今天开始这个系列会长期更新,(最好做到日更),我会慢慢把自己目前对CS的努力逐一上传,帮助那些和我一样有着梦想的玩家取得胜利!!&…...
丹摩|丹摩智算平台深度评测
1. 丹摩智算平台介绍 随着人工智能和大数据技术的快速发展,越来越多的智能计算平台涌现,为科研工作者和开发者提供高性能计算资源。丹摩智算平台作为其中的一员,定位于智能计算服务的提供者,支持从数据处理到模型训练的全流程操作…...
VSCode 快捷键
箭头函数 安装VSCODE插件用于在编辑器中生成ES6语法的JavaScript的代码段(支持JavaScript和Typescript)。 安装成功后输入an回车就可以了 (params) > {} 1、显示快捷键列表 快捷键:⌘ K S 可以通过上述按键显示vscode的快捷键列表&am…...
Java基础-Java多线程机制
(创作不易,感谢有你,你的支持,就是我前行的最大动力,如果看完对你有帮助,请留下您的足迹) 目录 一、引言 二、多线程的基本概念 1. 线程与进程 2. 多线程与并发 3. 多线程的优势 三、Java多线程的实…...
【创建型设计模式】单例模式
【创建型设计模式】单例模式 这篇博客接下来几篇都将阐述设计模式相关内容。 接下来的顺序大概是:单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式。 一、什么是单例模式 单例模式是一种创建型设计模式,它保证一个类仅有一个实例&#…...