Spring源码阅读-IOC容器初始化过程

简介: Spring IOC容器的初始化过程:Resource定位,BeanDefinition载入,向IOC容器注册BeanDefinition。整个过程由refresh()方法触发,三个过程由不同的模块完成,使用户更加灵活的对这三个过程剪裁和扩展。

Spring IOC容器的初始化过程:Resource定位,BeanDefinition载入,向IOC容器注册BeanDefinition。整个过程由refresh()方法触发,三个过程由不同的模块完成,使用户更加灵活的对这三个过程剪裁和扩展。


BeanDefinition 就是POJO对象在IOC容器中的抽象。通过BeanDefinition 这个数据结构,使IOC容器能够方便的对POJO对象也就是Bean进行管理。将BeanDefinition 注册到hashmap中,也就是一个IOC容器中,通过hashmap持有这些BeanDefinition结构。就好比我们对物品整理归类,苹果,桃子,草莓都是水果,可以归到水果类,然后对水果进行统一的处理,比如放到冰箱保鲜。这里BeanDefinition就是苹果或桃子或李子,容器就是冰箱。


IOC容器的初始化过程不包括Bean依赖注入。Bean定义的载入和依赖注入是两个独立的过程。依赖注入一般发生在应用第一次通过getBean向容器索取Bean的时候。(配置lazyinit属性除外)


具体过程(以FileSystemXmlApplicationContext为例):



Resource定位:

指BeanDefinition资源定位。它由ResourceLoader通过统一的Resource接口完成,这个Resource对各种形式的BeanDefinition的使用提供了统一接口。在文件系统中的BeanDefinition可以用FileSystemResource抽象;在类路径中的BeanDefinition可以用ClassPathResource来抽象。Resource定位就是找水果的过程,水果可能在树上,也可能在地里长着。


BeanDefinition载入:

指把用户定义好的Bean表示成IOC容器的数据结构,这个数据结构就是BeanDefinition。



注册:

指向IOC容器注册这些BeanDefinition的过程。通俗的讲就是将BeanDefinition注入到一个hashmap中。



以FileSystemXmlApplicationContext为例来看下IOC容器初始化的过程。

我们先看下FileSystemXmlApplicationContext继承关系:



02055ddcbd5bc96297b0c47b030a80f4.png




FileSystemXmlApplicationContext源码:

package org.springframework.context.support;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
/**
 * FileSystemXmlApplicationContext是一个支持xml定义BeanDefinition的ApplicationContext,
 * 并且可以指定以文件形式的BeanDefinition的读入,这些文件可以使用文件路径和URL定义来表示。
 * 在测试环境和独立应用环境中,这个ApplicationContext非常有用。
 */
public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext {
    public FileSystemXmlApplicationContext() {
    }
    public FileSystemXmlApplicationContext(ApplicationContext parent) {
        super(parent);
    }
    /**
     * 从给定的xml文件中加载Bean定义,创建一个FileSystemXmlApplicationContext,并自动刷新上下文
     * configLocation包含的是BeanDefinition所在的文件路径 
     */
    public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
        this(new String[] {configLocation}, true, null);
    }
    public FileSystemXmlApplicationContext(String... configLocations) throws BeansException {
        this(configLocations, true, null);
    }
    /**
     * 允许configLocation包含多个BeanDefinition的文件路径的同时,还允许指定自己的双亲IOC容器
     */
    public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException {
        this(configLocations, true, parent);
    }
    public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
        this(configLocations, refresh, null);
    }
    /**
     * 在对象的初始化过程中,调用refresh函数载入BeanDefinition,
     * 这个refresh()启动了BeanDefinition的载入过程
     */
    public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
            throws BeansException {
        super(parent);
        setConfigLocations(configLocations);
        if (refresh) {
            refresh();
        }
    }
    /**
     * 通过构造一个FileSystemResource来得到一个在文件系统中定位的BeanDefinition
     */
    @Override
    protected Resource getResourceByPath(String path) {
        if (path != null && path.startsWith("/")) {
            path = path.substring(1);
        }
        return new FileSystemResource(path);
    }
}



从前面的类图中我们可以看出,FileSystemXmlApplicationContext已经通过继承AbstractApplicationContext具备了ResourceLoader读入以Resource定义的BeanDefinition的能力。因为AbstractApplicationContext的基类是DefaultResourceLoader。


BeanDefinitionReader

在介绍IOC容器初始化过程之前,我们先了解一个类:BeanDefinition读取器-BeanDefinitionReader。BeanDefinitionReader利用ResouceLoader从指定位置加载Bean定义,将物理资源转化生成Resource。


a2e1b76ebe9fba3d20540b8f3107eb98.png



以编程的方式使用DefaultListableBeanFactory时,我们定义一个Resource来定位容器使用的BeanDefinition:ClassPathResource res = new ClassPathResource("beans.xml");


这里定义的Resource并不能由DefaultListableBeanFactory直接使用,而是通过BeanDefinitionReader来对这些信息进行处理。Spring为我们提供了一系列加载不同Resource的读取器的实现,使不同的容器可以配置相应的BeanDefinitionReader。比如FileSystemXmlApplicationContext的父类AbstractXmlApplicationContext用的读取器是XmlBeanDefinitionReader类型。



AbstractXmlApplicationContext

1.  
    @Override
    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        // Create a new XmlBeanDefinitionReader for the given BeanFactory.
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
        // Configure the bean definition reader with this context's
        // resource loading environment.
        beanDefinitionReader.setEnvironment(this.getEnvironment());
        beanDefinitionReader.setResourceLoader(this);
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
        // Allow a subclass to provide custom initialization of the reader,
        // then proceed with actually loading the bean definitions.
        initBeanDefinitionReader(beanDefinitionReader);
        loadBeanDefinitions(beanDefinitionReader);
    }
    // 用给定的XmlBeanDefinitionReader加载BeanDefinition
    protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
        Resource[] configResources = getConfigResources();
        if (configResources != null) {
            reader.loadBeanDefinitions(configResources);
        }
        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
            reader.loadBeanDefinitions(configLocations);
        }
    }



推荐阅读:SpringIOC源码解析(4)—— Resource、ResourceLoader、容器之间的微妙关系


下面我们进入正题,看一下FileSystemXmlApplicationContext的初始化流程:


FileSystemXmlApplicationContext的初始化流程由父类AbstractApplicationContext的refresh()方法触发。


AbstractApplicationContext类refresh()方法:

public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();
            // 子类启动refreshBeanFactory()
            // FileSystemXmlApplicationContext 是AbstractApplicationContext的子类
            // 在这一步执行refreshBeanFactory
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
            // 准备用于此上下文的beanFactory
            prepareBeanFactory(beanFactory);
            try {
                // 设置beanFactory的后置处理
                postProcessBeanFactory(beanFactory);
                // 在上下文中调用注册为bean的后置处理器。
                invokeBeanFactoryPostProcessors(beanFactory);
                // 注册拦截bean创建的bean处理器。
                registerBeanPostProcessors(beanFactory);
                // 初始化上下文中的消息源
                initMessageSource();
                // 为上下文初始化事件多播机制
                initApplicationEventMulticaster();
                // 初始化其他特殊的Bean
                onRefresh();
                // 检查监听的Bean并注册他们到容器中
                registerListeners();
                // 实例化所有剩余的(非懒加载)的单例
                finishBeanFactoryInitialization(beanFactory);
                // 最后:发布容器事件
                finishRefresh();
            }
            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }
                // 为避免资源占用销毁在前面创建的单例
                destroyBeans();
                // 重置Reset 'active' 表示
                cancelRefresh(ex);
                // 向调用方抛出异常
                throw ex;
            }
            finally {
                // 重置Spring核心中的常见缺省缓存,因为我们可能不再需要单例bean的元数据。。。
                resetCommonCaches();
            }
        }
    }


先看一张流程图(为了突出关键步骤,只画了关键类,省略了参数):



网络异常,图片无法展示
|

FileSystemResource定位



image.png



结合流程图可以看到,最后getResourceByPath()方法会被子类FileSystemXmlApplicationContext实现,返回一个FileSystemResource对象,完成BeanDefinition的定位。Spring通过FileSystemResource对象,可以进行相关的IO操作。


定位完成后,就可以用返回的Resource对象来进行BeanDefinition的载入了。


BeanDefinition载入



image.png


所谓BeanDefinition载入,就是把xml文件中定义的Bean定义,进过转换和层层解析,根据解析结果,将属性值封装成Property value对象设置到BeanDefinition中,处理成IOC容器中的数据结构。


入口在BeanDefinitionParserDelegate类的parseBeanDefinitionElement(Element ele, BeanDefinition containingBean)方法。


核心处理方法是:


parseBeanDefinitionElement(Element ele, String beanName, BeanDefinition containingBean)


我们重点看下:

// 解析bean定义本身,不考虑名称或别名。如果在解析bean定义期间出现问题可能返回null
// 只是把Bean的属性设置载入到BeanDefinition,不涉及对象的实例化过程。
// 对象的实例化在依赖注入时完成
public AbstractBeanDefinition parseBeanDefinitionElement(
            Element ele, String beanName, BeanDefinition containingBean) {
        this.parseState.push(new BeanEntry(beanName));
        String className = null;
        if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
            className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
        }
        try {
            String parent = null;
            if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
                parent = ele.getAttribute(PARENT_ATTRIBUTE);
            }
            // 生成需要的BeanDefinition对象,为Bean定义信息的载入做准备
            AbstractBeanDefinition bd = createBeanDefinition(className, parent);
            // 对当前的Bean元素进行属性解析,并设置description信息
            parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
            bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
            // 对各种<bean>元素的信息进行解析
            parseMetaElements(ele, bd);
            parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
            parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
            // 解析<bean>的构造函数设置
            parseConstructorArgElements(ele, bd);
            // 解析<bean>的property设置
            parsePropertyElements(ele, bd);
            parseQualifierElements(ele, bd);
            bd.setResource(this.readerContext.getResource());
            bd.setSource(extractSource(ele));
            return bd;
        }
        // 这里是我们经常见到的异常处理(原来是在载入BeanDefinition时处理的)
        catch (ClassNotFoundException ex) {
            error("Bean class [" + className + "] not found", ele, ex);
        }
        catch (NoClassDefFoundError err) {
            error("Class that bean class [" + className + "] depends on not found", ele, err);
        }
        catch (Throwable ex) {
            error("Unexpected failure during bean definition parsing", ele, ex);
        }
        finally {
            this.parseState.pop();
        }
        return null;
    }



BeanDefinitionParserDelegate类:


解析<bean>的property设置,对property子元素进行解析,Array、List、Set、Map、prop等各种元素都会在这里解析,生成对应的数据对象。比如MangedList、ManagedArray、ManageSet等,是Spring对具体的BeanDefinition的数据封装。

    /**
     * 分析给定元素子元素属性
     */
    public void parsePropertyElements(Element beanEle, BeanDefinition bd) {
        NodeList nl = beanEle.getChildNodes();
        // 遍历处理所以的子元素
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) {
                parsePropertyElement((Element) node, bd);
            }
        }
    }
public void parsePropertyElement(Element ele, BeanDefinition bd) {
        // 取得property的名字
        String propertyName = ele.getAttribute(NAME_ATTRIBUTE);
        if (!StringUtils.hasLength(propertyName)) {
            error("Tag 'property' must have a 'name' attribute", ele);
            return;
        }
        this.parseState.push(new PropertyEntry(propertyName));
        try {
           // 如果同一个Bean中已经有同名的property存在,则不进行解析,直接返回。
            if (bd.getPropertyValues().contains(propertyName)) {
                error("Multiple 'property' definitions for property '" + propertyName + "'", ele);
                return;
            }
            Object val = parsePropertyValue(ele, bd, propertyName);
            PropertyValue pv = new PropertyValue(propertyName, val);
            parseMetaElements(ele, pv);
            pv.setSource(extractSource(ele));
            bd.getPropertyValues().addPropertyValue(pv);
        }
        finally {
            this.parseState.pop();
        }
    }
/**
* 取得每个property的值,也许是一个list或其他
*/
public Object parsePropertyValue(Element ele, BeanDefinition bd, String propertyName) {
        String elementName = (propertyName != null) ?
                        "<property> element for property '" + propertyName + "'" :
                        "<constructor-arg> element";
        // 应该只有一个子节点: 引用类型, 值类型, 集合等.
        NodeList nl = ele.getChildNodes();
        Element subElement = null;
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT) &&
                    !nodeNameEquals(node, META_ELEMENT)) {
                // Child element is what we're looking for.
                if (subElement != null) {
                    error(elementName + " must not contain more than one sub-element", ele);
                }
                else {
                    subElement = (Element) node;
                }
            }
        }
        // 这里判断property的属性,是ref还是value。不允许同时是ref和value
        boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);
        boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);
        if ((hasRefAttribute && hasValueAttribute) ||
                ((hasRefAttribute || hasValueAttribute) && subElement != null)) {
            error(elementName +
                    " is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele);
        }
        // 如果是ref,创建一个RuntimeBeanReference类型的对象,封装ref的信息
        if (hasRefAttribute) {
            String refName = ele.getAttribute(REF_ATTRIBUTE);
            if (!StringUtils.hasText(refName)) {
                error(elementName + " contains empty 'ref' attribute", ele);
            }
            RuntimeBeanReference ref = new RuntimeBeanReference(refName);
            ref.setSource(extractSource(ele));
            return ref;
        }
        // 如果是value,创建一个TypedStringValue类型的对象,这个对象封装了value的信息
        else if (hasValueAttribute) {
            TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));
            valueHolder.setSource(extractSource(ele));
            return valueHolder;
        }
        // 如果还有其他子元素,触发对子元素的解析
        else if (subElement != null) {
            return parsePropertySubElement(subElement, bd);
        }
        else {
            // Neither child element nor "ref" or "value" attribute found.
            error(elementName + " must specify a ref or value", ele);
            return null;
        }
    }


/**
     * 分析属性或构造函数arg元素的值、ref或集合子元素。
     * @param ele 属性元素的子元素;我们还不知道是哪个
     * @param defaultValueType 任何的默认类型(类名)
     * {@code <value>} 可能创建的标记
     */
    public Object parsePropertySubElement(Element ele, BeanDefinition bd, String defaultValueType) {
        if (!isDefaultNamespace(ele)) {
            return parseNestedCustomElement(ele, bd);
        }
        // 节点是Bean
        else if (nodeNameEquals(ele, BEAN_ELEMENT)) {
            BeanDefinitionHolder nestedBd = parseBeanDefinitionElement(ele, bd);
            if (nestedBd != null) {
                nestedBd = decorateBeanDefinitionIfRequired(ele, nestedBd, bd);
            }
            return nestedBd;
        }
        // 节点是引用类型
        else if (nodeNameEquals(ele, REF_ELEMENT)) {
            // 对任何bean的任何名称的通用引用。
            String refName = ele.getAttribute(BEAN_REF_ATTRIBUTE);
            boolean toParent = false;
            if (!StringUtils.hasLength(refName)) {
                // 对同一XML文件中另一个bean的id的引用。
                refName = ele.getAttribute(LOCAL_REF_ATTRIBUTE);
                if (!StringUtils.hasLength(refName)) {
                    // 对父上下文中另一个bean的id的引用。
                    refName = ele.getAttribute(PARENT_REF_ATTRIBUTE);
                    toParent = true;
                    if (!StringUtils.hasLength(refName)) {
                        error("'bean', 'local' or 'parent' is required for <ref> element", ele);
                        return null;
                    }
                }
            }
            if (!StringUtils.hasText(refName)) {
                error("<ref> element contains empty target attribute", ele);
                return null;
            }
            RuntimeBeanReference ref = new RuntimeBeanReference(refName, toParent);
            ref.setSource(extractSource(ele));
            return ref;
        }
        // 节点是idref类型
        else if (nodeNameEquals(ele, IDREF_ELEMENT)) {
            return parseIdRefElement(ele);
        }
        // 节点是value
        else if (nodeNameEquals(ele, VALUE_ELEMENT)) {
            return parseValueElement(ele, defaultValueType);
        }
        // 节点是null
        else if (nodeNameEquals(ele, NULL_ELEMENT)) {
            // 对象是null,创建一个TypedStringValue类型的对象为其保留地址空间
            TypedStringValue nullHolder = new TypedStringValue(null);
            nullHolder.setSource(extractSource(ele));
            return nullHolder;
        }
        // 节点是数组类型
        else if (nodeNameEquals(ele, ARRAY_ELEMENT)) {
            return parseArrayElement(ele, bd);
        }
        // 节点是List集合类型
        else if (nodeNameEquals(ele, LIST_ELEMENT)) {
            return parseListElement(ele, bd);
        }
        // 节点是Set集合类型
        else if (nodeNameEquals(ele, SET_ELEMENT)) {
            return parseSetElement(ele, bd);
        }
        // 节点是Map类型
        else if (nodeNameEquals(ele, MAP_ELEMENT)) {
            return parseMapElement(ele, bd);
        }
        // 节点是props类型
        else if (nodeNameEquals(ele, PROPS_ELEMENT)) {
            return parsePropsElement(ele);
        }
        else {
            error("Unknown property sub-element: [" + ele.getNodeName() + "]", ele);
            return null;
        }
    }


我们看一下List这样的属性配置是如何被解析的:

// 返回ManagedList类型的对象
public List<Object> parseListElement(Element collectionEle, BeanDefinition bd) {
        String defaultElementType = collectionEle.getAttribute(VALUE_TYPE_ATTRIBUTE);
        NodeList nl = collectionEle.getChildNodes();
        ManagedList<Object> target = new ManagedList<Object>(nl.getLength());
        target.setSource(extractSource(collectionEle));
        target.setElementTypeName(defaultElementType);
        target.setMergeEnabled(parseMergeAttribute(collectionEle));
        // 具体的List元素解析过程
        parseCollectionElements(nl, target, bd, defaultElementType);
        return target;
    }
    protected void parseCollectionElements(
            NodeList elementNodes, Collection<Object> target, BeanDefinition bd, String defaultElementType) {
        for (int i = 0; i < elementNodes.getLength(); i++) {
            Node node = elementNodes.item(i);
            // node需要是element类型
            if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT)) {
                // 将node添加到taget中,并递归解析下一层子元素 
                target.add(parsePropertySubElement((Element) node, bd, defaultElementType));
            }
        }
    }


经过这样层层解析,我们就将xml文件中定义的Definition加载到了IOC容器中,并在容器中建立了映射,完成了IOC容器管理Bean对象的准备工作。这些数据结构可以以AbstractBeanDefinition为入口,让IOC容器执行索引、查询和操作。你没有看错,这一小节,经过了这么多的处理,就是对xml文件解析,把内容处理成BeanDefinition。看迷茫的同学可以结合类图,往回倒一下处理的入口。


这时候IOC容器BeanDefinition中存在的还只是一些静态的配置信息,要想让容器起作用,还需完成数据向容器的注册


BeanDefinition在IOC容器中的注册


image.png



我们回到DefaultBeanDefinitionDocumentReader类

    /**
     * 处理给定的bean元素,解析bean定义并将其注册到注册表中。
     */
    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
        // BeanDefinition载入
        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
        if (bdHolder != null) {
            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
            try {
                // 注册实例
                BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
            }
            catch (BeanDefinitionStoreException ex) {
                getReaderContext().error("Failed to register bean definition with name '" +
                        bdHolder.getBeanName() + "'", ele, ex);
            }
            // Send registration event.
            getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
        }
    }



到此,完成了BeanDefinition的注册,完成了IOC容器的初始化过程。此时使用的IOC容器DefaultListableBeanFactory中已经建立了整个Bean的配置信息,而且这些BeanDefinition已经可以被容器使用了,并且可以在BeanDefinitionMap中被检索到和使用。通过容器对这些Bean信息进行处理和维护


相关文章
|
2天前
|
监控 Java 应用服务中间件
Spring Boot 源码面试知识点
【5月更文挑战第12天】Spring Boot 是一个强大且广泛使用的框架,旨在简化 Spring 应用程序的开发过程。深入了解 Spring Boot 的源码,有助于开发者更好地使用和定制这个框架。以下是一些关键的知识点:
20 6
|
2天前
|
Java 应用服务中间件 测试技术
深入探索Spring Boot Web应用源码及实战应用
【5月更文挑战第11天】本文将详细解析Spring Boot Web应用的源码架构,并通过一个实际案例,展示如何构建一个基于Spring Boot的Web应用。本文旨在帮助读者更好地理解Spring Boot的内部工作机制,以及如何利用这些机制优化自己的Web应用开发。
29 3
|
2天前
|
XML Java 数据格式
Spring框架入门:IoC与DI
【5月更文挑战第15天】本文介绍了Spring框架的核心特性——IoC(控制反转)和DI(依赖注入)。IoC通过将对象的创建和依赖关系管理交给容器,实现解耦。DI作为IoC的实现方式,允许外部注入依赖对象。文章讨论了过度依赖容器、配置复杂度等常见问题,并提出通过合理划分配置、使用注解简化管理等解决策略。同时,提醒开发者注意过度依赖注入和循环依赖,建议适度使用构造器注入和避免循环引用。通过代码示例展示了注解实现DI和配置类的使用。掌握IoC和DI能提升应用的灵活性和可维护性,实践中的反思和优化至关重要。
17 4
|
2天前
|
XML Java 程序员
Spring特性之二——IOC控制反转
Spring特性之二——IOC控制反转
16 4
|
2天前
|
存储 前端开发 Java
Spring Boot自动装配的源码学习
【4月更文挑战第8天】Spring Boot自动装配是其核心机制之一,其设计目标是在应用程序启动时,自动配置所需的各种组件,使得应用程序的开发和部署变得更加简单和高效。下面是关于Spring Boot自动装配的源码学习知识点及实战。
16 1
|
2天前
|
传感器 人工智能 前端开发
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
智慧校园电子班牌,坐落于班级的门口,适合于各类型学校的场景应用,班级学校日常内容更新可由班级自行管理,也可由学校统一管理。让我们一起看看,电子班牌有哪些功能呢?
103 4
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
|
2天前
|
监控 Kubernetes Docker
【Docker 专栏】Docker 容器内应用的健康检查与自动恢复
【5月更文挑战第9天】本文探讨了Docker容器中应用的健康检查与自动恢复,强调其对应用稳定性和系统性能的重要性。健康检查包括进程、端口和应用特定检查,而自动恢复则涉及重启容器和重新部署。Docker原生及第三方工具(如Kubernetes)提供了相关功能。配置检查需考虑检查频率、应用特性和监控告警。案例分析展示了实际操作,未来发展趋势将趋向更智能和高效的检查恢复机制。
【Docker 专栏】Docker 容器内应用的健康检查与自动恢复
|
2天前
|
Ubuntu Docker 容器
docker容器保存和导入
docker容器保存和导入
20 0
|
2天前
|
Ubuntu Docker 容器
清理docker容器
清理docker容器
12 0
|
2天前
|
Prometheus 监控 Cloud Native
构建高效稳定的Docker容器监控体系
【5月更文挑战第14天】 在现代微服务架构中,Docker容器作为应用部署的基本单元,其运行状态的监控对于保障系统稳定性和性能至关重要。本文将探讨如何构建一个高效且稳定的Docker容器监控体系,涵盖监控工具的选择、关键指标的采集、数据可视化以及告警机制的设计。通过对Prometheus和Grafana的整合使用,实现对容器资源利用率、网络IO以及应用健康状态的全方位监控,确保系统的高可用性和故障快速响应。

热门文章

最新文章