spring的启动过程(一) :IOC容器的启动过程

news2024/10/6 12:31:36

一、web容器的加载
首先我们要先知道一个web项目的启动过程。

  1. 将Web项目部署到Tomcat中的方法之一,是部署没有封装到WAR文件中的Web项目。要使用这一方法部署未打包的webapp目录,只要把我们的项目(编译好的发布项目,非开发项目)放到Tomcat的webapps目录下就可以了。
    在这里插入图片描述

  2. 一个常规的Spring应用,在web容器启动时,默认会先去加载/WEB-INF/web.xml,它配置了:servletContext上下文、监听器(Listener)、过滤器(Filter)、Servlet等。

二、spring的启动

spring的启动过程其实就是ioc的启动过程。
spring的ioc支持了controller层注入service,service注入dao。打通了各层之间的桥梁,省去了原来的new service(),new Dao()的方法。

1、如上面所言,spring启动优先加载了web.xml,我们看看里面配置的内容。
web.xml 加载顺序为: context-param < listener < filter < servlet

    <!--该元素用来声明应用范围(整个WEB项目)内的上下文初始化参数。 -->
    
    <context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath*:applicationContext*.xml</param-value>
	</context-param>
	
    <!--监听器-->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	
    <!--servlet,mvc的前端控制器 -->
	<servlet>
		<servlet-name>mvc</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
	
    <!--截获请求,匹配的请求都交由上面配置的mvc这个servlet处理 -->
	<servlet-mapping>
		<servlet-name>mvc</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>

	 <!--过滤器 -->
	<filter>
		<filter-name>LoginValidateFilter</filter-name>
		<filter-class>com.tianque.clue.web.LoginValidateFilter</filter-class>
	</filter>
	
    <!--截获请求,匹配的请求都经过上面配置的LoginValidateFilter过滤处理 -->
	<filter-mapping>
		<filter-name>LoginValidateFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
 在启动Web项目时,容器会读web.xml配置文件中的两个节点context-param和listener。

知晓web.xml配置内容后,我们详细看一下。

2.ContextLoaderListener
在启动Web项目时,容器(比如Tomcat)会读web.xml配置文件中的两个节点
listener和context-param

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    public ContextLoaderListener() {
    }

    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }
    //该方法contextInitialized是重点
    public void contextInitialized(ServletContextEvent event) {
        this.initWebApplicationContext(event.getServletContext());
    }

    public void contextDestroyed(ServletContextEvent event) {
        this.closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
}

接着进入this.initWebApplicationContext方法,进入ContextLoader类中

 public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
        if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
            throw new IllegalStateException("Cannot initialize context because there is already a root application context present - check whether you have multiple ContextLoader* definitions in your web.xml!");
        } else {
            //日志略
            .......
            long startTime = System.currentTimeMillis();

            try {
                if (this.context == null) {
                    //创建 WebApplicationContext
                    this.context = this.createWebApplicationContext(servletContext);
                }

                if (this.context instanceof ConfigurableWebApplicationContext) {
                    ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
                    if (!cwac.isActive()) {
                        if (cwac.getParent() == null) {
                            ApplicationContext parent = this.loadParentContext(servletContext);
                            cwac.setParent(parent);
                        }
                        //该方法是重点,赋值初始化
                        this.configureAndRefreshWebApplicationContext(cwac, servletContext);
                    }
                }
                //略
                ........
                return this.context;
            } catch (Error | RuntimeException var8) {
                logger.error("Context initialization failed", var8);
                servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var8);
                throw var8;
            }
        }
    }

点进该方法细看源码

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
        String configLocationParam;
        if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
            configLocationParam = sc.getInitParameter("contextId");
            if (configLocationParam != null) {
                wac.setId(configLocationParam);
            } else {
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath()));
            }
        }

        wac.setServletContext(sc);
        
        //眼熟吧 contextConfigLocation就是web.xml里<context-param>
        configLocationParam = sc.getInitParameter("contextConfigLocation");
        if (configLocationParam != null) {
            wac.setConfigLocation(configLocationParam);
        }

        ConfigurableEnvironment env = wac.getEnvironment();
        if (env instanceof ConfigurableWebEnvironment) {
            ((ConfigurableWebEnvironment)env).initPropertySources(sc, (ServletConfig)null);
        }

        this.customizeContext(sc, wac);
        //重点方法,接来下会细看
        wac.refresh();
    }

点进去 wac.refresh();最后到了AbstractApplicationContext类中的refresh()

 public void refresh() throws BeansException, IllegalStateException {
        Object var1 = this.startupShutdownMonitor;
        //加锁
        synchronized(this.startupShutdownMonitor) {
            //创建和准备了 Environment 对象
            this.prepareRefresh();
            //重点,该方法执行结束之后,xml中定义的bean就已经加载到IOC容器中了
            //然而Bean 并没有完成初始化
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            
            //完善 BeanFactory,设置 BeanFactory 的类加载器,添加 BeanPostProcessor,
            //手动注册几个特殊的 bean。
            this.prepareBeanFactory(beanFactory);

            try {
                //空方法
                this.postProcessBeanFactory(beanFactory);
                //执行BeanFactory后置处理器,可以用来补充或修改 BeanDefinition
                this.invokeBeanFactoryPostProcessors(beanFactory);
            //继续从 beanFactory 中找出 bean 后处理器,添加至 beanPostProcessors 集合中
                this.registerBeanPostProcessors(beanFactory);
                //国际化
                this.initMessageSource();
                //
                this.initApplicationEventMulticaster();
                //空实现
                this.onRefresh();
                //注册监听器
                this.registerListeners();
                
                //这一步会将 beanFactory 的成员补充完毕,并初始化所有非延迟单例 bean
                //Spring怎么解决循环依赖问题就在该方法里面的getBean再里面的
                // doCreateBean()方法
                //再往里的AbstractAutowireCapableBeanFactory类的doCreateBean会判断
                //是否有aop加强,在里面获得bean加强后的代理对象(aop源码)
                //Spring为了解决单例的循环依赖问题,使用了三级缓存,当个问题写在文章最后
                this.finishBeanFactoryInitialization(beanFactory);
                
                //这一步会为 ApplicationContext 添加 lifecycleProcessor 成员,
                //用来控制容器内需要生命周期管理的 bean
                this.finishRefresh();
            } catch (BeansException var9) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                }

                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            }

        }
    }

点进去 this.obtainFreshBeanFactory();

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        //创建beanFactory
        this.refreshBeanFactory();
        return this.getBeanFactory();
    }

进入 AbstractRefreshableApplicationContext类的refreshBeanFactory();

 protected final void refreshBeanFactory() throws BeansException {
        if (this.hasBeanFactory()) {
            this.destroyBeans();
            this.closeBeanFactory();
        }

        try {
            DefaultListableBeanFactory beanFactory = this.createBeanFactory();
            beanFactory.setSerializationId(this.getId());
            this.customizeBeanFactory(beanFactory);
            //重点 加载bean定义
            this.loadBeanDefinitions(beanFactory);
            Object var2 = this.beanFactoryMonitor;
            synchronized(this.beanFactoryMonitor) {
                this.beanFactory = beanFactory;
            }
        } catch (IOException var5) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var5);
        }
    }

点进去到XmlWebApplicationContext类的loadBeanDefinitions方法

 protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
       //创建XmlBeanDefinitionReader对象,用于解析xml文件中定义的bean,
       // 将xml文件转化为Resource流对象
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
        beanDefinitionReader.setEnvironment(this.getEnvironment());
        beanDefinitionReader.setResourceLoader(this);
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
        this.initBeanDefinitionReader(beanDefinitionReader);
        //重点方法
        this.loadBeanDefinitions(beanDefinitionReader);
    }

点击进去细看

 protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
        //configLocations 获取到的就是applicationContext.xml文件
        String[] configLocations = this.getConfigLocations();
        if (configLocations != null) {
            String[] var3 = configLocations;
            int var4 = configLocations.length;
            //遍历每个配置文件,将配置文件中的标签解析成bean
            for(int var5 = 0; var5 < var4; ++var5) {
                String configLocation = var3[var5];
                //重点方法
                reader.loadBeanDefinitions(configLocation);
            }
        }

    }
    public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
        return this.loadBeanDefinitions(location, (Set)null);
    }

    public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
        ResourceLoader resourceLoader = this.getResourceLoader();
        if (resourceLoader == null) {
            throw new BeanDefinitionStoreException("Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
        } else {
            int count;
            if (resourceLoader instanceof ResourcePatternResolver) {
                try {
                   //拿到resources 即配置文件转化的
                    Resource[] resources = ((ResourcePatternResolver)resourceLoader).getResources(location);
                    //继续进入方法
                    count = this.loadBeanDefinitions(resources);
                    if (actualResources != null) {
                        Collections.addAll(actualResources, resources);
                    }

                    if (this.logger.isTraceEnabled()) {
                        this.logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
                    }

                    return count;
                } catch (IOException var6) {
                    throw new BeanDefinitionStoreException("Could not resolve bean definition resource pattern [" + location + "]", var6);
                }
            } else {
             ............
            }
        }
    }

拿到resource[xml文件],进入this.loadBeanDefinitions(resources)方法,一路往下走,最终又会进入XmlBeanDefinitionReader类的loadBeanDefinitions(EncodedResource encodedResource)方法

 public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        Assert.notNull(encodedResource, "EncodedResource must not be null");
        ...略......
        if (!((Set)currentResources).add(encodedResource)) {
            throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        } else {
            int var5;
            try {
                InputStream inputStream = encodedResource.getResource().getInputStream();

                try {
                    InputSource inputSource = new InputSource(inputStream);
                    if (encodedResource.getEncoding() != null) {
                        inputSource.setEncoding(encodedResource.getEncoding());
                    }
                    //重点方法,进入细看
                    var5 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
                } finally {
                    inputStream.close();
                }
            } catch (IOException var15) {
              .........
            } finally {
               ..........
            }

            return var5;
        }
    }
 protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
        try {
            //xml配置文件解析成document对象
            Document doc = this.doLoadDocument(inputSource, resource);
            //注册bean,重点
            int count = this.registerBeanDefinitions(doc, resource);
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Loaded " + count + " bean definitions from " + resource);
            }

            return count;
        } catch (BeanDefinitionStoreException var5) {
            throw var5;
        } 
        //各种try catch异常代码略
    }

doLoadBeanDefinitions()方法中主要包含2个步骤,第一步根据inputSource和resource获取到一个Document对象,我们知道xml文档可以解析成一个document树,其中最外层标签就是root元素,子标签就是一个个的叶子node,具体的解析成Document对象的过程不用过于纠结,Spring提供了详细实现, 第二步就是讲document对象注册到Spring容器里,而resouce参数用来选择XmlReaderContext

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();
        int countBefore = this.getRegistry().getBeanDefinitionCount();
        //重点,点进去细看
        documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));
        return this.getRegistry().getBeanDefinitionCount() - countBefore;
    }

一步步点击

 protected void doRegisterBeanDefinitions(Element root) {
        BeanDefinitionParserDelegate parent = this.delegate;
        this.delegate = this.createDelegate(this.getReaderContext(), root, parent);
        if (this.delegate.isDefaultNamespace(root)) {
            String profileSpec = root.getAttribute("profile");
            if (StringUtils.hasText(profileSpec)) {
                String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
                if (!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                    if (this.logger.isDebugEnabled()) {
                        this.logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + this.getReaderContext().getResource());
                    }

                    return;
                }
            }
        }

        this.preProcessXml(root);
        //重点方法,点进去细看
        this.parseBeanDefinitions(root, this.delegate);
        this.postProcessXml(root);
        this.delegate = parent;
    }

 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)) {
                       //重点方法,解析标签元素
                        this.parseDefaultElement(ele, delegate);
                    } else {
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        } else {
            delegate.parseCustomElement(root);
        }

    }
 private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
        if (delegate.nodeNameEquals(ele, "import")) {
            this.importBeanDefinitionResource(ele);
        } else if (delegate.nodeNameEquals(ele, "alias")) {
            this.processAliasRegistration(ele);
        } else if (delegate.nodeNameEquals(ele, "bean")) {
            //重点看bean标签
            this.processBeanDefinition(ele, delegate);
        } else if (delegate.nodeNameEquals(ele, "beans")) {
            this.doRegisterBeanDefinitions(ele);
        }

    }
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
        //不细看了,把bean标签的id,name解析返回BeanDefinitionHolder
        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
        if (bdHolder != null) {//有需要就装饰
            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);

            try {
                //重点,点进去细看
                BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.getReaderContext().getRegistry());
            } catch (BeanDefinitionStoreException var5) {
                this.getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, var5);
            }

            this.getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
        }

    }
public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {
        String beanName = definitionHolder.getBeanName();
        //重点,点进去细看
        registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
        String[] aliases = definitionHolder.getAliases();
        if (aliases != null) {
            String[] var4 = aliases;
            int var5 = aliases.length;

            for(int var6 = 0; var6 < var5; ++var6) {
                String alias = var4[var6];
                registry.registerAlias(beanName, alias);
            }
        }

    }

最终就是往Map<String, BeanDefinition> beanDefinitionMap中放beanDefinition

 this.beanDefinitionMap.put(beanName, beanDefinition);

至此this.obtainFreshBeanFactory()方法方法解析完毕。
该方法总结:

  • 这一步获取(或创建) BeanFactory,它也是作为 ApplicationContext 的一个成员变量

  • BeanFactory 的作用是负责 bean 的创建、依赖注入和初始化,bean 的各项特征由 BeanDefinition 定义

  • BeanDefinition 作为 bean 的设计蓝图,规定了 bean 的特征,如单例多例、依赖关系、初始销毁方法等

  • BeanDefinition 的来源有多种多样,可以是通过 xml 获得、配置类获得、组件扫描获得,也可以是编程添加

  • 所有的 BeanDefinition 会存入 BeanFactory 中的 beanDefinitionMap 集合

核心方法讲解完了,其他方法感兴趣可自行了解,不多赘述了。最后写个小问题。

spring管理的bean在默认情况下是会在服务器启动的时候初始化的。
bean设置了scope为prototype(原型)之后,会每次使用时生产一个
bean设置了lazy-init=”true”后,启动服务器不会马上实例化,而是在用到的时候被实例化。

问题:Spring怎么解决循环依赖问题?
循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。
doCreateBean 方法有三个核心流程。实例化,填充属性,初始化。
循环依赖主要发生在第一、第二步。也就是构造器循环依赖和field循环依赖。
那么我们要解决循环引用也应该从初始化过程着手,对于单例来说,在Spring容器整个生命周期内,有且只有一个对象,所以很容易想到这个对象应该存在Cache中,Spring为了解决单例的循环依赖问题,使用了三级缓存。

我们看一下doGetBean方法中的getSingleton方法。

Object sharedInstance = this.getSingleton(beanName);
 protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        Object singletonObject = this.singletonObjects.get(beanName);
        //判断当前单例bean是否正在创建中,也就是没有初始化完成
        //比如A的构造器依赖了B对象所以得先去创建B对象
		// 或则在A的populateBean过程中依赖了B对象,得先去创建B对象,
		//这时的A就是处于创建中的状态。
        if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
            Map var4 = this.singletonObjects;
            synchronized(this.singletonObjects) {
                singletonObject = this.earlySingletonObjects.get(beanName);
                // 是否允许从singletonFactories中通过getObject拿到对象
                if (singletonObject == null && allowEarlyReference) {
                    ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }

        return singletonObject;
    }

该方法还依赖于三个map,这三个map就是三级缓存。
分析getSingleton()的整个过程,Spring首先从一级缓存singletonObjects中获取。如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。

如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取,如果获取到了则从singletonFactories中移除,并放入earlySingletonObjects中。

   singletonObject = singletonFactory.getObject();
   this.earlySingletonObjects.put(beanName, singletonObject);
   this.singletonFactories.remove(beanName);

其实也就是从三级缓存移动到了二级缓存。

从上面三级缓存的分析,我们可以知道,Spring解决循环依赖的诀窍就在于singletonFactories这个三级cache里就是解决循环依赖的关键,这段代码发生在createBeanInstance之后,也就是说单例对象此时已经被创建出来(调用了构造器)。这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/363234.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

掌握MySQL分库分表(五)SpringBoot2+MybatisPlus整合Sharding-Jdbc水平分表实现

文章目录创建Java-Maven项目创建数据库、表创建Java实体类配置启动类水平分表配置文件配置测试分库分表实现分析控制台SQL逻辑SQL真实SQL主键重复问题创建Java-Maven项目 添加依赖 <properties><java.version>11</java.version><maven.compiler.source&…

PMP考试有没有什么技巧可以介绍一下么?

一、试题形式 ——中英文对照 即每道题都是一遍英文&#xff0c;一遍翻译的中文&#xff0c;在审题的时候有一些小的技巧需要注意。首先如果你的英文水平足够好&#xff0c;建议直接阅读原文。PMP试题毕竟是美国人出的&#xff0c;语言的组织、思想的表达&#xff0c;肯定更符…

python居然能语音控制电脑壁纸切换,只需60行代码

前言 嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! 家在日常的电脑使用中&#xff0c;都会有自己喜爱类型的桌面 单纯的桌面有时候会让人觉得单调 今天&#xff0c;就由我带领大家只用60行代码打造一款语音壁纸切换器程序&#xff0c; 让大家能够通过语音的方式来控制电脑去…

c++模板的简单认识

文章目录 前言一.泛型编程 函数模板 模板参数的匹配原则 类模板总结前言 ADD函数很好写&#xff0c;但是如果我们要有int类型的&#xff0c;double类型的&#xff0c;char类型的等等各种类型&#xff0c;难道要写这么多不同的ADD函数吗&#xff0c;这么写简直太麻…

Linux:基于bufferevent epoll tcp服务器代码

基于bufferevent epoll tcp服务器代码: #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <string.h> #include <event2/event.h> #include <event2/buffer…

Excel/Word的一些快捷操作整理

Excel/Word的一些快捷操作整理 1. 给Word文件导入目录 前提&#xff1a;大致内容已经定稿&#xff0c;文章中各标题分级明确&#xff0c;最好各级标题字体大小设置不一样。 步骤&#xff1a;在头部导航栏&#xff0c;选择**“引用”–“目录”**&#xff0c;根据自己需求选择其…

KDJB1200六相继电保护测试仪

一、概述 KDJB1200继电保护测试仪是在参照电力部颁发的《微机型继电保护试验装置技术条件(讨论稿)》的基础上&#xff0c;广泛听取用户意见&#xff0c;总结目前国内同类产品优缺点&#xff0c;充分使用现代新的的微电子技术和器件实现的一种新型小型化微机继电保护测试仪。可…

DataWhale 大数据处理技术组队学习task3

四、分布式数据库HBase 1. 产生背景 1.1 Hadoop的局限性 优点&#xff1a;存储结构化、半结构甚至非结构化的数据&#xff0c;是传统数据库的补充&#xff0c;是海量数据存储的最佳方法。缺陷&#xff1a; 只能进行批处理&#xff0c;并且只能以顺序的方式访问数据。&#x…

【C++】-- 异常

目录 C语言传统的处理错误的方式 C异常概念 异常的使用 异常的抛出和捕获 自定义异常体系 异常的重新抛出 异常安全 异常规范&#xff08;C期望&#xff09; C标准库的异常体系 异常的优缺点 C异常的优点 C异常的缺点 总结 C语言传统的处理错误的方式 传统的错误…

zookeeper单机部署

一.下载zookeeper压缩包 二.上传解压安装包到/data/zookeeper目录&#xff0c;并解压 tar -zxvf apache-zookeeper-3.5.8-bin.tar.gz 三.修改配置文件 cd apache-zookeeper-3.5.10-bin/conf mv zoo_sample.cfg zoo.cfg vi zoo.cfg 修改为如下&#xff1a; dataDir/data/zooke…

【研究空间复用及函数调用问题】

本篇总结函数调用过程会存在的一些奇怪现象&#xff0c;空间复用问题&#xff0c;其实本质上涉及函数调用的底层原理&#xff0c;理解函数栈帧的创建和销毁这样的问题直接迎刃而解。1.空间复用问题案例1案例22.函数调用过程不清晰问题案例33.总结1.空间复用问题 案例1 我们先…

程职场人必备微软出品的实用小工具

系统增强工具PowerToys 下载地址&#xff1a;https://github.com/microsoft/PowerToys 什么是 Windows 系统中&#xff0c;最好的辅助工具&#xff1f; PowerToys 一定可以获得提名。PowerToys 是一款来自微软的系统增强工具&#xff0c;就像是一个神奇的系统外挂&#xff0c;…

精确控制 AI 图像生成的破冰方案,ControlNet 和 T2I-Adapter

ControlNet 和 T2I-Adapter 的突破性在哪里&#xff1f;有什么区别&#xff1f;其它为 T2I 扩散模型施加条件引导的相关研究ControlNet 和 T2I-Adapter 的实际应用效果如何&#xff1f;使用体验上&#xff0c;跟 SD原生支持的 img2img 有什么区别&#xff1f;ControlNet 在插画…

电商数据采集——2022年中国手机行业数据浅析

据国家统计局数据显示&#xff0c;2022年12月&#xff0c;国内手机产量当期值为14310.3万台&#xff0c;同比下降18.4%&#xff1b;累计值为156080万台&#xff0c;同比下降6.2%。 据中国信通院数据显示&#xff0c;2022年12月&#xff0c;国内市场手机出货量2786.0万部&#x…

F.pad() 函数

F.pad() 对tensor 进行扩充的函数。 torch.nn.functional.pad (input, pad, mode‘constant’, value0) input&#xff1a;需要扩充的 tensor&#xff0c;可以是图像数据&#xff0c;亦或是特征矩阵数据&#xff1b;pad&#xff1a;扩充维度&#xff0c;预先定义某维度上的扩充…

内存泄露定位手段(c语言hook malloc相关方式)

如何确定有内存泄露问题&#xff0c;如何定位到内存泄露位置&#xff0c;如何写一个内存泄漏检测工具&#xff1f; 1&#xff1a;概述 内存泄露本质&#xff1a;其实就是申请调用malloc/new&#xff0c;但是释放调用free/delete有遗漏&#xff0c;或者重复释放的问题。 内存…

搞懂事件——C# 的event的机制深度理解

📢欢迎点赞 :👍 收藏 ⭐留言 📝 如有错误敬请指正,赐人玫瑰,手留余香!📢本文作者:由webmote 原创📢作者格言:无尽的折腾后,终于又回到了起点,工控,我来了 !1. 前言 为什么忽然对Event感兴趣了? 因为进入Web时代以后,很少使用它了,忽然想起这个知识点,…

STM32 CAN波特率计算

STM32 CAN波特率计算简介CAN总线收发&#xff0c;中断方式接收配置代码部分reference简介 CAN通信帧共分为数据帧、远程帧、错误帧、过载帧和帧间隔&#xff0c;本文这里以数据帧为例。 显性电平对应逻辑0&#xff0c;CAN_H和CAN_L之差为2.5V左右。而隐性电平对应逻辑1&#x…

元宇宙对营销方式的影响

营销方式的变化通常伴随着技术的发展。我们已经看到营销方式从印刷媒体、电视、广播到互联网的转变。而现在&#xff0c;我们又处在下一个营销方式大跃进的风口浪尖上。 关于元宇宙及其潜在的变革性影响&#xff0c;人们已经讨论了很多。虽然与元宇宙相关的大多数东西在很大程…

使用 husky 进行基础代码审查

在日常提交 PR 的过程中&#xff0c;我们提交的文件不应该有例如 console、debugger、test.only 等调试语句&#xff0c;这会影响到线上代码。那每次提交之前都检查似乎又像是一个繁琐的工作&#xff0c;如果有个工作能代替我们检查我们提交的代码&#xff0c;让不能提交到线上…