Spring Eureka 源码解析
本文将简要分析一下关于 Spring Eureka 相关的一些必要的源代码,对应的版本:Spring Cloud 2021.0.1
@EnableEurekaServer 注解
@EnableEurekaServer 注解标记当前的应用程序作为一个注册中心,查看 @EnableEurekaServer 的源代码,具体内容如下所示:
package org.springframework.cloud.netflix.eureka.server;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EurekaServerMarkerConfiguration.class)
public @interface EnableEurekaServer {
}
可以看到,该注解导入了 EurekaServerMarkerConfiguration 配置类,继续查看 EurekaServerMarkerConfiguration 对应的源代码,具体内容如下所示:
package org.springframework.cloud.netflix.eureka.server;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class EurekaServerMarkerConfiguration {
@Bean
public Marker eurekaServerMarkerBean() {
return new Marker();
}
class Marker {
}
}
可以看到,该配置类只是定义了一个 Mark 类型的 Bean。可以看到,Mark 就是在 EurekaServerMarkerConfiguration 中定义的一个类,该类并没有任何特殊的地方。
由于 Spring-Boot 的自动配置是通过加载 spring.factories 文件中的内容来完成自动配置的,因此,可以试着从当前 EurekaServerMarkerConfiguration 配置类所在的包的 spring.factories 文件入手来进一步的进行分析
查看 EurekaServerMarkerConfiguration 对应的包的 spring.factories 文件,具体内容如下所示:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration
可以看到,在 spring.factories 文件中只有一个自动配置类 .EurekaServerAutoConfiguratio,查看该类对应的源代码,具体的内容如下所示:
package org.springframework.cloud.netflix.eureka.server;
// 省略一些其它的包的导入
@Configuration(proxyBeanMethods = false)
@Import(EurekaServerInitializerConfiguration.class)
/*
可以看到,在这里定义了一个条件 Bean,只有当这个 Bean 出现在
Spring 上下文环境中时,才会将 EurekaServerAutoConfiguration Bean
加载到 Spring 应用的上下文中
*/
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({ EurekaDashboardProperties.class, InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration implements WebMvcConfigurer {
// 省略具体的源代码
}
也就是说,只有 Spring 中存在 EurekaServerMarkerConfiguration.Mark 类型的 Bean 时,才会将 EurekaServerAutoConfiguration 自动配置类加载到 Spring 上下文中,从而实现 Eureka 注册中心的功能
DashBoard Path
我们知道,当成功启动一个 Spring Eureka 注册中心时,访问对应的 IP 和端口即可看到对应的控制台界面,这里的访问路径是通过 EurekaDashboardProperties 来进行配置的。
查看 EurekaDashboardProperties 对应的源代码,如下所示:
package org.springframework.cloud.netflix.eureka.server;
import org.springframework.boot.context.properties.ConfigurationProperties;
/*
这里通过配置属性的方式来进行 dashboard 访问路径的配置
*/
@ConfigurationProperties("eureka.dashboard")
public class EurekaDashboardProperties {
// 默认的 dashboard 访问路径
private String path = "/";
// 省略一部分不是特别重要的代码
}
这就是为什么直接访问注册中心的 IP 加端口号就能访问到控制台的原因
EnableDiscoveryClient 注解
实际上,对于普通的应用服务来讲(为了和注册中心区分,将非注册中心的服务称为客户端),加上 @EnableDiscoveryClient 就能够实现服务的自动注册和发现,@EnableDiscoveryClient 注解对应的源代码如下所示:
package org.springframework.cloud.client.discovery;
// 省略一部分不太重要的代码
public @interface EnableDiscoveryClient {
/*
可以看到,这里的默认值为 true,因此默认情况下,
会将当前的服务注册到注册中心
*/
boolean autoRegister() default true;
}
这是加上 @EnableDiscoveryClient 注解后的默认属性,不是
即使在启动类上没有加上 @EnableDiscoveryClient 注解,该服务依旧能够被注册中心发现,这是由于在 Spring Eureka Client 的自动配置类中默认了这种行为。
查看 spring-cloud-starter-netflix-eureka-client 包中的 spring.factories 文件,具体的内容如下所示:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaClientConfigServerAutoConfiguration,\
org.springframework.cloud.netflix.eureka.config.DiscoveryClientOptionalArgsConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration,\
org.springframework.cloud.netflix.eureka.reactive.EurekaReactiveDiscoveryClientConfiguration,\
org.springframework.cloud.netflix.eureka.loadbalancer.LoadBalancerEurekaAutoConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaConfigServerBootstrapConfiguration
org.springframework.boot.BootstrapRegistryInitializer=\
org.springframework.cloud.netflix.eureka.config.EurekaConfigServerBootstrapper
其中,org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration 自动配置类定义了这一默认的行为,具体的源代码如下所示:
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class)
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
@ConditionalOnDiscoveryEnabled
@AutoConfigureBefore({ CommonsClientAutoConfiguration.class, ServiceRegistryAutoConfiguration.class })
@AutoConfigureAfter(name = { "org.springframework.cloud.netflix.eureka.config.DiscoveryClientOptionalArgsConfiguration",
"org.springframework.cloud.autoconfigure.RefreshAutoConfiguration",
// 在这个自动配置类中定义了服务发现的默认行为
"org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration",
"org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration" })
public class EurekaClientAutoConfiguration {
// 省略一部分不太重要的代码
}
继续查看 EurekaDiscoveryClientConfiguration 配置类的源代码,相关的内容如下所示:
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class)
// 重点在这里,可以看到,这里的默认值为 true,即默认允许被自动发现和注册
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
@ConditionalOnDiscoveryEnabled
@ConditionalOnBlockingDiscoveryEnabled
public class EurekaDiscoveryClientConfiguration {
// 省略一部分不太重要的代码
}
EurekaServerAutoConfiguration 解析
前文提到,在 EurekaServerMarkerConfiguration.Marker 类型的 Bean 装载到 Spring 上下文环境中时,才会加载 EurekaServerAutoConfiguration,这个配置类定义了有关 Eureka 服务端的相关配置,在这里进行简要的解析
相关属性介绍
EurekaServerAutoConfiguration 定义的相关属性如下:
// 省略一部分包的导入以及注解的声明
public class EurekaServerAutoConfiguration implements WebMvcConfigurer {
@Autowired
private ApplicationInfoManager applicationInfoManager;
/*
Eureka 服务端相关的配置
*/
@Autowired
private EurekaServerConfig eurekaServerConfig;
/*
Eureka 客户端的相关配置
*/
@Autowired
private EurekaClientConfig eurekaClientConfig;
@Autowired
private EurekaClient eurekaClient;
@Autowired
private InstanceRegistryProperties instanceRegistryProperties;
}
服务启动
有关集群和应用服务程序的注册对应的源代码:
// 省略一部分包的导入以及注解的声明
public class EurekaServerAutoConfiguration implements WebMvcConfigurer {
// 省略部分源代码
/*
初始化集群注册表
*/
@Bean
public PeerAwareInstanceRegistry peerAwareInstanceRegistry(ServerCodecs serverCodecs) {
this.eurekaClient.getApplications(); // force initialization
return new InstanceRegistry(this.eurekaServerConfig, this.eurekaClientConfig, serverCodecs, this.eurekaClient,
this.instanceRegistryProperties.getExpectedNumberOfClientsSendingRenews(),
this.instanceRegistryProperties.getDefaultOpenForTrafficCount());
}
/*
初始化集群节点集合
*/
@Bean
@ConditionalOnMissingBean
public PeerEurekaNodes peerEurekaNodes(PeerAwareInstanceRegistry registry, ServerCodecs serverCodecs,
ReplicationClientAdditionalFilters replicationClientAdditionalFilters) {
return new RefreshablePeerEurekaNodes(registry, this.eurekaServerConfig, this.eurekaClientConfig, serverCodecs,
this.applicationInfoManager, replicationClientAdditionalFilters);
}
/*
Eureka 服务端上下文
*/
@Bean
@ConditionalOnMissingBean
public EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs, PeerAwareInstanceRegistry registry,
PeerEurekaNodes peerEurekaNodes) {
return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs, registry, peerEurekaNodes,
this.applicationInfoManager);
}
/*
Eureka 服务端启动类
*/
@Bean
public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry,
EurekaServerContext serverContext) {
return new EurekaServerBootstrap(this.applicationInfoManager, this.eurekaClientConfig, this.eurekaServerConfig,
registry, serverContext);
}
}
具体的初始化类,可以看一下 EurekaServerAutoConfiguration 引入的注解:
@Configuration(proxyBeanMethods = false)
/*
可以看到,这里引入了 EurekaServerInitializerConfiguration 类型的 Bean,
该 Bean 的作用是用于初始化 Eureka Server
*/
@Import(EurekaServerInitializerConfiguration.class)
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({ EurekaDashboardProperties.class, InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration implements WebMvcConfigurer {
// 省略一部分具体的代码
}
继续查看 EurekaServerInitializerConfiguration 对应的源代码,如下所示:
// 省略一部分包的导入
@Configuration(proxyBeanMethods = false)
public class EurekaServerInitializerConfiguration
/*
注意这里的 SmartLifecycle 接口,这个接口继承了 Lifecycle,
用于管理 Spring Context 的生命周期
具体到当前分析的环境,由于是初始化 Eureka Server,因此需要调用
start() 方法
*/
implements ServletContextAware, SmartLifecycle, Ordered {
private static final Log log = LogFactory.getLog(EurekaServerInitializerConfiguration.class);
@Autowired
private EurekaServerConfig eurekaServerConfig;
private ServletContext servletContext;
@Autowired
private ApplicationContext applicationContext;
@Autowired
private EurekaServerBootstrap eurekaServerBootstrap;
private boolean running;
private int order = 1;
@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
@Override
public void start() {
new Thread(() -> {
try {
// TODO: is this class even needed now?
/*
Eureka Server 启动之前的初始化
*/
eurekaServerBootstrap.contextInitialized(EurekaServerInitializerConfiguration.this.servletContext);
log.info("Started Eureka Server");
/*
由于 Eureka Server 已经启动了,因此发布 Eureka Server 注册中心的启动事件
*/
publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
// 设置 Eureka 的运行状态
EurekaServerInitializerConfiguration.this.running = true;
// 现在 Eureka Server 已经完全启动了,此时再发布一个 Eureka Server 启动事件
publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
} catch (Exception ex) {
// Help!
log.error("Could not initialize Eureka servlet context", ex);
}
}).start();
}
// 省略一部分代码
}
由于 EurekaServerInitializerConfiguration 实现了 SmartLifecycle,因此会被 Spring 容器进行相对应的处理,这里不做过多的介绍,有关 Spring IOC 部分的源码解析,可以查看 Spring IOC 容器源码分析_Javadoop,这是一篇关于 Spring IOC 源码分析比较好的文章。
在这里只需要知道由于 EurekaServerInitializerConfiguration 实现了 SmartLifecycle 接口,因此在 Spring 应用上下文的初始化过程中会调用 start() 方法即可
接下来继续分析 contextInitialized(ServletContext) 方法,该方法用于初始化启动 Eureka Server 之前的必要准备。对应的源代码如下所示:
public void contextInitialized(ServletContext context) {
try {
/*
这里的具体实现只是单纯地打印一行日志
*/
initEurekaEnvironment();
/*
这里初始化 Eureka Server 的上下文
*/
initEurekaServerContext();
context.setAttribute(EurekaServerContext.class.getName(), this.serverContext);
}
catch (Throwable e) {
log.error("Cannot bootstrap eureka server :", e);
throw new RuntimeException("Cannot bootstrap eureka server :", e);
}
}
比较关系的是 Eureka Server 上下文的设置,继续进入该方法,对应的源代码如下所示:
protected void initEurekaServerContext() throws Exception {
// For backward compatibility
JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH);
XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH);
/*
针对 AWS 的特殊处理,一般情况下可能用不到,略过
*/
if (isAws(this.applicationInfoManager.getInfo())) {
this.awsBinder = new AwsBinderDelegate(this.eurekaServerConfig, this.eurekaClientConfig, this.registry,this.applicationInfoManager);
this.awsBinder.start();
}
EurekaServerContextHolder.initialize(this.serverContext);
log.info("Initialized server context");
// Copy registry from neighboring eureka node
/*
邻接点的数据同步
*/
int registryCount = this.registry.syncUp();
/*
服务剔除
*/
this.registry.openForTraffic(this.applicationInfoManager, registryCount);
// Register all monitoring statistics.
EurekaMonitors.registerAllStats();
}
邻节点的数据同步
邻接点的同步 syncUp(),对应的源代码如下所示:
// 省略部分包的导入以及注解的声明
public class PeerAwareInstanceRegistryImpl
extends AbstractInstanceRegistry
implements PeerAwareInstanceRegistry {
// 省略部分其它代码
@Override
public int syncUp() {
// Copy entire entry from neighboring DS node
int count = 0;
for (int i = 0; ((i < serverConfig.getRegistrySyncRetries()) && (count == 0)); i++) {
if (i > 0) {
try {
Thread.sleep(serverConfig.getRegistrySyncRetryWaitMs());
} catch (InterruptedException e) {
logger.warn("Interrupted during registry transfer..");
break;
}
}
/*
apps 表示获取到的所有的应用信息
*/
Applications apps = eurekaClient.getApplications();
/*
双层 Map 结构,第一层 Map 存储应用程序
*/
for (Application app : apps.getRegisteredApplications()) {
/*
第二层存储应用程序对应的实例信息
*/
for (InstanceInfo instance : app.getInstances()) {
try {
/*
服务的注册实际执行的地方
*/
if (isRegisterable(instance)) {
register(instance, instance.getLeaseInfo().getDurationInSecs(), true);
count++;
}
} catch (Throwable t) {
logger.error("During DS init copy", t);
}
}
}
}
return count;
}
}
服务剔除
服务剔除对应 openForTraffic(ApplicationInfoManager, int) 方法,实际对应的对象的源代码如下:
// 省略部分包导入代码
public class PeerAwareInstanceRegistryImpl
extends AbstractInstanceRegistry
implements PeerAwareInstanceRegistry {
// 省略部分关系不大的代码
public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
// Renewals happen every 30 seconds and for a minute it should be a factor of 2.
this.expectedNumberOfClientsSendingRenews = count;
updateRenewsPerMinThreshold();
logger.info("Got {} instances from neighboring DS node", count);
logger.info("Renew threshold is: {}", numberOfRenewsPerMinThreshold);
this.startupTime = System.currentTimeMillis();
if (count > 0) {
this.peerInstancesTransferEmptyOnStartup = false;
}
DataCenterInfo.Name selfName = applicationInfoManager.getInfo().getDataCenterInfo().getName();
/*
这里也是针对 AWS 的处理,一般用不到,略过
*/
boolean isAws = Name.Amazon == selfName;
if (isAws && serverConfig.shouldPrimeAwsReplicaConnections()) {
logger.info("Priming AWS connections for all replicas..");
primeAwsReplicas(applicationInfoManager);
}
// 针对 AWS 的处理结束
logger.info("Changing status to UP");
applicationInfoManager.setInstanceStatus(InstanceStatus.UP);
super.postInit();
}
}
继续查看父类的 postInit() 方法,对应的源代码如下所示:
public abstract class AbstractInstanceRegistry
implements InstanceRegistry {
// 省略部分不太重要的代码
protected void postInit() {
renewsLastMin.start();
if (evictionTaskRef.get() != null) {
evictionTaskRef.get().cancel();
}
// 这里添加一个剔除任务
evictionTaskRef.set(new EvictionTask());
// 设置这个剔除任务的执行周期
evictionTimer.schedule(evictionTaskRef.get(),
serverConfig.getEvictionIntervalTimerInMs(),
serverConfig.getEvictionIntervalTimerInMs());
}
}
这个剔除任务的定义对应的源代码如下所示:
class EvictionTask extends TimerTask {
@Override
public void run() {
try {
long compensationTimeMs = getCompensationTimeMs();
logger.info("Running the evict task with compensationTime {}ms", compensationTimeMs);
evict(compensationTimeMs);
} catch (Throwable e) {
logger.error("Could not run the evict task", e);
}
}
}
evict(int) 对应的源代码比较长,这里就不再贴出。剔除工作交给 internalCancel(String, String, boolean) 方法来完成。
更加具体一点,internalCancel(String, String, boolean) 是按照实例 id 来删除双层 Map 中对应的元素来实现的。
参考:
[1]
https://mp.weixin.qq.com/s/Xfq5YCaSSc7WgOH6Yqz4zQhttps://mp.weixin.qq.com/s/Xfq5YCaSSc7WgOH6Yqz4zQ
Spring Eureka 源码解析的更多相关文章
- Spring Cloud系列(四):Eureka源码解析之客户端
一.自动装配 1.根据自动装配原理(详见:Spring Boot系列(二):Spring Boot自动装配原理解析),找到spring-cloud-netflix-eureka-client.jar的 ...
- Spring系列(三):Spring IoC源码解析
一.Spring容器类继承图 二.容器前期准备 IoC源码解析入口: /** * @desc: ioc原理解析 启动 * @author: toby * @date: 2019/7/22 22:20 ...
- spring事务源码解析
前言 在spring jdbcTemplate 事务,各种诡异,包你醍醐灌顶!最后遗留了一个问题:spring是怎么样保证事务一致性的? 当然,spring事务内容挺多的,如果都要讲的话要花很长时间, ...
- Spring IoC源码解析之invokeBeanFactoryPostProcessors
一.Bean工厂的后置处理器 Bean工厂的后置处理器:BeanFactoryPostProcessor(触发时机:bean定义注册之后bean实例化之前)和BeanDefinitionRegistr ...
- Spring IoC源码解析之getBean
一.实例化所有的非懒加载的单实例Bean 从org.springframework.context.support.AbstractApplicationContext#refresh方法开发,进入到 ...
- Spring系列(五):Spring AOP源码解析
一.@EnableAspectJAutoProxy注解 在主配置类中添加@EnableAspectJAutoProxy注解,开启aop支持,那么@EnableAspectJAutoProxy到底做了什 ...
- Spring系列(六):Spring事务源码解析
一.事务概述 1.1 什么是事务 事务是一组原子性的SQL查询,或者说是一个独立的工作单元.要么全部执行,要么全部不执行. 1.2 事务的特性(ACID) ①原子性(atomicity) 一个事务必须 ...
- Spring Boot系列(四):Spring Boot源码解析
一.自动装配原理 之前博文已经讲过,@SpringBootApplication继承了@EnableAutoConfiguration,该注解导入了AutoConfigurationImport Se ...
- Spring Security源码解析一:UsernamePasswordAuthenticationFilter之登录流程
一.前言 spring security安全框架作为spring系列组件中的一个,被广泛的运用在各项目中,那么spring security在程序中的工作流程是个什么样的呢,它是如何进行一系列的鉴权和 ...
- Spring Cloud系列(三):Eureka源码解析之服务端
一.自动装配 1.根据自动装配原理(详见:Spring Boot系列(二):Spring Boot自动装配原理解析),找到spring-cloud-starter-netflix-eureka-ser ...
随机推荐
- 详解RecyclerView的预布局
概述 RecyclerView 的预布局用于 Item 动画中,也叫做预测动画.其用于当 Item 项进行变化时执行的一次布局过程(如添加或删除 Item 项),使 ItemAnimator 体验更加 ...
- Python面向对象——1、什么是异常 2、为何处理异常 3、如何处理异常? 4、何时使用异常处理 网络编程的一些预备知识
文章目录 异常 1.什么是异常 2.为何处理异常 3.如何处理异常? 4.何时使用异常处理 网络编程的一些预备知识 异常 1.什么是异常 异常是程序发生错误的信号.程序一旦出现错误,便会产生一个异常, ...
- Python面向对象——面向对象介绍、实现面向对象编程、定义类、再调用类产生对象、总结__init__方法、查找顺序
文章目录 面向对象介绍 实现面向对象编程 一:先定义类 二:再调用类产生对象 总结__init__方法 查找顺序 面向对象介绍 ''' 面向过程: 核心是"过程"二字 过程的终极奥 ...
- the solution of Mining Your Own Business
the description of problem (我看的是 PDF 里面的原题所以这里描述会和题目不一样,但是大意一致) 给定一个未必连通的无向图,问最少在几个点设置出口,可以保证任意一个点坍塌 ...
- Speex详解(2019年09月25日更新)
Speex详解 整理者:赤勇玄心行天道 QQ号:280604597 微信号:qq280604597 QQ群:511046632 博客:www.cnblogs.com/gaoyaguo 大家有什么不明白 ...
- 飞码LowCode前端技术系列(一):数据结构设计
简介 飞码是京东科技研发的低代码产品,可使营销运营域下web页面快速搭建.飞码是单web页面搭建工具,从创建页面到监测再到投产的一站式解决方案.会通过七篇文章介绍飞码,分别是:(1)背景与数据结构设计 ...
- Python 作用域:局部作用域、全局作用域和使用 global 关键字
变量只在创建它的区域内可用.这被称为作用域. 局部作用域 在函数内部创建的变量属于该函数的局部作用域,并且只能在该函数内部使用. 示例:在函数内部创建的变量在该函数内部可用: def myfunc() ...
- Gitlab集成jenkins及docker自动化部署教程
Gitlab集成jenkins及docker自动化部署教程 能实现提交代码到gitlab后,我们只需要合并代码到指定分支就可以上Jenkins自动拉取最新代码并重新构建部署 1.登录Jenkins点击 ...
- C#判断字符串的显示宽度
C#判断字符串的显示宽度 起因: 公司有一个使用项目使用HTML转换为PDF,其中有一个表格,表格的最后一列中的单元格,其字符串超长后会被丢弃,而不是换行到下一行展示(HtmlToPdf渲染引擎导致的 ...
- 运维初级实践——Linux系统命令教程
区块链运维工程师在Linux环境中常用的命令.快捷键,以及安装软件和文件管理的最佳实践. 1. 常用Linux命令 1.1 文件和目录操作 ls:列出目录内容 cd:更改目录 mkdir:创建新目录 ...