Springboot之---强大的Servlet(二)

举报
赵KK日常技术记录 发表于 2023/06/21 18:26:13 2023/06/21
【摘要】 Spring容器内部工作机制AbstractApplicationContextorg.springframework.context.support.AbstractApplicationContextpackage org.springframework.context.support;/** * 使用模板方法设计模式,需要具体的子类来实现抽象方法。 */public abstract...

Spring容器内部工作机制AbstractApplicationContext

流程

org.springframework.context.support.AbstractApplicationContext
package org.springframework.context.support;
/**
 * 使用模板方法设计模式,需要具体的子类来实现抽象方法。
 */
public abstract class AbstractApplicationContext extends DefaultResourceLoader
        implements ConfigurableApplicationContext {

    /**
     * 在此工厂中,国际化消息 MessageSource 的 bean的名称。
     * 如果没有提供消息,消息解析将委托给父节点。
     * @see MessageSource
     */
    public static final String MESSAGE_SOURCE_BEAN_NAME = "messageSource";

    /**
     * 在此工厂中,SpringBean 的生命周期LifecycleProcessor 的 bean的名称
     * 如果没有提供,则使用DefaultLifecycleProcessor。
     * @see org.springframework.context.LifecycleProcessor
     * @see org.springframework.context.support.DefaultLifecycleProcessor
     */
    public static final String LIFECYCLE_PROCESSOR_BEAN_NAME = "lifecycleProcessor";

    /**
     * 在此工厂中,应用事件多路广播器 的 bean的名称。
     * 如果没有提供,则使用默认的simpleapplicationeventmultiaster。
     * @see org.springframework.context.event.ApplicationEventMulticaster
     * @see org.springframework.context.event.SimpleApplicationEventMulticaster
     */
    public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";

    static {
        ContextClosedEvent.class.getName();
    }

    /** 这个类使用的logger。可用于子类。 */
    protected final Log logger = LogFactory.getLog(getClass());

    /** 此上下文的唯一id(如果有的话)。 */
    private String id = ObjectUtils.identityToString(this);

    /** 显示名称 */
    private String displayName = ObjectUtils.identityToString(this);

    /** 父上下文。 */
    @Nullable
    private ApplicationContext parent;

    /** 此上下文使用的环境。 */
    @Nullable
    private ConfigurableEnvironment environment;

    /** 应用于刷新的 BeanFactoryPostProcessors */
    private final List<BeanFactoryPostProcessor> beanFactoryPostProcessors = new ArrayList<>();

    /** 此上下文启动时的系统时间(毫秒)。 */
    private long startupDate;

    /** 指示此上下文当前是否处于活动状态的标志。 */
    private final AtomicBoolean active = new AtomicBoolean();

    /** 指示此上下文是否已关闭的标志。 */
    private final AtomicBoolean closed = new AtomicBoolean();

    /** 用于"刷新" 和 "销毁" 时的同步监视器(浅显是说就是当做锁) */
    private final Object startupShutdownMonitor = new Object();

    /** 如果已注册,则引用JVM关闭链接。 */
    @Nullable
    private Thread shutdownHook;

    /** 此上下文使用的 ResourcePatternResolver 资源模式解析器 */
    private ResourcePatternResolver resourcePatternResolver;

    /** LifecycleProcessor 生命周期处理器,用于在此上下文中管理bean的生命周期。 */
    @Nullable
    private LifecycleProcessor lifecycleProcessor;

    /** 我们将这个接口的实现委托给 MessageSource */
    @Nullable
    private MessageSource messageSource;

    /** 事件发布所使用的助手类。 */
    @Nullable
    private ApplicationEventMulticaster applicationEventMulticaster;

    /** 静态的、指定的 listeners 监听器Set集合. */
    private final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();

    /** 早期发布的 ApplicationEvents 应用事件Set集合. */
    @Nullable
    private Set<ApplicationEvent> earlyApplicationEvents;

    /** 创建一个没有父元素的新的AbstractApplicationContext。 */
    public AbstractApplicationContext() {
        this.resourcePatternResolver = getResourcePatternResolver();
    }

    /** 使用给定的父上下文来创建一个新的AbstractApplicationContext。 */
    public AbstractApplicationContext(@Nullable ApplicationContext parent) {
        this();
        setParent(parent);
    }

    //---------------------------------------------------------------------
    // ApplicationContext接口的实现
    //---------------------------------------------------------------------

    /**
     * 设置此应用程序上下文的惟一id。
     * 默认值是上下文实例的对象id,或者上下文bean的名称(如果上下文本身定义为bean的话)。
     * @param id 上下文的唯一id
     */
    @Override
    public void setId(String id) {
        this.id = id;
    }
    /** 获取此应用程序上下文的id。 */
    @Override
    public String getId() {
        return this.id;
    }

    @Override
    public String getApplicationName() {
        return "";
    }

    /**
     * 为这个上下文设置一个(显示)名称。
     * 通常在具体上下文实现的初始化过程中完成。
     * 默认值是上下文实例的对象id。
     */
    public void setDisplayName(String displayName) {
        // 设置显示的上下文名称不能为空
        Assert.hasLength(displayName, "Display name must not be empty");
        this.displayName = displayName;
    }

    /**
     * 获取并返回此应用上下文的展示名称
     * @return 此应用上下文的展示名称(非null)
     */
    @Override
    public String getDisplayName() {
        return this.displayName;
    }

    /**
     * 返回父上下文,如果没有父上下文,则返回 null。
     * (返回null意为着,此应用上下文是整个应用上下文体系的根上下文)。
     */
    @Override
    @Nullable
    public ApplicationContext getParent() {
        return this.parent;
    }

    /**
     * 为这个应用程序上下文设置 Environment 环境
     * 默认值由 createEnvironment()方法决定。
     * 用这个方法替换默认值不是唯一选择,还可通过 getEnvironment() 方法进行配置。
     * 但不管在哪一种情况下,这些修改都应该在 refresh() 方法前执行。
     * @see org.springframework.context.support.AbstractApplicationContext#createEnvironment
     */
    @Override
    public void setEnvironment(ConfigurableEnvironment environment) {
        this.environment = environment;
    }

    /**
     * 获取并返回此应用上下文的环境,如果为null,则通过 createEnvironment() 方法进行创建并返回。
     */
    @Override
    public ConfigurableEnvironment getEnvironment() {
        if (this.environment == null) {
            this.environment = createEnvironment();
        }
        return this.environment;
    }

    /**
     * 创建并返回新的StandardEnvironment 标准环境。
     * 子类可以重写此方法,以便提供自定义 ConfigurableEnvironment 可配置环境的实现。
     */
    protected ConfigurableEnvironment createEnvironment() {
        return new StandardEnvironment();
    }

    /**
     * 如果此应用上下文已经可用,则将此上下文的内部bean工厂返回为AutowireCapableBeanFactory。
     * @see #getBeanFactory()
     */
    @Override
    public AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException {
        return getBeanFactory();
    }

    /** 在首次加载此上下文时返回时间戳(ms)。 */
    @Override
    public long getStartupDate() {
        return this.startupDate;
    }

    /**
     * 将给定的事件发布给所有监听器。
     * 注意:监听器在 MessageSource 之后初始化,以便能够在监听器实现中访问它。
     * 因此,MessageSource 实现不能发布事件。要发布的事件(可能是特定于应用程序或标准框架事件)
     */
    @Override
    public void publishEvent(ApplicationEvent event) {
        publishEvent(event, null);
    }

    /**
     * 将给定的事件发布给所有监听器。
     * 注意:监听器在 MessageSource 之后初始化,以便能够在监听器实现中访问它。
     * 因此,MessageSource 实现不能发布事件。
     * 要发布的事件(可能是 ApplicationEvent 或要转换为 PayloadApplicationEvent 的有效负载对象)
     */
    @Override
    public void publishEvent(Object event) {
        publishEvent(event, null);
    }

    /**
     * 将给定的事件发布给所有监听器。
     * @param event 要发布的事件(可能是 ApplicationEvent 或要转换为 PayloadApplicationEvent 的有效负载对象)
     * @param eventType 可解析的事件类型(如果已知)(前面的文章已经提到过)
     * @since 4.2
     */
    protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
        Assert.notNull(event, "Event must not be null");

        // 必要时将事件装饰为 ApplicationEvent
        ApplicationEvent applicationEvent;
        if (event instanceof ApplicationEvent) {
            applicationEvent = (ApplicationEvent) event;
        }
        else {
            applicationEvent = new PayloadApplicationEvent<>(this, event);
            if (eventType == null) {
                eventType = ((PayloadApplicationEvent) applicationEvent).getResolvableType();
            }
        }

        // 如果可能的话,现在就进行多播——或者在初始化多播器之后延迟
        if (this.earlyApplicationEvents != null) {
            this.earlyApplicationEvents.add(applicationEvent);
        }
        else {
            getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
        }

        // 通过父上下文发布事件…
        if (this.parent != null) {
            if (this.parent instanceof AbstractApplicationContext) {
                ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
            }
            else {
                this.parent.publishEvent(event);
            }
        }
    }

    /**
     * 返回上下文使用的内部 ApplicationEventMulticaster 应用事件多路广播器
     * @return 上下文使用的内部 ApplicationEventMulticaster 应用事件多路广播器(从不为null)
     * @throws IllegalStateException 如果上下文尚未初始化
     */
    ApplicationEventMulticaster getApplicationEventMulticaster() throws IllegalStateException {
        if (this.applicationEventMulticaster == null) {
            throw new IllegalStateException("ApplicationEventMulticaster not initialized - " +
                    "call 'refresh' before multicasting events via the context: " + this);
        }
        return this.applicationEventMulticaster;
    }

    /**
     * 返回上下文使用的内部生命周期处理器。
     * @return 内部生命周期处理器。 (从不为null)
     * @throws IllegalStateException 如果上下文尚未初始化
     */
    LifecycleProcessor getLifecycleProcessor() throws IllegalStateException {
        if (this.lifecycleProcessor == null) {
            throw new IllegalStateException("LifecycleProcessor not initialized - " +
                    "call 'refresh' before invoking lifecycle methods via the context: " + this);
        }
        return this.lifecycleProcessor;
    }

    /**
     * 返回此 ResourcePatternResolver资源模式解析器,
     * 用于将多个资源的位置按照模式解析到资源实例中。
     * 默认是org.springframework.core.io.support.PathMatchingResourcePatternResolver。
     * 支持ant风格的位置模式。
     * 可以在子类中重写,用于扩展解析策略,例如在web环境中。在需要解决位置模式时不要调用此函数。
     * 相反,调用上下文的getResources方法,它将委托给ResourcePatternResolver。
     * @return 此应用上下文的 ResourcePatternResolver 资源模式解析器
     * @see #getResources
     * @see org.springframework.core.io.support.PathMatchingResourcePatternResolver
     */
    protected ResourcePatternResolver getResourcePatternResolver() {
        return new PathMatchingResourcePatternResolver(this);
    }

    //---------------------------------------------------------------------
    // ConfigurableApplicationContext接口的实现
    //---------------------------------------------------------------------

    /**
     * 设置此应用程序上下文的父上下文。
     * 父级ApplicationContext#getEnvironment()环境是
     * ConfigurableEnvironment#merge(ConfigurableEnvironment),如果父级非null,
     * 并且它的环境是ConfigurableEnvironment的实例,那么它就会与这个(子级)应用程序上下文环境合并
     * @see ConfigurableEnvironment#merge(ConfigurableEnvironment)
     */
    @Override
    public void setParent(@Nullable ApplicationContext parent) {
        this.parent = parent;
        if (parent != null) {
            Environment parentEnvironment = parent.getEnvironment();
            if (parentEnvironment instanceof ConfigurableEnvironment) {
                getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
            }
        }
    }

    /** 添加Bean工厂后置处理器 */
    @Override
    public void addBeanFactoryPostProcessor(BeanFactoryPostProcessor postProcessor) {
        Assert.notNull(postProcessor, "BeanFactoryPostProcessor must not be null");
        this.beanFactoryPostProcessors.add(postProcessor);
    }

    /**
     * 返回将应用到内部BeanFactory的BeanFactoryPostProcessors列表。
     */
    public List<BeanFactoryPostProcessor> getBeanFactoryPostProcessors() {
        return this.beanFactoryPostProcessors;
    }

    /** 添加应用上下文监听器 */
    @Override
    public void addApplicationListener(ApplicationListener<?> listener) {
        Assert.notNull(listener, "ApplicationListener must not be null");
        if (this.applicationEventMulticaster != null) {
            this.applicationEventMulticaster.addApplicationListener(listener);
        }
        this.applicationListeners.add(listener);
    }

    /**
     * 返回静态指定的ApplicationListeners集合.
     */
    public Collection<ApplicationListener<?>> getApplicationListeners() {
        return this.applicationListeners;
    }

    /**
     * 加载或刷新一个持久化的配置,可能是XML文件、属性文件或关系数据库模式。
     * 由于这是一种启动方法,如果失败,应该销毁已经创建的单例,以避免悬空资源。
     * 换句话说,在调用该方法之后,要么全部实例化,要么完全不实例化。
     * @throws 如果bean工厂无法初始化,则抛出 BeansException 异常
     * @throws 如果已经初始化且不支持多次刷新,则会抛出 IllegalStateException 异常
     */
    @Override
    public void refresh() throws BeansException, IllegalStateException {
        // 加载或刷新配置前的同步处理
        synchronized (this.startupShutdownMonitor) {
            // 为刷新而准备此上下文
            prepareRefresh();

            // 告诉子类去刷新内部bean工厂。
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // 准备好bean工厂,以便在此上下文中使用。
            prepareBeanFactory(beanFactory);

            try {
                // 允许在上下文子类中对bean工厂进行后置处理。
                postProcessBeanFactory(beanFactory);

                // 调用在上下文中注册为bean的工厂处理器。
                invokeBeanFactoryPostProcessors(beanFactory);

                // 注册拦截bean创建的bean处理器。
                registerBeanPostProcessors(beanFactory);

                // 初始化此上下文的 message resource 消息资源。
                initMessageSource();

                // 为这个上下文初始化事件多路广播器。
                initApplicationEventMulticaster();

                // 初始化特定上下文子类中的其他特殊bean。
                onRefresh();

                // 注册监听器(检查监听器的bean并注册它们)。
                registerListeners();

                // 实例化所有剩余的(非 lazy-init 懒初始化的)单例。
                finishBeanFactoryInitialization(beanFactory);

                // 最后一步: 发布相应的事件。
                finishRefresh();
            }

            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }

                // 销毁已经创建的单例,以避免悬空资源。
                destroyBeans();

                // 重置 'active' 表示.
                cancelRefresh(ex);

                // 将异常传播给调用者。
                throw ex;
            }

            finally {
                // 重置Spring内核中的共用的缓存,因为我们可能再也不需要单例bean的元数据了……
                resetCommonCaches();
            }
        }
    }

    /**
     * 准备这个上下文来刷新、设置它的启动日期和活动标志以及执行属性源的任何初始化。
     */
    protected void prepareRefresh() {
        this.startupDate = System.currentTimeMillis();
        this.closed.set(false);
        this.active.set(true);

        if (logger.isDebugEnabled()) {
            if (logger.isTraceEnabled()) {
                logger.trace("Refreshing " + this);
            }
            else {
                logger.debug("Refreshing " + getDisplayName());
            }
        }

        // 在此上下文环境中,初始化任何占位符处的属性资源
        initPropertySources();

        // 验证标记为必需的所有属性是否可解析
        // 请参考 ConfigurablePropertyResolver#setRequiredProperties
        getEnvironment().validateRequiredProperties();

        // 允许在多路广播器可用时,就会发布初期 ApplicationEvents 应用事件的集合
        this.earlyApplicationEvents = new LinkedHashSet<>();
    }

    /**
     * 用实际实例替换任何存根属性源。
     * @see org.springframework.core.env.PropertySource.StubPropertySource
     * @see org.springframework.web.context.support.WebApplicationContextUtils#initServletPropertySources
     */
    protected void initPropertySources() {
        // 对于子类:默认情况下不执行任何操作。
    }

    /**
     * 通知此上下文的子类去加载或刷新其内在的bean工厂
     * @return 新的bean工厂实例
     * @see #refreshBeanFactory()
     * @see #getBeanFactory()
     */
    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        refreshBeanFactory();
        return getBeanFactory();
    }

    /**
     * 配置工厂的标准上下文的特征属性,
     * 例如上下文的ClassLoader类加载器和post-processors后置处理器。
     * @param beanFactory 要配置的bean工厂
     */
    protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        // 告诉内部bean工厂使用上下文的类加载器等。
        beanFactory.setBeanClassLoader(getClassLoader());
        beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
        beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));

        // 使用上下文回调配置bean工厂。
        beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
        beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
        beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
        beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
        beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
        beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
        beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);

        // 在简单的工厂中,BeanFactory接口没有注册为可解析的类型。
        // MessageSource注册(并发现自动)为一个bean。
        beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
        beanFactory.registerResolvableDependency(ResourceLoader.class, this);
        beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
        beanFactory.registerResolvableDependency(ApplicationContext.class, this);

        // 注册早期后置处理器,用于检测内部bean作为应用程序监听器。
        beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));

        // 如何找到一个LoadTimeWeaver,那么就准备将后置处理器“织入”bean工厂
        if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
            beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
            // 为类型匹配设置临时类加载器。
            beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
        }

        // 注册默认environment环境bean。
        if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
            beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
        }
        if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
            beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
        }
        if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
            beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
        }
    }

    /**
     * 在标准初始化后修改应用程序上下文的内部bean工厂。
     * 所有bean定义都将被加载,但是没有bean会被实例化。
     * 这允许在某些应用上下文实现中注册特殊的BeanPostProcessors等。
     * @param beanFactory 应用环境下的Bean工厂
     */
    protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    }

    /**
     * 实例化并调用所有已注册的BeanFactoryPostProcessor 的 bean,如果已给出顺序,请按照顺序。
     * 必须在单实例实例化之前调用。
     */
    protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
        PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

        // 如何找到一个LoadTimeWeaver,那么就准备将后置处理器“织入”bean工厂
        // (例如,一个 @Bean 方法通过ConfigurationClassPostProcessor来注册)
        if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
            beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
            beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
        }
    }

    /**
     * BeanPostProcessor 的 bean,如果给出显式顺序,请按照顺序。
     * 必须在应用程序bean的任何实例化之前调用。
     */
    protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {
        PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);
    }

    /**
     * 初始化MessageSource。
     * 如果在此上下文中未定义国际化资源,则使用父上下文的国际化资源。
     */
    protected void initMessageSource() {
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {
            this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);
            // 使用此上下文的 MessageSource 知道父上下文的 MessageSource.
            if (this.parent != null && this.messageSource instanceof HierarchicalMessageSource) {
                HierarchicalMessageSource hms = (HierarchicalMessageSource) this.messageSource;
                if (hms.getParentMessageSource() == null) {
                    // 如果没有已注册的父MessageSource,则只将父上下文设置为父MessageSource
                    // registered already.
                    hms.setParentMessageSource(getInternalParentMessageSource());
                }
            }
            if (logger.isTraceEnabled()) {
                logger.trace("Using MessageSource [" + this.messageSource + "]");
            }
        }
        else {
            // 使用空MessageSource可以接受getMessage方法的调用。
            DelegatingMessageSource dms = new DelegatingMessageSource();
            dms.setParentMessageSource(getInternalParentMessageSource());
            this.messageSource = dms;
            beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);
            if (logger.isTraceEnabled()) {
                logger.trace("No '" + MESSAGE_SOURCE_BEAN_NAME + "' bean, using [" + this.messageSource + "]");
            }
        }
    }

    /**
     * 初始化ApplicationEventMulticaster。
     * 如果在上下文中没有定义,则使用SimpleApplicationEventMulticaster。
     * @see org.springframework.context.event.SimpleApplicationEventMulticaster
     */
    protected void initApplicationEventMulticaster() {
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
            this.applicationEventMulticaster =
                    beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
            if (logger.isTraceEnabled()) {
                logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
            }
        }
        else {
            this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
            beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
            if (logger.isTraceEnabled()) {
                logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
                        "[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
            }
        }
    }

    ```
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。