前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【DUBBO】 Schema解析Spring扩展机制集成Spring

【DUBBO】 Schema解析Spring扩展机制集成Spring

作者头像
spilledyear
发布2018-12-19 16:29:23
1K0
发布2018-12-19 16:29:23
举报
文章被收录于专栏:小白鼠小白鼠

本文主要介绍dubbo与spring的集成细节,阅读本文前最好对springIOC的运作流程有较好的掌握 springIOC详解

dubbo是如何做到与spring集成的?这都依赖于Spring提供的XML Schema可扩展机制,用户可以自定义XML Schema文件,并自定义XML Bean解析器,并集成到SpringIOC容器中。

创建自定义扩展,主要有以下步骤:

1、创建XML Schema 文件,描述自定义的合法构建模块,也就是xsd文件,主要用于定义数据约束;

2、自定义个处理器类,并实现NamespaceHandler接口,在里面注册各个标签对应的BeanDefinitionParser;

3、自定义一个或多个解析器,实现BeanDefinitionParser接口,用于定义Bean的解析逻辑;

Spring扩展机制

Spring在解析Bean的时候,会判断要解析的BeanDefinition是否属于默认的命名空间,例如<bean>标签。这里会分两个流程:解析Spring本身相关的BeanDefinition;解析用户自定义相关的BeanDefinition。DefaultBeanDefinitionDocumentReader中定义了parseBeanDefinitions方法,然后委托给BeanDefinitionParserDelegate进行相关操。如下:

代码语言:javascript
复制
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    if (delegate.isDefaultNamespace(root)) {
        NodeList nl = root.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            if (node instanceof Element) {
                Element ele = (Element) node;
                if (delegate.isDefaultNamespace(ele)) {
                    // 默认解析
                    parseDefaultElement(ele, delegate);
                }else {
                    // 自定义解析
                    delegate.parseCustomElement(ele);
                }
            }
        }
    }
    else {
        delegate.parseCustomElement(root);
    }
}

在此之前,先看看Spring中是怎么将各个XML Schema文件和NamespaceHandler对应起来的。

DefaultNamespaceHandlerResolver

代码语言:javascript
复制
public class DefaultNamespaceHandlerResolver implements NamespaceHandlerResolver {

    // 映射文件路径默认在 META-INF/spring.handlers,可以存在于多个jar文件中
    public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";

    // 资源路径
    private final String handlerMappingsLocation;

    // 缓存 namespace URI:NamespaceHandler 映射关系
    private volatile Map<String, Object> handlerMappings;


    public DefaultNamespaceHandlerResolver() {
        this(null, DEFAULT_HANDLER_MAPPINGS_LOCATION);
    }


    public DefaultNamespaceHandlerResolver(ClassLoader classLoader) {
        this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION);
    }


    public DefaultNamespaceHandlerResolver(ClassLoader classLoader, String handlerMappingsLocation) {
        Assert.notNull(handlerMappingsLocation, "Handler mappings location must not be null");
        this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
        this.handlerMappingsLocation = handlerMappingsLocation;
    }


    /**
     * 根据 namespace URI 加载 NamespaceHandler
     * @param namespaceUri the relevant namespace URI
     * @return the located {@link NamespaceHandler}, or {@code null} if none found
     */
    @Override
    public NamespaceHandler resolve(String namespaceUri) {
        Map<String, Object> handlerMappings = getHandlerMappings();
        Object handlerOrClassName = handlerMappings.get(namespaceUri);
        if (handlerOrClassName == null) {
            return null;
        }else if (handlerOrClassName instanceof NamespaceHandler) {
            return (NamespaceHandler) handlerOrClassName;
        }
        else {
            String className = (String) handlerOrClassName;
            try {
                Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
                if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
                    throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
                            "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
                }
                NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
                // 实例化之后会调用init方法
                namespaceHandler.init();
                handlerMappings.put(namespaceUri, namespaceHandler);
                return namespaceHandler;
            }
            catch (ClassNotFoundException ex) {
                throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" +
                        namespaceUri + "] not found", ex);
            }
            catch (LinkageError err) {
                throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +
                        namespaceUri + "]: problem with handler class file or dependent class", err);
            }
        }
    }

    private Map<String, Object> getHandlerMappings() {
        // 只会在第一次调用的时候执行以下逻辑,即只会加载一次,只会就从缓存中获取了
        if (this.handlerMappings == null) {
            synchronized (this) {
                if (this.handlerMappings == null) {
                    try {
                        // 加载所有配置文件:META-INF/spring.handlers
                        Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
                        if (logger.isDebugEnabled()) {
                            logger.debug("Loaded NamespaceHandler mappings: " + mappings);
                        }
                        Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>(mappings.size());
                        // 将 Properties解析成 Map
                        CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
                        this.handlerMappings = handlerMappings;
                    }
                    catch (IOException ex) {
                        throw new IllegalStateException(
                                "Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
                    }
                }
            }
        }
        return this.handlerMappings;
    }


    @Override
    public String toString() {
        return "NamespaceHandlerResolver using mappings " + getHandlerMappings();
    }

}

那么的DefaultNamespaceHandlerResolver的resolve方法是在什么时候被调用呢?前面提到,在Spring启动解析Bean时有可能涉及到自定义解析,即:delegate.parseCustomElement(root),看看它的代码

parseCustomElement

代码语言:javascript
复制
public BeanDefinition parseCustomElement(Element ele) {
    return parseCustomElement(ele, null);
}

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
    String namespaceUri = getNamespaceURI(ele);
    // 这里就调用了resolve方法
    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
    if (handler == null) {
        error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
        return null;
    }

    // 调用NamespaceHandler的parse方法,其内部委托各种BeanDefinitionParser进行真正的解析
    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

集成Spring

前面已经介绍了Spring扩展机制的原理,dubbo也就是利用这种机制与spring实现集成的,下面简单介绍这个流程

XML Schema文件

dubbo的XML Schema文件位于dubbo-config模块下的dubbo-config-spring模块中:

dubbo.xsd文件的内容比较多,就不过多介绍了,主要就是定义了一些约束,重点看看spring.handlers的文件内容:

代码语言:javascript
复制
http\://dubbo.apache.org/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler
http\://code.alibabatech.com/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler

其实就是定义了namespace url 和 NamespaceHandler 的映射关系,从这里可以知道,dubbo相关命名空间的解析主要就是借助于DubboNamespaceHandler

DubboNamespaceHandler

代码语言:javascript
复制
// NamespaceHandlerSupport是一个抽象类,实现了NamespaceHandler接口
public class DubboNamespaceHandler extends NamespaceHandlerSupport {

    static {
        Version.checkDuplicate(DubboNamespaceHandler.class);
    }

    @Override
    public void init() {
        registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
        registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
        registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
        registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
        registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
        registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
        registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
        registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
        registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
        registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
    }

}

NamespaceHandlerSupport是一个抽象类,实现了NamespaceHandler接口,里面提供了一些默认实现,所以在自定义NamespaceHandler的时候,只要继承NamespaceHandlerSupport即可。

DubboNamespaceHandler中的init方法在上面已经介绍过,在调用DefaultNamespaceHandlerResolver的resolve方法的时候,实例化NamespaceHandler会被执行。

DubboBeanDefinitionParser

registerBeanDefinitionParser主要就是将标签与对应的BeanDefinitionParser缓存到一个Map中。这里涉及到一个DubboBeanDefinitionParser,它实现了BeanDefinitionParser接口,调用NamespaceHandler的parse方法的时,其内部委托DubboBeanDefinitionParser执行真正的Bean解析逻辑

代码语言:javascript
复制
private final Map<String, BeanDefinitionParser> parsers =new HashMap<String, BeanDefinitionParser>();

protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
    this.parsers.put(elementName, parser);
}

NamespaceHandler的parse方法在NamespaceHandlerSupport中有默认实现,如下:

代码语言:javascript
复制
public BeanDefinition parse(Element element, ParserContext parserContext) {
    // 委托委托DubboBeanDefinitionParser执行真正的Bean解析逻辑
    return findParserForElement(element, parserContext).parse(element, parserContext);
}

// 根据标签找到对应的DubboBeanDefinitionParser
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
    String localName = parserContext.getDelegate().getLocalName(element);
    BeanDefinitionParser parser = this.parsers.get(localName);
    if (parser == null) {
        parserContext.getReaderContext().fatal(
                "Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
    }
    return parser;
}

DubboBeanDefinitionParser中的代码比较多,如果你不是很熟悉dubbo中xml配置方式,估计很难看懂。项目中一般都是通过注解的使用引用服务,xml太过繁琐,简单看看代码吧,有机会再补充

代码语言:javascript
复制
public DubboBeanDefinitionParser(Class<?> beanClass, boolean required) {
    this.beanClass = beanClass;
    this.required = required;
}

@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
    return parse(element, parserContext, beanClass, required);
}

private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
    RootBeanDefinition beanDefinition = new RootBeanDefinition();

    // 这里的beanClass主要有:ApplicationConfig.class、ModuleConfig.class、RegistryConfig.class、MonitorConfig.class、ProviderConfig.class、
    // ConsumerConfig.class、ServiceBean.class、ReferenceBean.class
    beanDefinition.setBeanClass(beanClass);
    beanDefinition.setLazyInit(false);

    // 处理标签中的id,如果id设置为必输但又没有给值,则取name属性的值,如果name属性也没有值,则不同beanClass有不同的取值逻辑
    String id = element.getAttribute("id");
    if ((id == null || id.length() == 0) && required) {
        String generatedBeanName = element.getAttribute("name");
        if (generatedBeanName == null || generatedBeanName.length() == 0) {
            if (ProtocolConfig.class.equals(beanClass)) {
                generatedBeanName = "dubbo";
            } else {
                generatedBeanName = element.getAttribute("interface");
            }
        }
        if (generatedBeanName == null || generatedBeanName.length() == 0) {
            generatedBeanName = beanClass.getName();
        }
        id = generatedBeanName;
        int counter = 2;
        while (parserContext.getRegistry().containsBeanDefinition(id)) {
            id = generatedBeanName + (counter++);
        }
    }

    // 如果有多个相同的id,抛IllegalStateException异常
    if (id != null && id.length() > 0) {
        if (parserContext.getRegistry().containsBeanDefinition(id)) {
            throw new IllegalStateException("Duplicate spring bean id " + id);
        }
        parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
        beanDefinition.getPropertyValues().addPropertyValue("id", id);
    }

    // 当beanClass为ProtocolConfig.class
    if (ProtocolConfig.class.equals(beanClass)) {
        for (String name : parserContext.getRegistry().getBeanDefinitionNames()) {
            BeanDefinition definition = parserContext.getRegistry().getBeanDefinition(name);
            PropertyValue property = definition.getPropertyValues().getPropertyValue("protocol");
            if (property != null) {
                Object value = property.getValue();
                if (value instanceof ProtocolConfig && id.equals(((ProtocolConfig) value).getName())) {
                    definition.getPropertyValues().addPropertyValue("protocol", new RuntimeBeanReference(id));
                }
            }
        }
    } 

    // 当beanClass为ServiceBean.class
    else if (ServiceBean.class.equals(beanClass)) {
        String className = element.getAttribute("class");
        if (className != null && className.length() > 0) {
            RootBeanDefinition classDefinition = new RootBeanDefinition();
            // 设置beanClass为dubbo标签中真正配置的接口所对应的Class
            classDefinition.setBeanClass(ReflectUtils.forName(className));
            classDefinition.setLazyInit(false);
            parseProperties(element.getChildNodes(), classDefinition);
            beanDefinition.getPropertyValues().addPropertyValue("ref", new BeanDefinitionHolder(classDefinition, id + "Impl"));
        }
    } 
    
    // 当beanClass为ProviderConfig.class
    else if (ProviderConfig.class.equals(beanClass)) {
        parseNested(element, parserContext, ServiceBean.class, true, "service", "provider", id, beanDefinition);
    } 
    
    // 当beanClass为ConsumerConfig.class
    else if (ConsumerConfig.class.equals(beanClass)) {
        parseNested(element, parserContext, ReferenceBean.class, false, "reference", "consumer", id, beanDefinition);
    }

    // 以下的逻辑主要用于获取一些属性的值,然后存到MutablePropertyValues中的 List<PropertyValue> propertyValueList。一个属性对应一个
    Set<String> props = new HashSet<String>();
    ManagedMap parameters = null;
    for (Method setter : beanClass.getMethods()) {
        String name = setter.getName();

        // 获取beanClass中的所有 public、只有一个参数 并且以set开头的方法
        if (name.length() > 3 && name.startsWith("set")
                && Modifier.isPublic(setter.getModifiers())
                && setter.getParameterTypes().length == 1) {
            Class<?> type = setter.getParameterTypes()[0];

            // 这里就是为了得到要注入对象的属性名,但是有一点要注意:属性 name => name;属性 userName => user-name
            String property = StringUtils.camelToSplitName(name.substring(3, 4).toLowerCase() + name.substring(4), "-");
            props.add(property);

            // 得到get方法
            Method getter = null;
            try {
                getter = beanClass.getMethod("get" + name.substring(3), new Class<?>[0]);
            } catch (NoSuchMethodException e) {
                try {
                    getter = beanClass.getMethod("is" + name.substring(3), new Class<?>[0]);
                } catch (NoSuchMethodException e2) {
                }
            }
            if (getter == null
                    || !Modifier.isPublic(getter.getModifiers())
                    || !type.equals(getter.getReturnType())) {
                continue;
            }

            // 和dubbo中的一一些标签属性相关
            if ("parameters".equals(property)) {
                parameters = parseParameters(element.getChildNodes(), beanDefinition);
            } else if ("methods".equals(property)) {
                parseMethods(id, element.getChildNodes(), beanDefinition, parserContext);
            } else if ("arguments".equals(property)) {
                parseArguments(id, element.getChildNodes(), beanDefinition, parserContext);
            } else {

                // 获取标签中所配置的属性值
                String value = element.getAttribute(property);
                if (value != null) {
                    value = value.trim();
                    if (value.length() > 0) {
                        if ("registry".equals(property) && RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(value)) {
                            RegistryConfig registryConfig = new RegistryConfig();
                            registryConfig.setAddress(RegistryConfig.NO_AVAILABLE);
                            beanDefinition.getPropertyValues().addPropertyValue(property, registryConfig);
                        } else if ("registry".equals(property) && value.indexOf(',') != -1) {
                            parseMultiRef("registries", value, beanDefinition, parserContext);
                        } else if ("provider".equals(property) && value.indexOf(',') != -1) {
                            parseMultiRef("providers", value, beanDefinition, parserContext);
                        } else if ("protocol".equals(property) && value.indexOf(',') != -1) {
                            parseMultiRef("protocols", value, beanDefinition, parserContext);
                        } else {
                            Object reference;
                            if (isPrimitive(type)) {
                                if ("async".equals(property) && "false".equals(value)
                                        || "timeout".equals(property) && "0".equals(value)
                                        || "delay".equals(property) && "0".equals(value)
                                        || "version".equals(property) && "0.0.0".equals(value)
                                        || "stat".equals(property) && "-1".equals(value)
                                        || "reliable".equals(property) && "false".equals(value)) {
                                    // backward compatibility for the default value in old version's xsd
                                    value = null;
                                }
                                reference = value;
                            } else if ("protocol".equals(property)
                                    && ExtensionLoader.getExtensionLoader(Protocol.class).hasExtension(value)
                                    && (!parserContext.getRegistry().containsBeanDefinition(value)
                                    || !ProtocolConfig.class.getName().equals(parserContext.getRegistry().getBeanDefinition(value).getBeanClassName()))) {
                                if ("dubbo:provider".equals(element.getTagName())) {
                                    logger.warn("Recommended replace <dubbo:provider protocol=\"" + value + "\" ... /> to <dubbo:protocol name=\"" + value + "\" ... />");
                                }
                                // backward compatibility
                                ProtocolConfig protocol = new ProtocolConfig();
                                protocol.setName(value);
                                reference = protocol;
                            } else if ("onreturn".equals(property)) {
                                int index = value.lastIndexOf(".");
                                String returnRef = value.substring(0, index);
                                String returnMethod = value.substring(index + 1);
                                reference = new RuntimeBeanReference(returnRef);
                                beanDefinition.getPropertyValues().addPropertyValue("onreturnMethod", returnMethod);
                            } else if ("onthrow".equals(property)) {
                                int index = value.lastIndexOf(".");
                                String throwRef = value.substring(0, index);
                                String throwMethod = value.substring(index + 1);
                                reference = new RuntimeBeanReference(throwRef);
                                beanDefinition.getPropertyValues().addPropertyValue("onthrowMethod", throwMethod);
                            } else if ("oninvoke".equals(property)) {
                                int index = value.lastIndexOf(".");
                                String invokeRef = value.substring(0, index);
                                String invokeRefMethod = value.substring(index + 1);
                                reference = new RuntimeBeanReference(invokeRef);
                                beanDefinition.getPropertyValues().addPropertyValue("oninvokeMethod", invokeRefMethod);
                            } else {
                                if ("ref".equals(property) && parserContext.getRegistry().containsBeanDefinition(value)) {
                                    BeanDefinition refBean = parserContext.getRegistry().getBeanDefinition(value);
                                    if (!refBean.isSingleton()) {
                                        throw new IllegalStateException("The exported service ref " + value + " must be singleton! Please set the " + value + " bean scope to singleton, eg: <bean id=\"" + value + "\" scope=\"singleton\" ...>");
                                    }
                                }
                                reference = new RuntimeBeanReference(value);
                            }
                            beanDefinition.getPropertyValues().addPropertyValue(property, reference);
                        }
                    }
                }
            }
        }
    }
    NamedNodeMap attributes = element.getAttributes();
    int len = attributes.getLength();
    for (int i = 0; i < len; i++) {
        Node node = attributes.item(i);
        String name = node.getLocalName();
        if (!props.contains(name)) {
            if (parameters == null) {
                parameters = new ManagedMap();
            }
            String value = node.getNodeValue();
            parameters.put(name, new TypedStringValue(value, String.class));
        }
    }
    if (parameters != null) {
        beanDefinition.getPropertyValues().addPropertyValue("parameters", parameters);
    }
    return beanDefinition;
}

解析的主要流程都在代码中有注释,还有一些细节就不过多说明了。

有关于各个标签和具体抽象类的对应关系,其实在DubboNamespaceHandler已经简单的写出来的

代码语言:javascript
复制
registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());

有关于各个标签的简单用法,如下图:

为了让大家看的更具体一些,我从官网扒了一张图下来

有关于它们的继承关系:

以下是dubbo的一个经典xml配置

代码语言:javascript
复制
<!-- 应用信息 -->  
<dubbo:application name="demo"/>  

<!-- 用dubbo协议在20880端口暴露服务 -->  
<dubbo:protocol name="dubbo" port="20880"/>  

<!-- 使用zookeeper注册中心暴露服务地址 -->  
<dubbo:registry address="zookeeper://localhost:2181" id="registry"/>  

<!-- 默认的服务端配置 -->  
<dubbo:provider registry="registry" retries="0" timeout="5000"/>  

<!-- 声明需要暴露的服务接口 -->  
<dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService"/>  

<!-- 引用服务 -->  
<dubbo:reference id="demoService" check="false" interface="org.apache.dubbo.demo.DemoService"/>

结合上面的的对应关系可以发现,大部分标签对应的Bean只是用于维护一些配置信息,比如:ApplicationConfig、ProtocolConfig、ProviderConfig等,这些Bean的实例化细节就不介绍了。重点关注ServiceBean和

ReferenceBean。

已经将dubbo中的标签解析成对应的RootBeanDefinition,接下来就是Spring中正常的Bean实例化流程。Spring在实例化Bean的时候预留了很多接口,也就是生命周期函数,在实例化Bean的时候可以进行各种扩展,dubbo也就是借助这些接口完成了很多的功能。有关于dubbo实例化Bean的细节,将在下一篇文章中详细介绍 【DUBBO】 Bean实例化

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.11.28 ,如有侵权请联系 cloudcommunity@tencent.com 删除

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Spring扩展机制
    • DefaultNamespaceHandlerResolver
      • parseCustomElement
      • 集成Spring
        • XML Schema文件
          • DubboNamespaceHandler
            • DubboBeanDefinitionParser
            相关产品与服务
            容器服务
            腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档