前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring 高级笔记

Spring 高级笔记

作者头像
MashiroT
发布2023-04-30 15:20:44
2530
发布2023-04-30 15:20:44
举报
文章被收录于专栏:MashiroのBlog

Spring 高级笔记

容器接口

由图可见,ConfigurationApplicationContext 实现了 ApplicationContext 接口,实现了 BeanFactory 接口。

BeanFactory 接口是 Spring 的核心容器,主要的 ApplicationContext 实现都组合(借助)了它的功能。 如 ConfigurationApplicationContext 中 BeanFactory 作为成员变量

BeanFactory 表面上只有 getBean,但实际上控制反转、基本的依赖注入,直至 Bean 的生命周期的各种功能,都由它的 实现类 提供。

ApplicationContext 功能

容器实现

BeanFactory实现

DefaultListableBeanFactory 是 BeanFactory 最重要的实现

通过工具类的方法 AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory) 添加 BeanFactory后处理器

通过 beanFactory.getBeansOfType(BeanFactoryPostProcessor.class) 获取后处理器,返回Map集合

通过遍历Map的value获取后处理器对象 beanFactoryPostProcessor,并调用 postProcessBeanFactory(beanFactory) 方法执行这些后处理器,实现注解解析功能

添加 Bean后处理器 针对 bean 的声明周期的各个阶段提供拓展,如 @Autowired @Resource `beanFactory.getBeansOfType(BeanPostProcessor.class).values.forEach(beanFactory::addBeanPostProcesser);

小结:

  • 不会主动调用BeanFactory后处理器
  • 不会注定添加Bean后处理器
  • 不会初始化单例
  • 不会解析BeanFactory
  • 不会解析${ }, #{ }

ApplicationContext的实现和用法

ApplicationContext 帮我们添加了上述的后处理器等功能,较为友好

ClassPathXmlApplicationContext 基于 classpath 下 xml 格式的配置文件来创建

FileSystemXmlApplicationContext 基于磁盘路径下 xml 格式的配置文件来创建

AnnotationConfigAppliCationContext 基于 Java 配置类来创建

AnnotationConfigServletWebServerApplicationContext 基于 Java 配置类来创建,用于 Web 环境

内嵌容器、注册DispatchrServlet

代码语言:javascript
复制
@Configuration
class WebConfig {

    // 内嵌容器
    @Bean
    public ServletWebServerFactory servletWebServerFactory() {
        return new TomcatServletWebServerFactory();
    }
    
    @Bean
    public DispatcherServlet dispatchrServlet() {
        return new DispatcherServlet();
    }
    
    @Bean
    public DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet) {
        return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
    }
}

Bean 的生命周期

Spring bean 生命周期的各个阶段

代码语言:javascript
复制
@Component
class LifeCycleBean {
    
    public LifeCycleBean() {
        System.out.println("Construction");
    }

    @Autowired
    public void autowire(@Value("${JAVA_HOME}") String javaHome) {
        System.out.println("注入: " + javaHome);
    }
    
    @PostConstruct
    public void init() {
        System.out.println("Init");
    }
    
    @PreDestroy
    public void destroy() {
        System.out.println("Destroy");
    }

}

运行结果:

代码语言:javascript
复制
Construction
注入:D:\Java\jdk-17.0.6
Init
...
{dataSource-1} closed
...
Destroy

Bean 后处理器,实现接口 InstantiationAwareBeanPostProcessor, DestructionAwareBeanPostProcessor

重写方法:

Object postProcessBeforeInstantiation 实例化前执行,如果不返回 null 则替换原本的bean

boolean postProcessProperties 依赖注入阶段执行,返回 false 跳过依赖注入阶段

Object postProcessBeforeInitialization 初始化前执行,如果不返回 null 则替换原本的bean

Object postProcessAfterInitialization 初始化后执行,如果不返回 null 则替换原本的bean

Object postProcessBeforeDestruction 销毁前执行,如果不返回 null 则替换原本的bean

Bean 后处理器

AutowiredAnnotationBeanPostProcessor @Autowired @Value

还需注册解析器 beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver()); 获取 @Value 的值

CommonAnnotationBeanPostProcessor @Resource @PostConstruct @PreDestroy

ConfiguratonPropertiesBindingPostProcessor @ConfigurationProperties

AutowiredAnnotationBeanPostProcessor 执行分析

  1. 查找哪些属性、方法添加了 @Autowired,这称之为 InjectionMetadata
代码语言:javascript
复制
AutowiredAnnotationBeanPostProcessor processor = new AutoWiredAnnotationBeanPostProcessor();

processor.setBeanFactory(beanFactory);

processor.postProcessProperties(null, bean1, "bean1");

内部调用 findAutowiringMetadata 方法,拿到 metadata 对象,其通过反射获取 bean 上加了 @Autowired @Value 的成员变量、方法

  1. 调用 InjectionMetadata 来进行依赖注入,注入时按类型查找值

通过 metadata.inject() 进行反射,完成依赖注入

  1. 如何按类型查找值
代码语言:javascript
复制
// 模拟内部反射
Field bean3 = Bean1.class.getDeclaredField("bean3");

// 第二个形参用于找不到对应的bean时是否报错,false不报错
DependencyDescriptor dd = new DependencyDescriptor(bean3, false);

// null为beanName和别名,可以为null
beanFactory.doResolveDependency(dd, null, null, null);

doResolveDependency 通过成员变量的找到其类型,根据类型找到容器中符合的 bean

最后反射,将找到的 bean set 给需要注入的成员变量、方法

BeanFactory 后处理器

ConfigurationClassPostProcessor @ComponentScan @Bean @Import @ImportResource

MapperScannerConfigurer @MapperScanner

Aware 接口及 InitializingBean 接口

Aware 接口用于注入一些与容器相关的信息,如:

  • a.BeanNameAware 注入 bean 的名字
  • b.BeanFactoryAware 注入 BeanFactory 容器
  • c.ApplicationContextAware 注入 ApplicationContext 容器
  • d.EmbeddedValueResolverAware ${}

b, c, d 虽然用 @Autowired 也能实现,但是有区别:

  • @Autowired 注解需要用到 Bean 后处理器,属于拓展功能
  • Aware 接口数据内置功能,不加拓展,Spring 就能识别,某些情况下,拓展功能会失效,而内置功能不会失效

在使用 @Configuration 的配置类中,@Autowired 等注解都会失效,因为包含 BeanFactoryPostProcessor,因此要创建其中的 BeanFactoryPostProcessor 必须提前创建配置类,此时的 BeanPostProcessor 还未准备好,导致 @Autowired 等注解失效

初始化与销毁

初始化:

  1. Bean注解内提供方法名,类内提供方法 @Bean(initMethod = "xxxx")
  2. 方法上加@PostConstruct @PostConstruct public void init() { xxx }
  3. 实现 InitializingBean 接口 重写 afterPropertiesSet 方法 @Override public void afterPropertiesSet() { xxx }

执行顺序为:@PostConstruct -> Aware -> afterPropertiesSet -> @Bean(initMethod = "xxxx")

销毁同上

Scope

Spring5 有 5 个 Scope:singleton(单例返回), prototype(非单例)

  • request(请求域):单次请求
  • session(会话域):单个session时间内
  • application(应用域):同一个ServletTomcat容器内

singleton 内有 prototype,导致 prototype 失效 原因:singleton只会注入一次依赖 解决(代理):

  1. 给该成员变量添加 @Lazy 注解,实际上是注入了 prototype 对象的代理对象,代理每次返回不同的对象。
  2. 在 prototype 的 @Scope标签里添加一个proxyMode,@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)

两种均可,推荐 @Lazy,简单

解决(工厂):

  1. 将对象作为对象工厂的泛型
代码语言:javascript
复制
@Autowired
private ObjectFactory<F3> f3;

public F3 getF3() {
    return f3.getObject();
}
  1. 注入 ApplicationContext
代码语言:javascript
复制
@Autowired
private ApplicationContext context;

public F4 getF4() {
    return context.getBean(F4.class);
}

AOP 实现之 ajc 编译器

仅使用 @Aspect 注解,并未使用 Spring,使用依赖 aspectj-maven-plugin

编译时直接修改源码,直接在类中插入代码,不使用代理

突破代理限制,可以对静态方法使用

使用时需使用 maven compile 编译

AOP 实现之 agent 类加载

仅使用 @Aspect 注解,并未使用 Spring,使用依赖 aspectjweaver

突破代理限制,可以对方法内部调用其他本类方法进行修改

在类加载时,进行对类修改

AOP 之 动态代理

JDK 动态代理

JDK动态代理 思路整理

代码语言:javascript
复制
public class AlthleteProxy {
    public Skill AlthleteProxy(Althletes obj) {
        return (Skill) Proxy.newProxyInstance(
            // 代理类没有源码,在加载时使用asm生成字节码,需要类加载器来加载它
            obj.getClass().getClassLoader(),
            // 实现同一接口
             obj.getClass().getInterfaces(),
            // 封装行为
             ((proxy, method, args) ->  {
                System.out.println("Start");
                Object result = method.invoke(proxy, args);
                System.out.println("Finish");
                return result;
             };)
        );
    }
}
底层实现

InvocationHandler 接口

代码语言:javascript
复制
interface InvocationHandler {
    Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

通过asm生成一个 $Proxy0

代码语言:javascript
复制
final class $Proxy0 extends Proxy implements Foo {
    private static final Method m0;
    
    // 父类Proxy中声明了该成员变量,用于invoke回调
    public $Proxy0(InvocationHandler invocationHandler) {
        super(invocationHandler);
    }
    
    static {
        try {
            // 通过反射获取方法对象
            m0 = Class.forName("ski.mashiro.JdkProxyDemo$Foo").getMethod("foo", new Class[0]);
        } catch (NoSuchMethodExpection e) {
            throw new NoSuchMethodError(e.getMessage());
        } catch (ClassNotFoundException e) {
            throw new NoClassDefFoundError(e.getMessage());
        }
    }
    
    public final void foo() {
        try {
            // 通过父类中的成员 InvocationHandler h,调用 invoke
            this.h.invoke(this, m0, null);
        } catch (Error | RuntimeException throwable) {
            throw throwable;
        } catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }
}

jdk反射优化,前16次基于JNI实现,性能低;在第17次对同一方法进行反射调用时,会用asm生成一个新的代理类 GeneratedMethodAccessor,内部正常调用了方法,没有使用反射,但是一个方法对应一个新的代理类,代价是生成新的代理类。

CGLib 动态代理

代码语言:javascript
复制
public class CglibProxyDemo {
    class Target {
        public void foo() {
            System.out.println("target foo");
        }
    }
    
    public static void main(String...args) {
        Target target = new Target();
        Target proxy = (Target) Enhancer.create(
//                指定父类型,代理对象为其子类型,因此父类不能为final,方法也不能为final,因为使用重写实现
                Target.class,
//                new MethodInterceptor() {
//                    @Override
//                    public Object interceptor(Object proxy, Method method, Object[] args, MethodProxy methodProxy) {}
//                }
//				 methodProxy可以避免反射调用,内部直接调用
//                Object result = methodProxy.invoke(target, args); 需要目标对象(Spring使用)
//                Object result = methodProxy.invokeSuper(proxy, args); 需要代理对象
                (proxy, method, args, methodProxy) -> {
                    System.out.println("Before...");
                    Object result = method.invoke(target, args);
                    System.out.println("After...");
                    return result;
                }
        );
        proxy.foo();
    }
}
底层实现

主要区别在于 MethodProxy

代码语言:javascript
复制
public class Proxy extends Target {
    private static Method m0;
    private static MethodProxy mp0;
    
    private MethodInterceptor methodInterceptor;
    
    public void setMethodInterceptor(MethodInterceptor methodInterceptor) {
        this.methodInterceptor = methodInterceptor;
    }
    
    static {
        try {
            // 通过反射获取方法对象
            m0 = Class.forName("ski.mashiro.CglibProxyDemo").getMethod("foo", new Class[0]);
            // 获取 MethodProxy 对象,第3,4个形参即为签名Signature的参数
            // 首次使用MethodProxy时底层会创建 FastClass 的子类,本质是代理类,目的是避免反射调用
            mp0 = MethodProxy.create(Target.class, Proxy.class, "()V", "foo", "fooSuper");
        } catch (NoSuchMethodExpection e) {
            throw new NoSuchMethodError(e.getMessage());
        } catch (ClassNotFoundException e) {
            throw new NoClassDefFoundError(e.getMessage());
        }
    }
    
    
    public void fooSuper() {
        super.foo();
    }
    
    @Override
    public void foo() {
        try {
            methodInterceptor.intercept(this, m0, new Object[0], mp0);
        } catch (Error | RuntimeException throwable) {
            throw throwable;
        } catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
        super.foo();
    }
}

// 这个类也是直接生成字节码的,其父类是 FastClass,是抽象类,这里仅做部分实现
// 一个代理目标对应两个 FastClass,一个作用于目标,一个作用于代理
public class TargetFastClass {
    private static Signature s0 = new Signature("foo", "()V");
    
    /**
     * signature 方法特征签名
     * @return 索引
     * 给每个方法一个索引
     */
    public int getIndex(Signature signature) {
        if (signature.equals(s0)) {
            return 0;
        }
        return -1;
    }
    
    public Object invoke(int index, Object target, Object[] args) {
        if (index == 0) {
            ((Target) target).foo();
            return null;
        } else {
            throw new RuntimeException("Err");
        }
    }
}

Spring 选择代理 - JDK 和 CGLib 的统一

两个切面概念:

Aspect

Aspect = 通知1(advice) + 切点1(pointcut) 通知2(advice) + 切点2(pointcut)

Advisor

更细粒度的切面,仅包含一个 advice 和 pointcut

Aspect最终会被拆解成多个Advisor生效

代码语言:javascript
复制
class Target implements T1 {
    public void foo() {
        System.out.println("foo");
    }
}

class AopDemo {
    public static void main(String...args) {

        // 切点
        var pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* foo())");

        // 通知, 非CGLib的接口
        MethodInterceptor advice = invocation -> {
            System.out.println("before");
            // 调用目标
            Object result = invocation.proceed();
            System.out.println("after");
            return result;
        };

        // 切面
        var advisor = new DefaultPointcutAdvisor(pointcut, advice);

        // 创建代理, ProxyFactory是用来创建代理的核心实现
        ProxyFactory factory = new ProxyFactory();
        factory.setTarget(target);
        factory.addAdvisor(advisor);
        /*
         规则:
         proxyTargetClass = false, 目标类实现了接口, 用JDK实现
         proxyTargetClass = false, 目标类未实现接口,用CGLib实现
         proxyTargetClass = true,  用CGLib实现

         factory.setProxyTargetClass(false);
         
         其内部又用了 AopProxyFactory 选择具体的代理实现
             - JdkDynamicAopProxy
             - ObjenesisCglibAopProxy
        */
        I1 proxy = (I1) factory.getProxy();
        proxy.foo();
    }
}

切点匹配

切点表达式只能匹配方法的信息

代码语言:javascript
复制
var pointcut = new AspectJExpressionPointcut();
    // 方法名判断
    pointcut.setExpression("execution(* foo())");
    
    // 代理也这样做检查,判断是否符合
    pointcut.matches(T1.class.getMethod("foo"), T1.class);
    
    // 注解判断
    pointcut.setExpression("@annotation(org.springframework.transaction.annotation.Transactional)");

    pointcut.matches(T1.class.getMethod("foo"), T1.class);

通过 StaticMathodMatcherPointcut 抽象类,实现匹配方法,类,接口的信息

代码语言:javascript
复制
var pt = new StaticMethodMatcherPointcut() {
        @Override
        public boolean matches(Method method, Class<?> targetClass) {
            var annotations = MergedAnnotations.from(method);
            // 检查方法上是否有 Transactional 注解
            if (annotations.isPresent(Transactional.class)) {
                return true;
            }
            // 检查类及其接口上是否有 Transactional 注解
            annotations = MergedAnnotations.from(targetClass, SearchStrategy.TYPE_HIERARCHY);
            if (annotations.isPresent(Transactional.class)) {
                return true;
            }
            return false;
        }
    }

@Aspect 与 Advisor

@Aspect 切面,适合编程使用,使用简单,抽象程度高 有多个通知和切面

Advisor 切面,适合框架内部使用,使用复杂,抽象程度低 只有一个通知和切面

Spring 最终会将 @Aspect切面 转换成 Advisor切面

高级转换为低级切面的时机及代理生成时机

在 bean 加载时,后处理器中有一个 findEligibleAdvisors(Class<?> clazz, String beanName) 方法,接收类型和beanName,返回一个 List<Advisor> 集合。 负责将符合条件的切面添加到该集合中,如果是 Advisor 切面直接添加,如果是 Aspect 切面则转换成 Advisor 切面后添加。

后处理器中还有一个 warpIfNecessary(Object target, String beanName, String cacheKey) 方法,判断是否需要创建代理对象,需要则返回代理,否则返回自身,它在内部调用 findEligibleAdvisors 方法,只要返回集合不为空,则表示需要创建代理。

创建代理对象的时机

bean实例创建 -> (1) 依赖注入 -> 初始化 (2)

代理创建有两个位置,只会在上面两个中任一一个创建

高级切面转低级切面

通过反射获取目标类的方法,判断是否有指定类型的注解,如果有则根据方法信息创建Advisor切面,然后添加进集合

RequestMappingHandlerMapping 与 RequestMappingHandlerAdapter

DispatcherServlet 初始化时机

DispatcherServlet 对象 Bean 由 Spring 管理,但其由 Tomcat 在首次访问 DispatcherServlet 时初始化

也可以在注册 DispatcherServlet 时,通过注册Bean的方法setLoadOnStartup(1)调整为在 Tomcat 启动时初始化

DispatcherServlet 初始化行为

代码语言:javascript
复制
protected void initStrategies(ApplicationContext context) {
    // 文件上传解析
    this.initMultipartResolver(context);
    // i18n支持
    this.initLocaleResolver(context);
    this.initThemeResolver(context);
    // 路径映射处理
    this.initHandlerMappings(context);
    // 适配不同形式的Controller方法
    this.initHandlerAdapters(context);
    // 异常解析
    this.initHandlerExceptionResolvers(context);
    this.initRequestToViewNameTranslator(context);
    this.initViewResolvers(context);
    this.initFlashMapManager(context);
}

RequestMappingHandlerMapping 基本用途

解析 @RequestMapping 注解,其派生注解有 @PostMapping @GetMapping 等,生成路径与控制器方法的映射关系,该映射关系在RequestMappingHandlerMapping初始化时生成

RequestMappingHandlerAdapter 基本用途

调用Controller方法,提供参数解析器(如@RequestParam),返回值解析器(根据返回值不同进行处理,解析@ResponseBody注解等)

MVC处理流程

当浏览器发送一个请求到 http://127.0.0.1:8080/hello 后,处理流程是:

  1. 服务器提供了 DispatcherServlet,它使用的是标准的 Servlet 技术
代码语言:txt
复制
- 路径:默认映射路径为 `/`,即会匹配到所有请求URL,可作为请求的统一入口,也被称之为前控制器
    - 例外:JSP不会匹配到 DispatcherServlet
- 创建:在 Boot 中,由 DispatcherServletAutoConfiguration 这个自动配置类提供 DispatcherServlet 的 bean
- 初始化:DispatcherServlet 初始化时会优先到容器里寻找各种组件,作为它的成员变量
    - HandlerMapping:初始化时记录映射关系
    - HandlerAdapter:初始化时准备参数解析器、返回值处理器、消息转换器
    - HandlerExceptionResolver:初始化时准备参数解析器、返回值处理器、消息转换器
    - ViewResolverDispatcherServlet 会利用 HandlerMapping 进行路径匹配,找到 @RequestMapping("/hello") 对应的控制器方法
代码语言:txt
复制
- 控制器方法会被封装为 HandlerMethod 对象,并结合匹配到的拦截器一起返回给 DispatcherServlet
- HandlerMethod 和拦截器合在一起称为 HandlerExecutionChain (调用链)对象DispatcherServlet 接下来会:
代码语言:txt
复制
1. 调用拦截器的 preHandler 方法
代码语言:txt
复制
2. RequestMappingHandlerAdapter 调用 handle 方法,准备数据绑定工厂、模型工厂、将 HandlerMethod 完善为 ServletInvocableHandlerMethod
代码语言:txt
复制
    - @ControllerAdvice:补充模型数据、自定义类型转换器,@RequestBody 增强,@ResponseBody 增强,@ExceptionHandler 异常处理
    - 使用 HandlerMethodArgumentResolver 准备参数
    - 调用 ServletInvocableHandlerMethod
    - 使用 HandlerMethodReturnValueHandler 处理返回值
        - 如果返回的 ModelAndView 为 null,不走第 4 步视图解析及渲染流程 如标注了 @ResponseBody 的控制器方法,调用 HttpMessageConverter 来将结果转换为 Json,这时返回的 ModelAndView 就为 null
        - 如果返回的 ModelAndView 不为 null,会在第 4 步走视图解析渲染流程
3. 调用拦截器的 postHandle 方法
代码语言:txt
复制
4. 处理异常或视图渲染
代码语言:txt
复制
    - 如果 1~3 出现异常,走 ExceptionHandlerExceptionResolver 处理异常流程
    - 正常,走视图解析渲染流程
5. 调用拦截器的 afterCompletion 方法

Boot 启动过程

构造

创建 SpringApplication 对象

  1. 记录 BeanDefinition 源
  2. 推断应用类型
  3. 记录 ApplicationContext 初始化器
  4. 记录监听器
  5. 推断主启动类

run

执行 run 方法

  1. 得到 SpringApplicationRunListeners 事件发布器
代码语言:txt
复制
- 发布 application starting 事件封装启动 args
  1. 准备 Environment 添加命令行参数
  2. ConfigurationPropertySources 处理
代码语言:txt
复制
- 发布 application environment 已准备事件通过 EnvironmentPostProcessorApplicationListener 进行 env 后处理
代码语言:txt
复制
- application.properties,由 StandardConfigDataLocationResolver 解析
- spring.application.json绑定 spring.main 到 SpringApplication 对象
  1. 打印 banner
  2. 创建容器
  3. 准备容器
代码语言:txt
复制
- 发布 application context 已初始化事件加载 bean 定义
代码语言:txt
复制
- 发布 application prepared 事件refresh 容器
代码语言:txt
复制
- 发布 application started 事件执行 runner
代码语言:txt
复制
- 发布 application ready 事件
- 有异常则发布 application failed 事件
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023 年 04 月,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Spring 高级笔记
    • 容器接口
      • 容器实现
        • BeanFactory实现
        • ApplicationContext的实现和用法
      • Bean 的生命周期
        • Spring bean 生命周期的各个阶段
      • Bean 后处理器
        • AutowiredAnnotationBeanPostProcessor 执行分析
      • BeanFactory 后处理器
        • Aware 接口及 InitializingBean 接口
          • 初始化与销毁
            • Scope
              • AOP 实现之 ajc 编译器
                • AOP 实现之 agent 类加载
                  • AOP 之 动态代理
                    • JDK 动态代理
                    • CGLib 动态代理
                  • Spring 选择代理 - JDK 和 CGLib 的统一
                    • 切点匹配
                      • @Aspect 与 Advisor
                        • 高级转换为低级切面的时机及代理生成时机
                      • RequestMappingHandlerMapping 与 RequestMappingHandlerAdapter
                        • DispatcherServlet 初始化时机
                        • DispatcherServlet 初始化行为
                        • RequestMappingHandlerMapping 基本用途
                        • RequestMappingHandlerAdapter 基本用途
                      • MVC处理流程
                        • Boot 启动过程
                          • 构造
                          • run
                      相关产品与服务
                      容器服务
                      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档