SpringApplication之prepareEnvironment

Environment是Spring提供的一个接口,代表了当前Spring程序运行的环境,包含了两层含义:profiles和properties,前者用于环境类型的版本隔离,后者是对properties文件中key-value键值对内容的抽象。关于properties操作的方法主要都是通过接口PropertyResolver来暴露的。而properties在项目中主要是通过以properties文件来呈现的,在java中的抽象为java.util.Properties。而在Spring中,通过PropertySource进一步抽象(给每个资源一个name),当然除了Properties,Map、ServletContext、ServletConfig也可以作为其source。

/**
 * Create a new {@code PropertySource} with the given name and source object.
 */
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;
	// 这个Source可以代表一切key-value的模型
	this.source = source;
}

比如MapPropertySource

public class MapPropertySource extends EnumerablePropertySource<Map<String, Object>> {

	public MapPropertySource(String name, Map<String, Object> source) {
		super(name, source);
	}

}

在Spring中,PropertySource一般并不会单独使用,而是被封装在PropertySources中。这样再通过PropertyResolver对资源进行优先级的搜索(通过表数据结构即可)。

// MutablePropertySources针对PropertySource通过容器进行封装 可以看到这个类是线程安全的
private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();


/**
 * Create a new {@link MutablePropertySources} object.
 */
public MutablePropertySources() {
}

其实除了对资源文件的抽象外,在ConfigurablePropertyResolver这个接口中还关联到了另一个重要的服务,就是ConversionService,因为在资源文件中的内容都是String类型,但是真实的对象可能是其他类型的,之间必然存在转换,所以类型转换服务是必不可少的。这种类型转换非常多,因为针对特定的类型需要特别的转换器,Spring中为了更好管理这些转换器,因此也提供了ConverterRegistry接口,用于注册和管理各种转换器。比如ApplicationConversionService这个转换器,在构造的时候就填充了各种有用的转换器,为了性能,用到了单例模式。

private static volatile ApplicationConversionService sharedInstance;

public ApplicationConversionService() {
	this(null);
}

public ApplicationConversionService(StringValueResolver embeddedValueResolver) {
	if (embeddedValueResolver != null) {
		setEmbeddedValueResolver(embeddedValueResolver);
	}
	configure(this);
}
/**
 * Configure the given {@link FormatterRegistry} with formatters and converters
 * appropriate for most Spring Boot applications.
 * @param registry the registry of converters to add to (must also be castable to
 * ConversionService, e.g. being a {@link ConfigurableConversionService})
 * @throws ClassCastException if the given FormatterRegistry could not be cast to a
 * ConversionService
 */
public static void configure(FormatterRegistry registry) {
	DefaultConversionService.addDefaultConverters(registry);
	DefaultFormattingConversionService.addDefaultFormatters(registry);
	addApplicationFormatters(registry);
	addApplicationConverters(registry);
}

最后在应用程序级别,很多时候,beans不需要直接与Environment打交道,但是可能存在一些占位符信息(${…})需要在实例化之前进行解析,所以这个时候就需要提供一个工具类方便进行处理,这就是PropertySourcesPlaceholderConfigurer(一个bean工厂后置处理器)这个类的作用了。

在Spring Boot中主要使用的是ConfigurableEnvironment,针对于Environment进行了额外的扩展,通过它可以方便的进行默认或激活profiles的设置以及对内部资源的管理、并通过ConfigurablePropertyResolver来对所需要的属性进行设置和验证、转换服务的设置等等。

比如针对资源文件的操作,就包括移除、再排序、替换,如下代码所示:

// Example: adding a new property source with highest search priority
ConfigurableEnvironment environment = new StandardEnvironment();
MutablePropertySources propertySources = environment.getPropertySources();
Map<String, String> myMap = new HashMap<>();
myMap.put("xyz", "myValue");
propertySources.addFirst(new MapPropertySource("MY_MAP", myMap));
 
// Example: removing the default system properties property source
MutablePropertySources propertySources = environment.getPropertySources();
propertySources.remove(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME);
   
// Example: mocking the system environment for testing purposes
MutablePropertySources propertySources = environment.getPropertySources();
MockPropertySource mockEnvVars = new MockPropertySource().withProperty("xyz", "myValue");
propertySources.replace(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, mockEnvVars);

以下就是这些类之间的关系图。

在这里插入图片描述

从上面看来,Environment是一个看起来非常复杂的体系,因此在SpringApplication中有一个单独的方法用于进行Environment的准备工作,也就是prepareEnvironment方法。

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
		ApplicationArguments applicationArguments) {
	// Create and configure the environment
	// ConfigurableEnvironment获取与创建,但第一次基本都是null,因此直接创建
	ConfigurableEnvironment environment = getOrCreateEnvironment();
	// 配置环境-》模板方法代理configureProfiles和configureProfiles
	// 这里配置了转换服务 并设置了命令行参数资源 然后解析当前激活版本 
	configureEnvironment(environment, applicationArguments.getSourceArgs());
	// 绑定环境资源信息
	ConfigurationPropertySources.attach(environment);
	// 发布ApplicationEnvironmentPreparedEvent事件 其中就包括了Spring Boot默认配置文件的读取
	listeners.environmentPrepared(environment);
	bindToSpringApplication(environment);
	if (!this.isCustomEnvironment) {
		environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
				deduceEnvironmentClass());
	}
	ConfigurationPropertySources.attach(environment);
	return environment;
}
ConfigurableEnvironment 对象创建

如果SpringApplication对象中的environment属性不为空,则直接返回这个属性对象。如果不存在,则根据webApplicationType来构造不同的ConfigurableEnvironment的实现类。在SpringApplication对象构造的过程中会判断出webApplicationType类型,前两者都是在web模式下,第三个非web模式。
官方说明如下:
在这里插入图片描述

private ConfigurableEnvironment getOrCreateEnvironment() {
	if (this.environment != null) {
		return this.environment;
	}
	// 根据webApplicationType类型来判断ConfigurableEnvironment的类型
	switch (this.webApplicationType) {
	case SERVLET:
		return new StandardServletEnvironment();
	case REACTIVE:
		return new StandardReactiveWebEnvironment();
	default:
		return new StandardEnvironment();
	}
}

StandardEnvironment使用的默认的构造方法,初始化时首先初始化父类的构造方法,也就是

/**
 * Create a new {@code Environment} instance, calling back to
 * {@link #customizePropertySources(MutablePropertySources)} during construction to
 * allow subclasses to contribute or manipulate {@link PropertySource} instances as
 * appropriate.
 * @see #customizePropertySources(MutablePropertySources)
 */
public AbstractEnvironment() {
	customizePropertySources(this.propertySources);
}

// 重要的属性
private final MutablePropertySources propertySources = new MutablePropertySources();

private final ConfigurablePropertyResolver propertyResolver =
		new PropertySourcesPropertyResolver(this.propertySources);

// 需要子类去覆盖的方法
protected void customizePropertySources(MutablePropertySources propertySources) {
}

其中customizePropertySources这个抽象回调方法需要子类去实现,相当重要(保证配置优先级)。

Customize the set of PropertySource objects to be searched by this Environment during calls to getProperty(String) and related methods.
Subclasses that override this method are encouraged to add property sources using MutablePropertySources.addLast(PropertySource) such that further subclasses may call super.customizePropertySources() with predictable results. For example:

 public class Level1Environment extends AbstractEnvironment {
     @Override
     protected void customizePropertySources(MutablePropertySources propertySources) {
         super.customizePropertySources(propertySources); // no-op from base class
         propertySources.addLast(new PropertySourceA(...));
         propertySources.addLast(new PropertySourceB(...));
     }
 }

 public class Level2Environment extends Level1Environment {
     @Override
     protected void customizePropertySources(MutablePropertySources propertySources) {
         super.customizePropertySources(propertySources); // add all from superclass
         propertySources.addLast(new PropertySourceC(...));
         propertySources.addLast(new PropertySourceD(...));
     }
 }

In this arrangement, properties will be resolved against sources A, B, C, D in that order. That is to say that property source “A” has precedence over property source “D”. If the Level2Environment subclass wished to give property sources C and D higher precedence than A and B, it could simply call super.customizePropertySources after, rather than before adding its own:

 public class Level2Environment extends Level1Environment {
     @Override
     protected void customizePropertySources(MutablePropertySources propertySources) {
         propertySources.addLast(new PropertySourceC(...));
         propertySources.addLast(new PropertySourceD(...));
         super.customizePropertySources(propertySources); // add all from superclass
     }
 }

The search order is now C, D, A, B as desired.
Beyond these recommendations, subclasses may use any of the add*, remove, or replace methods exposed by MutablePropertySources in order to create the exact arrangement of property sources desired.
The base implementation registers no property sources.
Note that clients of any ConfigurableEnvironment may further customize property sources via the getPropertySources() accessor, typically within an ApplicationContextInitializer. For example:

ConfigurableEnvironment env = new StandardEnvironment();
env.getPropertySources().addLast(new PropertySourceX(...));

A warning about instance variable access

Instance variables declared in subclasses and having default initial values should not be accessed from within this method. Due to Java object creation lifecycle constraints, any initial value will not yet be assigned when this callback is invoked by the AbstractEnvironment() constructor, which may lead to a NullPointerException or other problems. If you need to access default values of instance variables, leave this method as a no-op and perform property source manipulation and instance variable access directly within the subclass constructor. Note that assigning values to instance variables is not problematic; it is only attempting to read default values that must be avoided.

子类定义的属性在父类构造的时候还没有初始化,不能在这个回调方法中处理,即便定义了初始值也会出现空指针异常。对于这种情况,应该在子类的构造中单独处理,而不是放在这个回调方法中。

那么StandardEnvironment中是怎么覆盖这个回调方法的呢?

@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()));
}

对于java标准环境而言,少不了系统属性配置和系统环境配置,以上在propertySources中添加两个属性资源,如下所示:

systemEnvironment = (Map) System.getProperties();
systemProperties = (Map) System.getenv();

这里有一个扩展点:

在获取获取系统环境配置

@Override
@SuppressWarnings({"rawtypes", "unchecked"})
public Map<String, Object> getSystemEnvironment() {
	if (suppressGetenvAccess()) {
		return Collections.emptyMap();
	}
	try {
		return (Map) System.getenv();
	}

那这个suppressGetenvAccess是什么意思呢?

protected boolean suppressGetenvAccess() {
	return SpringProperties.getFlag(IGNORE_GETENV_PROPERTY_NAME);
}

从这可以看出来,是从一个SpringProperties来中获取一个标记,这个SpringProperties是哪里来的呢?原来可以在项目的classpath目录下定义一个名字spring.properties的文件,在其中可以添加一些属性值,当SpringProperties类初始化的时候,会读取这个文件。也可以自己通过setProperty来设置一些属性。

private static final String PROPERTIES_RESOURCE_LOCATION = "spring.properties";

private static final Log logger = LogFactory.getLog(SpringProperties.class);

private static final Properties localProperties = new Properties();


static {
	try {
		ClassLoader cl = SpringProperties.class.getClassLoader();
		URL url = (cl != null ? cl.getResource(PROPERTIES_RESOURCE_LOCATION) :
				ClassLoader.getSystemResource(PROPERTIES_RESOURCE_LOCATION));
		if (url != null) {
			logger.debug("Found 'spring.properties' file in local classpath");
			InputStream is = url.openStream();
			try {
				localProperties.load(is);
			}
			finally {
				is.close();
			}
		}
	}
	catch (IOException ex) {
		if (logger.isInfoEnabled()) {
			logger.info("Could not load 'spring.properties' file from local classpath: " + ex);
		}
	}
}

初始化后对象各属性内容如下:
在这里插入图片描述

配置环境属性

通过模板方法设置propertySource和profiles

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
	if (this.addConversionService) {
		// 设置转换服务信息
		ConversionService conversionService = ApplicationConversionService.getSharedInstance();
		environment.setConversionService((ConfigurableConversionService) conversionService);
	}
	// 将命令行参数作为一个资源设置到环境当中
	configurePropertySources(environment, args);
	// 设置当前项目的版本
	configureProfiles(environment, args);
}
  1. 添加转换服务
// 为了线程安全 这个属性需要添加volatile 
private static volatile ApplicationConversionService sharedInstance;

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

如果是第一次来获取,需要构造这个类

public ApplicationConversionService() {
	this(null);
}

public ApplicationConversionService(StringValueResolver embeddedValueResolver) {
	if (embeddedValueResolver != null) {
		setEmbeddedValueResolver(embeddedValueResolver);
	}
	// 此时会配置各种默认转换器
	configure(this);
}

这些转换器有一类属于Formatter
在这里插入图片描述
另一类属于ConditionalGenericConverter
在这里插入图片描述

  1. 设置命令行参数
    如果存在命令行参数的话,则将命令行参数(key-value)也作为一种资源配置到环境属性中。名称为commandLineArgs。这里用到了一个CompositePropertySource ,将同名的资源放到一起。
protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
	MutablePropertySources sources = environment.getPropertySources();
	if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
		sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
	}
	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));
		}
	}
}
  1. 设置当前激活的版本信息
    这里只能从系统配置属性、系统环境属性以及命令行参数中获得激活的版本,当前还没有读取到默认的application.properties文件
/**
 * Configure which profiles are active (or active by default) for this application
 * environment. Additional profiles may be activated during configuration file
 * processing via the {@code spring.profiles.active} property.
 * @param environment this application's environment
 * @param args arguments passed to the {@code run} method
 * @see #configureEnvironment(ConfigurableEnvironment, String[])
 * @see org.springframework.boot.context.config.ConfigFileApplicationListener
 */
protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
	environment.getActiveProfiles(); // ensure they are initialized
	// But these ones should go first (last wins in a property key clash)
	Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
	// 此时会读取key值为spring.profiles.active的属性值,由于当前只读取了系统配置属性和系统环境属性,另外还有命令行参数,所以一般也就是命令行设置这个参数了
	profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
	environment.setActiveProfiles(StringUtils.toStringArray(profiles));
}
绑定资源信息

通过ConfigurationPropertySources这个类对系统资源信息进行包装并绑定,其他地方可以方便的获取资源信息(静态方法)。包装成SpringConfigurationPropertySource类,这是另一个PropertySource类。

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)));
	}
}

这里面看似简单,其实包括一大堆的类,绑定的目的就是方便进行查找。此处不详细探讨。
在这里插入图片描述

发布ApplicationEnvironmentPreparedEvent事件并监听

在这里插入图片描述
首先是ConfigFileApplicationListener监听到事件

@Override
public void onApplicationEvent(ApplicationEvent event) {
	if (event instanceof ApplicationEnvironmentPreparedEvent) {
		// 当前执行
		onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
	}
	if (event instanceof ApplicationPreparedEvent) {
		onApplicationPreparedEvent(event);
	}
}
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
	// 通过SPI机制加载EnvironmentPostProcessor类型的后置处理器
	List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
	postProcessors.add(this);
	AnnotationAwareOrderComparator.sort(postProcessors);
	for (EnvironmentPostProcessor postProcessor : postProcessors) {
		postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
	}
}

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

在这里插入图片描述
其中SystemEnvironmentPropertySourceEnvironmentPostProcessor会将SystemEnvironmentPropertySource替换成OriginAwareSystemEnvironmentPropertySource,可用于为每个系统环境属性追踪SystemEnvironmentOrigin。
而SpringApplicationJsonEnvironmentPostProcessor会将键值为spring.application.json或者SPRING_APPLICATION_JSON的属性转换为一个JsonPropertySource并添加到环境属性中,而且优先级高于系统属性

@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
	MutablePropertySources propertySources = environment.getPropertySources();
	propertySources.stream().map(JsonPropertyValue::get).filter(Objects::nonNull).findFirst()
			.ifPresent((v) -> processJson(environment, v));
}

private void processJson(ConfigurableEnvironment environment, JsonPropertyValue propertyValue) {
	JsonParser parser = JsonParserFactory.getJsonParser();
	Map<String, Object> map = parser.parseMap(propertyValue.getJson());
	if (!map.isEmpty()) {
		addJsonPropertySource(environment, new JsonPropertySource(propertyValue, flatten(map)));
	}
}

ConfigFileApplicationListener会读取系统默认的配置文件,比如application.properties或者application.yml。读取完之后还会根据当前系统的激活版本判断是否需要继续读取其他配置文件,比如当前激活版本为web,则会考虑继续读取application-web.properties或者application-web.yml文件。

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

/**
 * Add config file property sources to the specified environment.
 * @param environment the environment to add source to
 * @param resourceLoader the resource loader
 * @see #addPostProcessors(ConfigurableApplicationContext)
 */
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
	// 为每个以random开头的键值生成随机值 因为在Spring Boot配置文件中可以配置默认值
	RandomValuePropertySource.addToEnvironment(environment);
	// 加载默认资源
	new Loader(environment, resourceLoader).load();
}
···

```java
// org.springframework.boot.env.RandomValuePropertySource

/**
 * Name of the random {@link PropertySource}.
 */
public static final String RANDOM_PROPERTY_SOURCE_NAME = "random";

public static void addToEnvironment(ConfigurableEnvironment environment) {
	environment.getPropertySources().addAfter(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
			new RandomValuePropertySource(RANDOM_PROPERTY_SOURCE_NAME));
	logger.trace("RandomValuePropertySource add to Environment");
}

在这里插入图片描述
通过Loader类来加载资源

Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
	this.environment = environment;
	this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment);
	this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader();
	this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
			getClass().getClassLoader());
}

此处加载的逻辑请参看另一个博客:
https://blog.csdn.net/m0_37607945/article/details/106577833

总结:
prepareEnvironment主要用于处理环境信息,而环境信息其实主要就是各种配置文件以及系统参数、环境参数这些。这个地方最主要的扩展点就是一个ApplicationEnvironmentPreparedEvent事件的发布与监听。默认配置文件的加载就是在这个时候进行加载的,另外Spring Cloud也是通过监听这个事件保证在Spring Boot的配置文件加载之前进行容器的初始化的。注意到这个顺序,很容易理解为啥Spring Boot的配置文件对Spring Cloud没有任何意思。
在这里插入图片描述

D:\nacos\bin>startup.cmd -m standalone "nacos is starting with standalone" 00:15:46.952 [main] ERROR org.springframework.boot.SpringApplication - Application run failed java.lang.RuntimeException: java.io.IOException: Unable to create directory D:\nacos\logs at com.alibaba.nacos.core.listener.StartingApplicationListener.makeWorkDir(StartingApplicationListener.java:208) at com.alibaba.nacos.core.listener.StartingApplicationListener.environmentPrepared(StartingApplicationListener.java:78) at com.alibaba.nacos.core.code.SpringApplicationRunListener.environmentPrepared(SpringApplicationRunListener.java:67) at org.springframework.boot.SpringApplicationRunListeners.lambda$environmentPrepared$2(SpringApplicationRunListeners.java:66) at java.util.ArrayList.forEach(ArrayList.java:1259) at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:120) at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:114) at org.springframework.boot.SpringApplicationRunListeners.environmentPrepared(SpringApplicationRunListeners.java:65) at org.springframework.boot.SpringApplication.prepareEnvironment(SpringApplication.java:343) at org.springframework.boot.SpringApplication.run(SpringApplication.java:301) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1303) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1292) at com.alibaba.nacos.Nacos.main(Nacos.java:35) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:49) at org.springframework.boot.loader.Launcher.launch(Launcher.java:108) at org.springframework.boot.loader.Launcher.launch(Launcher.java:58) at org.springframework.boot.loader.PropertiesLauncher.main(PropertiesLauncher.java:467) Caused by: java.io.IOException: Unable to create directory D:\nacos\logs at org.apache.commons.io.FileUtils.forceMkdir(FileUtils.java:1391) at com.alibaba.nacos.sys.utils.DiskUtils.forceMkdir(DiskUtils.java:283) at com.alibaba.nacos.core.listener.StartingApplicationListener.makeWorkDir(StartingApplicationListener.java:206) ... 20 common frames omitted
07-14
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lang20150928

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值