前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >从SpringBootApplication谈谈Spring Boot启动时都做了哪些事?

从SpringBootApplication谈谈Spring Boot启动时都做了哪些事?

作者头像
架构探险之道
发布2020-01-13 16:21:57
1.5K0
发布2020-01-13 16:21:57
举报

[Spring Boot]从SpringBootApplication谈谈Spring Boot启动时都做了哪些事?

简介

Spring Boot 极大的减少了我们Spring项目开发的工作量,很多的配置文件往往都不需要编写了,只需要引入对应的starter,就可以完成配置实例的自动装配。那么,在Spring Boot项目执行时,到底都做了哪些事呢?


手机用户请横屏获取最佳阅读体验,REFERENCES中是本文参考的链接,如需要链接和更多资源,可以关注其他博客发布地址。

平台

地址

CSDN

https://blog.csdn.net/sinat_28690417

简书

https://www.jianshu.com/u/3032cc862300

个人博客

https://yiyuery.github.io/NoteBooks/


正文

项目依赖

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compile project(':common-dependencies:common-web-template')

    compile project(':spring-boot-examples:spring-boot-hello-starter')

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testCompile("org.junit.jupiter:junit-jupiter-api")
    testRuntime("org.junit.jupiter:junit-jupiter-engine")
}

入口

@SpringBootApplication◊
public class BootAutoConfigApplication {

    public static void main(String[] args) {
        SpringApplication.run(BootAutoConfigApplication.class, args);
    }
}

点击run我们可以看到一个最基础的构造器函数的调用:

new SpringApplication(null,primarySources)

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        //[1] 自动根据加载的Dipatcher推断当前容器环境
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        //[2] 设置应用于Spring的{@link ApplicationContextInitializer}的初始化监听器
        setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));
        //[3] 设置自动装配的监听器
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        //[4] 获取当前启动的main方法
        this.mainApplicationClass = deduceMainApplicationClass();
    }

推断容器环境

代码 [1]

//WebApplicationType
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
            "org.springframework.web.context.ConfigurableWebApplicationContext" };

    private static final String WEBMVC_INDICATOR_CLASS = "org.springframework."
            + "web.servlet.DispatcherServlet";

    private static final String WEBFLUX_INDICATOR_CLASS = "org."
            + "springframework.web.reactive.DispatcherHandler";

    private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";

    private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";

    private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";

static WebApplicationType deduceFromClasspath() {
//如果包路径下对应的Class文件可以加载到,则匹配到对应的容器环境
        if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
                && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
                && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
            return WebApplicationType.REACTIVE;
        }
        for (String className : SERVLET_INDICATOR_CLASSES) {
            if (!ClassUtils.isPresent(className, null)) {
                return WebApplicationType.NONE;
            }
        }
        return WebApplicationType.SERVLET;
    }

//ClassUtils
public static boolean isPresent(String className, @Nullable ClassLoader classLoader) {
    try {
        //[2.1]代码
        forName(className, classLoader);
        return true;
    }
    catch (IllegalAccessError err) {
        throw new IllegalStateException("Readability mismatch in inheritance hierarchy of class [" +
                className + "]: " + err.getMessage(), err);
    }
    catch (Throwable ex) {
        // Typically ClassNotFoundException or NoClassDefFoundError...
        return false;
    }
}

代码[1.1]根据包路径直接加载Class

.

如果包路径下对应的Class文件可以加载到,则匹配到对应的容器环境。

设置监听器和加载初始化启动类

代码 [2]、[3]

如何设置初始化器呢?实现ApplicationContextInitializer.class接口的实例类很多,如何选择加载哪些呢?

public class ContextIdApplicationContextInitializer implements
        ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
//...
}

.

看实际加载结果是6个,那么为什么加载这6个初始化器呢?

.

pringFactoriesLoader.loadFactoryNames(type, classLoader))

核心方法是这个,那么如果读取到的者6个类包路径的呢?

.

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = cache.get(classLoader);
        if (result != null) {
            return result;
        }

        try {
            Enumeration<URL> urls = (classLoader != null ?
            //FACTORIES_RESOURCE_LOCATION是个常量值,对应的内容为 "META-INF/spring.factories";
                    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            result = new LinkedMultiValueMap<>();
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    String factoryClassName = ((String) entry.getKey()).trim();
                    for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                        result.add(factoryClassName, factoryName.trim());
                    }
                }
            }
            cache.put(classLoader, result);
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }

META-INF/spring.factories,其实际配置的是 Spring Boot 启动后自动装配的一些实现类。这里我们猜测是由于项目引用了相关的jar包。

.

.

回头看下项目的依赖是否真的含有spring-boot-autoconfigure,发现spring-boot-starter包中果然是引用了的,印证了我们的猜想。

.

所以此处加载逻辑,其实是自动装配spring.factories中定义的那些ApplicationContextInitializer接口的实现类。

代码 [2] 的思路理清楚了,对应代码[3]的实现也是一致的。

  • 首先获取ApplicationListener接口包路径
  • 然后加载所应用的包中是否包含该接口的自动装配的类。
  • 设置进当前启动对象的成员变量中,后续Spring容器加载时进行注入。

.

.

获取main方法

代码 [4]

private Class<?> deduceMainApplicationClass() {
        try {
            StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
            for (StackTraceElement stackTraceElement : stackTrace) {
                if ("main".equals(stackTraceElement.getMethodName())) {
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        }
        catch (ClassNotFoundException ex) {
            // Swallow and continue
        }
        return null;
    }

根据当前栈帧的调用栈信息,并解析当前启动类。主要用于后续进行包路径下的类扫描和加载。

.

核心Run方法

/**
     * Run the Spring application, creating and refreshing a new
     * {@link ApplicationContext}.
     * @param args the application arguments (usually passed from a Java main method)
     * @return a running {@link ApplicationContext}
     */
    public ConfigurableApplicationContext run(String... args) {
        // [1] 项目启动信息收集,记录一些启动耗时等信息
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        configureHeadlessProperty();
        // [2] 启动监听器加载
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            // [3]运行参数解析获取
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            configureIgnoreBeanInfo(environment);
            // [4] 启动 Banner 打印
            Banner printedBanner = printBanner(environment);
            // [5] 创建应用上下文
            context = createApplicationContext();
            // 自动装配启动异常处理实例类
            exceptionReporters = getSpringFactoriesInstances(
                    SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
            // [6] 准备上下文        
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);
            // [7] 刷新上下文        
            refreshContext(context);
            // [8] 刷新后更新上下文中启动参数(模板方法,用于子类根据启动参数做些处理)
            afterRefresh(context, applicationArguments);
            // 停止启动信息收集
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            // [9] 启动后更新监听器中实例上下文信息
            listeners.started(context);
            // [10] 启动 Runner
            callRunners(context, applicationArguments);
        }catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

        try {
            // [11] 发布运行事件给监听器
            listeners.running(context);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
        //返回应用上下文
        return context;
    }

项目启动信息收集

代码 [1]

.

加载运行监听器

代码 [2] 加载SpringApplication执行相关的监听器

private SpringApplicationRunListeners getRunListeners(String[] args) {
        Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
        return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
                SpringApplicationRunListener.class, types, this, args));
    }

仍然是自动装配的加载方式,加载实现接口SpringApplicationRunListener的类:

public interface SpringApplicationRunListener {

    /**
     * Called immediately when the run method has first started. Can be used for very
     * early initialization.
     */
    void starting();

    /**
     * Called once the environment has been prepared, but before the
     * {@link ApplicationContext} has been created.
     * @param environment the environment
     */
    void environmentPrepared(ConfigurableEnvironment environment);

    /**
     * Called once the {@link ApplicationContext} has been created and prepared, but
     * before sources have been loaded.
     * @param context the application context
     */
    void contextPrepared(ConfigurableApplicationContext context);

    /**
     * Called once the application context has been loaded but before it has been
     * refreshed.
     * @param context the application context
     */
    void contextLoaded(ConfigurableApplicationContext context);

    /**
     * The context has been refreshed and the application has started but
     * {@link CommandLineRunner CommandLineRunners} and {@link ApplicationRunner
     * ApplicationRunners} have not been called.
     * @param context the application context.
     * @since 2.0.0
     */
    void started(ConfigurableApplicationContext context);

    /**
     * Called immediately before the run method finishes, when the application context has
     * been refreshed and all {@link CommandLineRunner CommandLineRunners} and
     * {@link ApplicationRunner ApplicationRunners} have been called.
     * @param context the application context.
     * @since 2.0.0
     */
    void running(ConfigurableApplicationContext context);

    /**
     * Called when a failure occurs when running the application.
     * @param context the application context or {@code null} if a failure occurred before
     * the context was created
     * @param exception the failure
     * @since 2.0.0
     */
    void failed(ConfigurableApplicationContext context, Throwable exception);

}

.

.

然后就是循环调用starting方法开启运行监听器:

public void starting() {
    for (SpringApplicationRunListener listener : this.listeners) {
        listener.starting();
    }
}

准备启动环境

代码 [3] 运行参数解析获取

public class SimpleCommandLinePropertySource extends CommandLinePropertySource<CommandLineArgs> {

    /**
     * Create a new {@code SimpleCommandLinePropertySource} having the default name
     * and backed by the given {@code String[]} of command line arguments.
     * @see CommandLinePropertySource#COMMAND_LINE_PROPERTY_SOURCE_NAME
     * @see CommandLinePropertySource#CommandLinePropertySource(Object)
     */
    public SimpleCommandLinePropertySource(String... args) {
        super(new SimpleCommandLineArgsParser().parse(args));
    }
//...
}

解析平常我们执行jar的一些参数:如 jar xxx-boot.jarm --server.port=8080

代码 [3.1] 启动环境准备

//启动环境准备
private ConfigurableEnvironment prepareEnvironment(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    // Create and configure the environment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    listeners.environmentPrepared(environment);
    bindToSpringApplication(environment);
    if (!this.isCustomEnvironment) {
        environment = new EnvironmentConverter(getClassLoader())
                .convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
}
// 根据前文提到的类文件加载判断容器类型,此处根据获取到的不同类型返回不同的环境对象   
private ConfigurableEnvironment getOrCreateEnvironment() {
    if (this.environment != null) {
        return this.environment;
    }
    switch (this.webApplicationType) {
    case SERVLET:
        return new StandardServletEnvironment();
    case REACTIVE:
        return new StandardReactiveWebEnvironment();
    default:
        return new StandardEnvironment();
    }
}
//更新SpringApplicationRunListener的环境信息
public void environmentPrepared(ConfigurableEnvironment environment) {
    for (SpringApplicationRunListener listener : this.listeners) {
        listener.environmentPrepared(environment);
    }
}
//绑定运行环境到当前应用实例
protected void bindToSpringApplication(ConfigurableEnvironment environment) {
        try {
            Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
        }
        catch (Exception ex) {
            throw new IllegalStateException("Cannot bind to SpringApplication", ex);
        }
}

初始化应用上下文

代码 [5] 创建应用上下文

根据前文提到的当前容器环境加载对应的上下文实例

protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch (this.webApplicationType) {
            case SERVLET:
            //AnnotationConfigServletWebServerApplicationContext
                contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                break;
            case REACTIVE:
            //AnnotationConfigReactiveWebServerApplicationContext
                contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                break;
            default:
            //AnnotationConfigApplicationContext
                contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
            }
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Unable create a default ApplicationContext, "
                            + "please specify an ApplicationContextClass",
                    ex);
        }
    }
    // 应用上下文实例化并返回
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

代码 [6] 上下文准备,初始化之后需要填充环境信息,并注入到监听器中

private void prepareContext(ConfigurableApplicationContext context,
            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments, Banner printedBanner) {
        //[6.1] 设置运行环境信息
        context.setEnvironment(environment);
        //[6.2] 加工上下文
        postProcessApplicationContext(context);
        //[6.3] 加载初始化监听器
        applyInitializers(context);
        //[6.4] 监听器中加载上下文信息,并广播事件
        listeners.contextPrepared(context);
        if (this.logStartupInfo) {
            logStartupInfo(context.getParent() == null);
            logStartupProfileInfo(context);
        }
        // Add boot specific singleton beans
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        //将解析到的启动参数对象注册到实例工厂,便于后续读取
        beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
        //如果负责Banner打印的实例存在,这注册到实例工厂
        if (printedBanner != null) {
            beanFactory.registerSingleton("springBootBanner", printedBanner);
        }
        //根据字段 allowBeanDefinitionOverriding 判断是否允许用相同的名称重新注册不同的定义
        if (beanFactory instanceof DefaultListableBeanFactory) {
            ((DefaultListableBeanFactory) beanFactory)
                    .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }
        // Load the sources
        Set<Object> sources = getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        //加载上下文
        load(context, sources.toArray(new Object[0]));
        //广播加载上下文完毕的容器事件
        listeners.contextLoaded(context);
    }

代码 [6.3] 加载初始化监听器

//此处用到的getInitializers()即前文通过自动装配加载的一些启动初始化器
protected void applyInitializers(ConfigurableApplicationContext context) {
    for (ApplicationContextInitializer initializer : getInitializers()) {
        Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
                initializer.getClass(), ApplicationContextInitializer.class);
        Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
        initializer.initialize(context);
    }
}
//返回根据@Order排序后的启动初始化器
public Set<ApplicationContextInitializer<?>> getInitializers() {
    return asUnmodifiableOrderedSet(this.initializers);
}

代码 [6.4] 监听器中加载上下文信息,并广播事件

前文提到了启动通过自动装配方式设置了EventPublishingRunListener的实例,此处主要是通过Spring中的广播容器事件机制进行上下文准备完毕的事件通知。

public void contextPrepared(ConfigurableApplicationContext context) {
    for (SpringApplicationRunListener listener : this.listeners) {
        listener.contextPrepared(context);
    }
}

代码 [7] 刷新上下文

刷新上下文之后,根据字段registerShutdownHook判断是否需要向JVM运行时注册一个关闭挂钩,关闭这个上下文。

private void refreshContext(ConfigurableApplicationContext context) {
    refresh(context);
    if (this.registerShutdownHook) {
        try {
            context.registerShutdownHook();
        }
        catch (AccessControlException ex) {
            // Not allowed in some environments.
        }
    }
}
//调用上下文的refresh方法
protected void refresh(ApplicationContext applicationContext) {
    Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
    ((AbstractApplicationContext) applicationContext).refresh();
}
//AbstractApplicationContext.refresh
@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // Prepare this context for refreshing.
        prepareRefresh();

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

        // 在上下文中准备要使用的实例工厂
        prepareBeanFactory(beanFactory);

        try {
            // 告诉子类刷新内部的实例工厂
            postProcessBeanFactory(beanFactory);

            // 在上下文中获取备注册到实例工厂的处理器
            invokeBeanFactoryPostProcessors(beanFactory);

            // 注册一些拦截实例创建的处理器
            registerBeanPostProcessors(beanFactory);

            // 为当前上下文初始化消息源
            initMessageSource();

            // 为当前上下文初始化事件广播
            initApplicationEventMulticaster();

            // 在上下文的自定义实现子类中初始化一些特殊的实例
            onRefresh();

            // 检查监听器的实例并把她们注册到实例工厂
            registerListeners();

            // 实例化所有非懒加载的实例.
            finishBeanFactoryInitialization(beanFactory);

            // 最后一步:Spring容器内对应的事件发布
            finishRefresh();
        }

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

            // Destroy already created singletons to avoid dangling resources.
            destroyBeans();

            // Reset 'active' flag.
            cancelRefresh(ex);

            // Propagate exception to caller.
            throw ex;
        }

        finally {
            // Reset common introspection caches in Spring's core, since we
            // might not ever need metadata for singleton beans anymore...
            resetCommonCaches();
        }
    }
}

AbstractApplicationContextspring-context中的类

.

启动运行监听器

代码 [9] 启动后更新监听器中实例上下文信息(发布事件)

public void started(ConfigurableApplicationContext context) {
    for (SpringApplicationRunListener listener : this.listeners) {
        listener.started(context);
    }
}
//EventPublishingRunListener
@Override
public void started(ConfigurableApplicationContext context) {
    context.publishEvent(
            new ApplicationStartedEvent(this.application, this.args, context));
}
//ApplicationStartedEvent
public ApplicationStartedEvent(SpringApplication application, String[] args,
        ConfigurableApplicationContext context) {
    super(application, args);
    this.context = context;
}

代码 [10] 启动 Runner

private void callRunners(ApplicationContext context, ApplicationArguments args) {
    List<Object> runners = new ArrayList<>();
    //获取上下文工厂中的ApplicationRunner启动类
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    //获取上下文工厂中的CommandLineRunner启动类
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    AnnotationAwareOrderComparator.sort(runners);
    for (Object runner : new LinkedHashSet<>(runners)) {
        if (runner instanceof ApplicationRunner) {
            callRunner((ApplicationRunner) runner, args);
        }
        if (runner instanceof CommandLineRunner) {
            callRunner((CommandLineRunner) runner, args);
        }
    }
}
//挨个根据启动参数进行调用
private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
    try {
        (runner).run(args);
    }
    catch (Exception ex) {
        throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
    }
}

代码 [11] 发布Spring Boot应用运行中事件给监听器

public void running(ConfigurableApplicationContext context) {
    for (SpringApplicationRunListener listener : this.listeners) {
        listener.running(context);
    }
}
//EventPublishingRunListener.running
@Override
public void running(ConfigurableApplicationContext context) {
    context.publishEvent(
            new ApplicationReadyEvent(this.application, this.args, context));
}

总结

本文从SpringBootApplication,详细介绍了具体的执行流程。至此,总算对Spring Boot启动时做了啥有了个大概的了解,如果觉得有帮助的化,可以动动小手指,点击下右下角的在看哦 ^-^ !

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-01-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 架构探险之道 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • [Spring Boot]从SpringBootApplication谈谈Spring Boot启动时都做了哪些事?
    • 推断容器环境
      • 设置监听器和加载初始化启动类
        • 获取main方法
          • 核心Run方法
            • 项目启动信息收集
            • 加载运行监听器
            • 准备启动环境
            • 初始化应用上下文
          • 启动运行监听器
            • 总结
            相关产品与服务
            容器服务
            腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档