七.SpringCloud源码剖析-Eureka Server的自动配置

简介: 前面的章节我们针对于Eureak Client的初始化 ,服务注册 ,服务发现,服务续约,取消注册功能进行了分析,接下来我们围绕Eureka的核心功能对Server端进行分析,本章将会分析Eureka Server的启动过程。

系列文章目录

一.SpringCloud源码剖析-Eureka核心API

二.SpringCloud源码剖析-Eureka Client 初始化过程

三.SpringCloud源码剖析-Eureka服务注册

四.SpringCloud源码剖析-Eureka服务发现

五.SpringCloud源码剖析-Eureka Client服务续约

六.SpringCloud源码剖析-Eureka Client取消注册

七.SpringCloud源码剖析-Eureka Server的自动配置

八.SpringCloud源码剖析-Eureka Server初始化流程

九.SpringCloud源码剖析-Eureka Server服务注册流程

十.SpringCloud源码剖析-Eureka Server服务续约

十一.SpringCloud源码剖析-Eureka Server服务注册表拉取

十二.SpringCloud源码剖析-Eureka Server服务剔除

十三.SpringCloud源码剖析-Eureka Server服务下线


# 前言 前面的章节我们针对于Eureak Client的初始化 ,服务注册 ,服务发现,服务续约,取消注册功能进行了分析,接下来我们围绕Eureka的核心功能对Server端进行分析,本章将会分析Eureka Server的启动过程。

1.EureakServerAutoConfiguration的注册

这里和EureakClientAutoConfiguration差不多,都是由主启动类上的@SpringBootApplication标签中的@EnableAutoConfiguration启动自动配置,通过AutoConfigurationImportSelector来扫描classpath下的starter包中的自动配置类

@Target({
   
   ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration    //开启自动配置
@ComponentScan(
    excludeFilters = {
   
   @Filter(
    type = FilterType.CUSTOM,
    classes = {
   
   TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {
   
   AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
   
   

@EnableAutoConfiguration标签的源码

@Target({
   
   ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({
   
   AutoConfigurationImportSelector.class})    //自动配置选择器
public @interface EnableAutoConfiguration {
   
   
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {
   
   };

    String[] excludeName() default {
   
   };
}

AutoConfigurationImportSelector导入选择器源码

public class AutoConfigurationImportSelector
        implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
        BeanFactoryAware, EnvironmentAware, Ordered {
   
   

    ...省略部分代码...

    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
   
   
        if (!isEnabled(annotationMetadata)) {
   
   
            return NO_IMPORTS;
        }
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                .loadMetadata(this.beanClassLoader);
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        //1.这里会扫描spring.factories文件中的配置
        List<String> configurations = getCandidateConfigurations(annotationMetadata,
                attributes);
        configurations = removeDuplicates(configurations);
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = filter(configurations, autoConfigurationMetadata);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        //2.加载一堆自动配置,其中就有 EureakServerAutoConfiguration 
        return StringUtils.toStringArray(configurations);
    }
    //获取候选的自动配置
    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
            AnnotationAttributes attributes) {
   
   
            //通过SpringFactoriesLoader加载spring.spring.factories中的配置类
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
                getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
        Assert.notEmpty(configurations,
                //从错误日志就可以看得出在扫描 META-INF/spring.factories
                "No auto configuration classes found in META-INF/spring.factories. If you "
                        + "are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

在这里插入图片描述

这里的 selectImports方法会通过getCandidateConfigurations方法使用SpringFactoriesLoader去加载classpath下的jar中的META-INF/spring.factories文件,该文件中有很多的自动配置,其中 EureakServerAutoConfiguration 就在spring-cloud-netflix-eureka-server-2.0.1.RELEASE.jar 中如下
在这里插入图片描述

2.EureakServerAutoConfiguration被加载的条件

EureakServerAutoConfiguration自动配置类上有一个注册条件

/**
 * @author Gunnar Hillert
 * @author Biju Kunjummen
 * @author Fahim Farook
 */
@Configuration
//导入 EurekaServerInitializerConfiguration 对Eureak进行初始化
@Import(EurekaServerInitializerConfiguration.class)
//条件:EurekaServerMarkerConfiguration.Marker是通过@EnableEureakServer激活
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
//Eureka仪表盘界面配置,以及实例注册的配置
@EnableConfigurationProperties({
   
    EurekaDashboardProperties.class,
        InstanceRegistryProperties.class })
//加载server.properties 服务端配置文件
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter {
   
   

@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class) 这个条件是通过@EnableEureakServer开启,

/**
    激活 EurekaServerAutoConfiguration
 * Annotation to activate Eureka Server related configuration {@link EurekaServerAutoConfiguration}
 *
 * @author Dave Syer
 * @author Biju Kunjummen
 *
 */

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EurekaServerMarkerConfiguration.class)
public @interface EnableEurekaServer {
   
   

}

EnableEurekaServer的注释写的很清楚,该注解用来激活EureakServerAutoConfiguration配置,通过@Import(EurekaServerMarkerConfiguration.class)来激活的

/**
 * Responsible for adding in a marker bean to activate
 * {@link EurekaServerAutoConfiguration}
 *
 * @author Biju Kunjummen
 */
@Configuration
public class EurekaServerMarkerConfiguration {
   
   

    @Bean
    public Marker eurekaServerMarkerBean() {
   
   
        return new Marker();
    }

    class Marker {
   
   
    }
}

看到这里就清楚了,EnableEurekaServer通过@Import(EurekaServerMarkerConfiguration.class)导入EurekaServerMarkerConfiguration.Marker,而在EureakServerAutoConfiguration配置类上正好有一个条件
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class),满足这个条件,自动配置被加载,他们的加载关系如下:

在这里插入图片描述

3.EureakServerAutoConfiguration配置了什么?

EureakServerAutoConfiguration配置了那些东西?我们来看一下源码,哦~~~,特别说明一下,服务端的pom我用的是spring-cloud-starter-netflix-eureka-server,该依赖会把客户端的包spring-cloud-starter-netflix-eureka-client也导入进来(Eureka集群需要互相注册),所以你会看到下面的配置类中也会有eureka client的配置

/**
 * @author Gunnar Hillert
 * @author Biju Kunjummen
 * @author Fahim Farook
 */
@Configuration
//导入 EurekaServerInitializerConfiguration 对Eureak进行初始化
@Import(EurekaServerInitializerConfiguration.class)
//条件:EurekaServerMarkerConfiguration.Marker是通过@EnableEureakServer激活
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
//Eureka仪表盘界面配置,以及实例注册的配置
@EnableConfigurationProperties({
   
    EurekaDashboardProperties.class,
        InstanceRegistryProperties.class })
//加载server.properties 服务端配置文件
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter {
   
   
    /**
     * List of packages containing Jersey resources required by the Eureka server
     */
    private static final String[] EUREKA_PACKAGES = new String[] {
   
    "com.netflix.discovery",
            "com.netflix.eureka" };


    @Autowired
    private ApplicationInfoManager applicationInfoManager;
    //eureka server服务端配置对象加载 eureka.server 开头的配置项
    //里面记录了了EurekaServer所需要的配置
    @Autowired
    private EurekaServerConfig eurekaServerConfig;
    //eureka client 客户端配置对象 加载eureka.client开头的配置项
    @Autowired
    private EurekaClientConfig eurekaClientConfig;
    //Eureka客户端
    @Autowired
    private EurekaClient eurekaClient;
    //实例注册表属性配置,加载eureka.instance.registry开头的配置
    @Autowired
    private InstanceRegistryProperties instanceRegistryProperties;

    public static final CloudJacksonJson JACKSON_JSON = new CloudJacksonJson();

    @Bean
    public HasFeatures eurekaServerFeature() {
   
   
        return HasFeatures.namedFeature("Eureka Server",
                EurekaServerAutoConfiguration.class);
    }
    //配置EurekaServerConfig ,Eureka服务器配置,加载eureka.server开头配置
    @Configuration
    protected static class EurekaServerConfigBeanConfiguration {
   
   
        @Bean
        @ConditionalOnMissingBean
        public EurekaServerConfig eurekaServerConfig(EurekaClientConfig clientConfig) {
   
   
            EurekaServerConfigBean server = new EurekaServerConfigBean();
            //如果开启了eureka.client.registerWithEureka=true 注册到Eureka
            if (clientConfig.shouldRegisterWithEureka()) {
   
   
                // Set a sensible default if we are supposed to replicate
                //当eureka服务器启动时尝试去获取集群里其他服务器上的注册信息的次数,默认为5
                server.setRegistrySyncRetries(5);
            }
            return server;
        }
    }

    //定义Eureka Server dashboard仪表盘监控界面的端点,注册中心的监控界面就是在这里定义的默认是“/”
    @Bean
    @ConditionalOnProperty(prefix = "eureka.dashboard", name = "enabled", matchIfMissing = true)
    public EurekaController eurekaController() {
   
   
        return new EurekaController(this.applicationInfoManager);
    }

    static {
   
   
        CodecWrappers.registerWrapper(JACKSON_JSON);
        EurekaJacksonCodec.setInstance(JACKSON_JSON.getCodec());
    }

    @Bean
    public ServerCodecs serverCodecs() {
   
   
        return new CloudServerCodecs(this.eurekaServerConfig);
    }

    private static CodecWrapper getFullJson(EurekaServerConfig serverConfig) {
   
   
        CodecWrapper codec = CodecWrappers.getCodec(serverConfig.getJsonCodecName());
        return codec == null ? CodecWrappers.getCodec(JACKSON_JSON.codecName()) : codec;
    }

    private static CodecWrapper getFullXml(EurekaServerConfig serverConfig) {
   
   
        CodecWrapper codec = CodecWrappers.getCodec(serverConfig.getXmlCodecName());
        return codec == null ? CodecWrappers.getCodec(CodecWrappers.XStreamXml.class)
                : codec;
    }

    class CloudServerCodecs extends DefaultServerCodecs {
   
   

        public CloudServerCodecs(EurekaServerConfig serverConfig) {
   
   
            super(getFullJson(serverConfig),
                    CodecWrappers.getCodec(CodecWrappers.JacksonJsonMini.class),
                    getFullXml(serverConfig),
                    CodecWrappers.getCodec(CodecWrappers.JacksonXmlMini.class));
        }
    }
    // 注册PeerAwareInstanceRegistry,应用对象注册表接口,提供了Eureka群内注册信息的同步功能。
    @Bean
    public PeerAwareInstanceRegistry peerAwareInstanceRegistry(
            ServerCodecs serverCodecs) {
   
   
            //客户端获取注册列表,强制初始化EurekaClient
        this.eurekaClient.getApplications(); // force initialization
        //创建实例注册器InstanceRegistry,继承了PeerAwareInstanceRegistry,PeerAwareInstanceRegistryimpl的实现

        return new InstanceRegistry(this.eurekaServerConfig, this.eurekaClientConfig,
                serverCodecs, this.eurekaClient,
                //期望最大每分钟续租次数
                this.instanceRegistryProperties.getExpectedNumberOfRenewsPerMin(),
                this.instanceRegistryProperties.getDefaultOpenForTrafficCount());
    }
    //PeerEurekaNode的工具类,用来管理PeerEurekaNode的生命周期,PeerEurekaNode表示集群中的一个节点
    @Bean
    @ConditionalOnMissingBean
    public PeerEurekaNodes peerEurekaNodes(PeerAwareInstanceRegistry registry,
            ServerCodecs serverCodecs) {
   
   
        return new RefreshablePeerEurekaNodes(registry, this.eurekaServerConfig,
                this.eurekaClientConfig, serverCodecs, this.applicationInfoManager);
    }

    /**
    //可刷新的集群节点,通过监听EnvironmentChangeEvent环境改变事件,如果特点的配置改变如:
    //eureka.client.region,eureka.client.service-url配置改变就刷新集群中的节点信息
     * {@link PeerEurekaNodes} which updates peers when /refresh is invoked.
     * Peers are updated only if
     * <code>eureka.client.use-dns-for-fetching-service-urls</code> is
     * <code>false</code> and one of following properties have changed.
     * </p>
     * <ul>
     * <li><code>eureka.client.availability-zones</code></li>
     * <li><code>eureka.client.region</code></li>
     * <li><code>eureka.client.service-url.&lt;zone&gt;</code></li>
     * </ul>
     */
    static class RefreshablePeerEurekaNodes extends PeerEurekaNodes
            implements ApplicationListener<EnvironmentChangeEvent> {
   
   

        public RefreshablePeerEurekaNodes(
                final PeerAwareInstanceRegistry registry,
                final EurekaServerConfig serverConfig,
                final EurekaClientConfig clientConfig, 
                final ServerCodecs serverCodecs,
                final ApplicationInfoManager applicationInfoManager) {
   
   
            super(registry, serverConfig, clientConfig, serverCodecs, applicationInfoManager);
        }
        //监听事件
        @Override
        public void onApplicationEvent(final EnvironmentChangeEvent event) {
   
   
            //如果配置发生改变
            if (shouldUpdate(event.getKeys())) {
   
   
                //更新集群中的节点,通过删除旧的不可用的PeerEurekaNode,创建新的副本
                updatePeerEurekaNodes(resolvePeerUrls());
            }
        }

        /*
            检查是否需要更新节点,检查特点的配置是否有改变
         * Check whether specific properties have changed.
         */
        protected boolean shouldUpdate(final Set<String> changedKeys) {
   
   
            assert changedKeys != null;

            // if eureka.client.use-dns-for-fetching-service-urls is true, then
            // service-url will not be fetched from environment.
            if (clientConfig.shouldUseDnsForFetchingServiceUrls()) {
   
   
                return false;
            }

            if (changedKeys.contains("eureka.client.region")) {
   
   
                return true;
            }

            for (final String key : changedKeys) {
   
   
                // property keys are not expected to be null.
                if (key.startsWith("eureka.client.service-url.") ||
                    key.startsWith("eureka.client.availability-zones.")) {
   
   
                    return true;
                }
            }

            return false;
        }
    }
    //EurekaServer的上下文对象
    @Bean
    public EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs,
            PeerAwareInstanceRegistry registry, PeerEurekaNodes peerEurekaNodes) {
   
   
        return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs,
                registry, peerEurekaNodes, this.applicationInfoManager);
    }
    //EurekaServerBootstrap  :EurekaServer的启动引导
    @Bean
    public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry,
            EurekaServerContext serverContext) {
   
   
        return new EurekaServerBootstrap(this.applicationInfoManager,
                this.eurekaClientConfig, this.eurekaServerConfig, registry,
                serverContext);
    }

    /**
    注册 Jersey filter ,通过ServletContainer处理/eureka/*请求
     * Register the Jersey filter
     */
    @Bean
    public FilterRegistrationBean jerseyFilterRegistration(
            javax.ws.rs.core.Application eurekaJerseyApp) {
   
   
        FilterRegistrationBean bean = new FilterRegistrationBean();
        //使用ServletContainer处理
        bean.setFilter(new ServletContainer(eurekaJerseyApp));
        bean.setOrder(Ordered.LOWEST_PRECEDENCE);
        //filter的拦截url   /eureka/*
        bean.setUrlPatterns(
                Collections.singletonList(EurekaConstants.DEFAULT_PREFIX + "/*"));

        return bean;
    }

    /**
    创建用Eureka服务器所需的所有资源,构建Jersey {@link javax.ws.rs.core.Application}。
     * Construct a Jersey {@link javax.ws.rs.core.Application} with all the resources
     * required by the Eureka server.
     */
    @Bean
    public javax.ws.rs.core.Application jerseyApplication(Environment environment,
            ResourceLoader resourceLoader) {
   
   

        ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(
                false, environment);

        // Filter to include only classes that have a particular annotation.
        //
        provider.addIncludeFilter(new AnnotationTypeFilter(Path.class));
        provider.addIncludeFilter(new AnnotationTypeFilter(Provider.class));

        // Find classes in Eureka packages (or subpackages)
        //
        Set<Class<?>> classes = new HashSet<>();
        for (String basePackage : EUREKA_PACKAGES) {
   
   
            Set<BeanDefinition> beans = provider.findCandidateComponents(basePackage);
            for (BeanDefinition bd : beans) {
   
   
                Class<?> cls = ClassUtils.resolveClassName(bd.getBeanClassName(),
                        resourceLoader.getClassLoader());
                classes.add(cls);
            }
        }

        // Construct the Jersey ResourceConfig
        //
        Map<String, Object> propsAndFeatures = new HashMap<>();
        propsAndFeatures.put(
                // Skip static content used by the webapp
                ServletContainer.PROPERTY_WEB_PAGE_CONTENT_REGEX,
                EurekaConstants.DEFAULT_PREFIX + "/(fonts|images|css|js)/.*");

        DefaultResourceConfig rc = new DefaultResourceConfig(classes);
        rc.setPropertiesAndFeatures(propsAndFeatures);

        return rc;
    }

    @Bean
    public FilterRegistrationBean traceFilterRegistration(
            @Qualifier("httpTraceFilter") Filter filter) {
   
   
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setFilter(filter);
        bean.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
        return bean;
    }
}

我们来归纳一下EurekaServerAutoConfiguration 配置了那些东西

  • @Import(EurekaServerInitializerConfiguration.class):导入了EurekaServer初始化配置
  • @ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class):这个是配置起效的条件,通过@EnableEurekaServer开启该条件
  • EurekaDashboardProperties.class:仪表盘配置
  • InstanceRegistryProperties.class :以 eureka.instance.registry开头的服务注册配置
  • @PropertySource("classpath:/eureka/server.properties"):加载server.properties配置文件

  • 注册了EurekaServerConfig:Eureka服务端配置对象,eureka服务器运行所需的配置信息,实现由两个一个是EurekaServerConfigBean是netflix的实现,一个是DefaultEurekaServerConfig是Eureka的实现

  • 注册了EurekaController:仪表盘端点controller,默认的路径是“/”
  • 注册了PeerAwareInstanceRegistry应用对象注册接口,提供了eureka集群间的服务同步功能及相关操作
  • 注册了PeerEurekaNodes:集群节点PeerEurekaNode的管理类,
  • 注册了EurekaServerContext :EurekaServer的上下文对象默认实现DefaultEurekaServerContext,提供了initialize初始化和shutdown关闭方法。
  • 注册了EurekaServerBootstrap 服务端启动引导,EurekaServer启动的关键
  • 注册了jersey Filter:使用ServletContainer实现功能,用来处理/eureka的请求

总结

在这里插入图片描述

  1. SpringBoot自动配置加载EurekaServerAutoConfiguration自动配置
  2. @EnableEurekaServer导入EurekaServerMarkerConfiguration.marker激活EurekaServerAutoConfiguration自动配置
  3. EurekaServerAutoConfiguration注册了一系列组件,比较重要的有EurekaServerInitializerConfiguration初始化EurekaServer配置,PeerAwareInstanceRegistry服务注册器,PeerEurekaNodes服务节PeerEurekaNode点生命周期管理工具,EurekaServerContext服务上下文,EurekaServerBootstrap EurekaServer的启动引导等等

在下一章节我们将来研究一下EurekaServer的初始化的详细过程

相关文章
|
2月前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
366 37
|
29天前
|
负载均衡 算法 Nacos
SpringCloud 微服务nacos和eureka
SpringCloud 微服务nacos和eureka
55 0
|
3月前
|
人工智能 前端开发 Java
【实操】Spring Cloud Alibaba AI,阿里AI这不得玩一下(含前后端源码)
本文介绍了如何使用 **Spring Cloud Alibaba AI** 构建基于 Spring Boot 和 uni-app 的聊天机器人应用。主要内容包括:Spring Cloud Alibaba AI 的概念与功能,使用前的准备工作(如 JDK 17+、Spring Boot 3.0+ 及通义 API-KEY),详细实操步骤(涵盖前后端开发工具、组件选择、功能分析及关键代码示例)。最终展示了如何成功实现具备基本聊天功能的 AI 应用,帮助读者快速搭建智能聊天系统并探索更多高级功能。
1229 2
【实操】Spring Cloud Alibaba AI,阿里AI这不得玩一下(含前后端源码)
|
1月前
|
负载均衡 Java API
【Spring Cloud生态】Spring Cloud Gateway基本配置
【Spring Cloud生态】Spring Cloud Gateway基本配置
37 0
|
2月前
|
负载均衡 Java Nacos
SpringCloud基础1——远程调用、Eureka,Nacos注册中心、Ribbon负载均衡
微服务介绍、SpringCloud、服务拆分和远程调用、Eureka注册中心、Ribbon负载均衡、Nacos注册中心
SpringCloud基础1——远程调用、Eureka,Nacos注册中心、Ribbon负载均衡
|
3月前
|
Java Spring
【Azure Spring Cloud】Spring Cloud Azure 4.0 调用Key Vault遇见认证错误 AADSTS90002: Tenant not found.
【Azure Spring Cloud】Spring Cloud Azure 4.0 调用Key Vault遇见认证错误 AADSTS90002: Tenant not found.
|
3月前
|
Java Spring 容器
【Azure Spring Cloud】在Azure Spring Apps上看见 App Memory Usage 和 jvm.menory.use 的指标的疑问及OOM
【Azure Spring Cloud】在Azure Spring Apps上看见 App Memory Usage 和 jvm.menory.use 的指标的疑问及OOM
|
3月前
|
存储 Java Spring
【Azure Spring Cloud】Azure Spring Cloud服务,如何获取应用程序日志文件呢?
【Azure Spring Cloud】Azure Spring Cloud服务,如何获取应用程序日志文件呢?
|
3月前
|
SQL Java 数据库连接
【Azure Spring Cloud】Azure Spring Cloud connect to SQL using MSI
【Azure Spring Cloud】Azure Spring Cloud connect to SQL using MSI
|
3月前
|
Java 开发工具 Spring
【Azure Spring Cloud】使用azure-spring-boot-starter-storage来上传文件报错: java.net.UnknownHostException: xxxxxxxx.blob.core.windows.net: Name or service not known
【Azure Spring Cloud】使用azure-spring-boot-starter-storage来上传文件报错: java.net.UnknownHostException: xxxxxxxx.blob.core.windows.net: Name or service not known