【源码解析】Nacos配置中心的源码解析

news2024/11/13 15:29:18

POM文件添加依赖

<dependency>
	<groupId>com.alibaba.cloud</groupId>
	<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

在依赖中查看自动装配文件spring.factories

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.alibaba.cloud.nacos.NacosConfigBootstrapConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.cloud.nacos.NacosConfigAutoConfiguration,\
com.alibaba.cloud.nacos.endpoint.NacosConfigEndpointAutoConfiguration
org.springframework.boot.diagnostics.FailureAnalyzer=\
com.alibaba.cloud.nacos.diagnostics.analyzer.NacosConnectionFailureAnalyzer
org.springframework.boot.env.PropertySourceLoader=\
com.alibaba.cloud.nacos.parser.NacosJsonPropertySourceLoader,\
com.alibaba.cloud.nacos.parser.NacosXmlPropertySourceLoader

在这里插入图片描述

核心类

  • NacosConfigBootstrapConfiguration
    启动时拉取配置的原理。
  • NacosConfigAutoConfiguration
    更改配置时实现动态刷新的原理。

NacosConfigBootstrapConfiguration

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.nacos.config.enabled", matchIfMissing = true)
public class NacosConfigBootstrapConfiguration {

   @Bean
   @ConditionalOnMissingBean
   public NacosConfigProperties nacosConfigProperties() {
      return new NacosConfigProperties();
   }

   @Bean
   @ConditionalOnMissingBean
   public NacosConfigManager nacosConfigManager(
         NacosConfigProperties nacosConfigProperties) {
      return new NacosConfigManager(nacosConfigProperties);
   }

   @Bean
   public NacosPropertySourceLocator nacosPropertySourceLocator(
         NacosConfigManager nacosConfigManager) {
      return new NacosPropertySourceLocator(nacosConfigManager);
   }

}

NacosPropertySourceLocator用来实现Nacos配置文件加载。

NacosConfigManager

它的构造方法里面会创建ConfigService,实现是由NacosFactory#createConfigService()来完成的。具体就是执行反射创建实例。

    public static ConfigService createConfigService(Properties properties) throws NacosException {
        try {
            Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.config.NacosConfigService");
            Constructor constructor = driverImplClass.getConstructor(Properties.class);
            ConfigService vendorImpl = (ConfigService)constructor.newInstance(properties);
            return vendorImpl;
        } catch (Throwable var4) {
            throw new NacosException(-400, var4);
        }
    }

NacosConfigService

实例构造方法中,会初始化agent,worker。agent对象是用来发http请求到nacos服务端的;ClientWorker是用来长轮训nacos服务端来感知配置更新。

public NacosConfigService(Properties properties) throws NacosException {
    ValidatorUtils.checkInitParam(properties);
    String encodeTmp = properties.getProperty("encode");
    if (StringUtils.isBlank(encodeTmp)) {
        this.encode = "UTF-8";
    } else {
        this.encode = encodeTmp.trim();
    }

    this.initNamespace(properties);
    this.agent = new MetricsHttpAgent(new ServerHttpAgent(properties));
    this.agent.start();
    this.worker = new ClientWorker(this.agent, this.configFilterChainManager, properties);
}

ClientWorker

初始化了两个线程池,一个用来定时执行checkConfigInfo方法,用来用来执行长轮询。checkConfigInfo方法中会执行 LongPollingRunable的 run 方法。

    public ClientWorker(final HttpAgent agent, ConfigFilterChainManager configFilterChainManager, Properties properties) {
        this.agent = agent;
        this.configFilterChainManager = configFilterChainManager;
        this.init(properties);
        this.executor = Executors.newScheduledThreadPool(1, new ThreadFactory() {
            public Thread newThread(Runnable r) {
                Thread t = new Thread(r);
                t.setName("com.alibaba.nacos.client.Worker." + agent.getName());
                t.setDaemon(true);
                return t;
            }
        });
        this.executorService = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() {
            public Thread newThread(Runnable r) {
                Thread t = new Thread(r);
                t.setName("com.alibaba.nacos.client.Worker.longPolling." + agent.getName());
                t.setDaemon(true);
                return t;
            }
        });
        this.executor.scheduleWithFixedDelay(new Runnable() {
            public void run() {
                try {
                    ClientWorker.this.checkConfigInfo();
                } catch (Throwable var2) {
                    ClientWorker.LOGGER.error("[" + agent.getName() + "] [sub-check] rotate check error", var2);
                }

            }
        }, 1L, 10L, TimeUnit.MILLISECONDS);
    }

    public void checkConfigInfo() {
        int listenerSize = this.cacheMap.size();
        int longingTaskCount = (int)Math.ceil((double)listenerSize / ParamUtil.getPerTaskConfigSize());
        if ((double)longingTaskCount > this.currentLongingTaskCount) {
            for(int i = (int)this.currentLongingTaskCount; i < longingTaskCount; ++i) {
                this.executorService.execute(new ClientWorker.LongPollingRunnable(i));
            }

            this.currentLongingTaskCount = (double)longingTaskCount;
        }

    }

LongPollingRunnable

  1. 判断是否有更新,/v1/cs/configs/listenerClientWorker#checkUpdateConfigStr
  2. 有更新则远程拉取配置信息,/v1/cs/configsClientWorker#getServerConfig
  3. 有更新就触发回调,cacheData.checkListenerMd5();
        public void run() {
            List<CacheData> cacheDatas = new ArrayList();
            ArrayList inInitializingCacheList = new ArrayList();

            try {
                Iterator var3 = ClientWorker.this.cacheMap.values().iterator();

                while(var3.hasNext()) {
                    CacheData cacheData = (CacheData)var3.next();
                    if (cacheData.getTaskId() == this.taskId) {
                        cacheDatas.add(cacheData);

                        try {
                            ClientWorker.this.checkLocalConfig(cacheData);
                            if (cacheData.isUseLocalConfigInfo()) {
                                cacheData.checkListenerMd5();
                            }
                        } catch (Exception var13) {
                            ClientWorker.LOGGER.error("get local config info error", var13);
                        }
                    }
                }

                List<String> changedGroupKeys = ClientWorker.this.checkUpdateDataIds(cacheDatas, inInitializingCacheList);
                if (!CollectionUtils.isEmpty(changedGroupKeys)) {
                    ClientWorker.LOGGER.info("get changedGroupKeys:" + changedGroupKeys);
                }

                Iterator var16 = changedGroupKeys.iterator();

                while(var16.hasNext()) {
                    String groupKey = (String)var16.next();
                    String[] key = GroupKey.parseKey(groupKey);
                    String dataId = key[0];
                    String group = key[1];
                    String tenant = null;
                    if (key.length == 3) {
                        tenant = key[2];
                    }

                    try {
                        String[] ct = ClientWorker.this.getServerConfig(dataId, group, tenant, 3000L);
                        CacheData cache = (CacheData)ClientWorker.this.cacheMap.get(GroupKey.getKeyTenant(dataId, group, tenant));
                        cache.setContent(ct[0]);
                        if (null != ct[1]) {
                            cache.setType(ct[1]);
                        }

                        ClientWorker.LOGGER.info("[{}] [data-received] dataId={}, group={}, tenant={}, md5={}, content={}, type={}", new Object[]{ClientWorker.this.agent.getName(), dataId, group, tenant, cache.getMd5(), ContentUtils.truncateContent(ct[0]), ct[1]});
                    } catch (NacosException var12) {
                        String message = String.format("[%s] [get-update] get changed config exception. dataId=%s, group=%s, tenant=%s", ClientWorker.this.agent.getName(), dataId, group, tenant);
                        ClientWorker.LOGGER.error(message, var12);
                    }
                }

                var16 = cacheDatas.iterator();

                while(true) {
                    CacheData cacheDatax;
                    do {
                        if (!var16.hasNext()) {
                            inInitializingCacheList.clear();
                            ClientWorker.this.executorService.execute(this);
                            return;
                        }

                        cacheDatax = (CacheData)var16.next();
                    } while(cacheDatax.isInitializing() && !inInitializingCacheList.contains(GroupKey.getKeyTenant(cacheDatax.dataId, cacheDatax.group, cacheDatax.tenant)));

                    cacheDatax.checkListenerMd5();
                    cacheDatax.setInitializing(false);
                }
            } catch (Throwable var14) {
                ClientWorker.LOGGER.error("longPolling error : ", var14);
                ClientWorker.this.executorService.schedule(this, (long)ClientWorker.this.taskPenaltyTime, TimeUnit.MILLISECONDS);
            }
        }

cacheData.checkListenerMd5();,会触发 listener.receiveConfigInfo(contentTmp);

listener方法定义在NacosContextRefresher#registerNacosListener

	private void registerNacosListener(final String groupKey, final String dataKey) {
		String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey);
		Listener listener = listenerMap.computeIfAbsent(key,
				lst -> new AbstractSharedListener() {
					@Override
					public void innerReceive(String dataId, String group,
							String configInfo) {
						refreshCountIncrement();
						nacosRefreshHistory.addRefreshRecord(dataId, group, configInfo);
						// todo feature: support single refresh for listening
						applicationContext.publishEvent(
								new RefreshEvent(this, null, "Refresh Nacos config"));
						if (log.isDebugEnabled()) {
							log.debug(String.format(
									"Refresh Nacos config group=%s,dataId=%s,configInfo=%s",
									group, dataId, configInfo));
						}
					}
				});
		try {
			configService.addListener(dataKey, groupKey, listener);
		}
		catch (NacosException e) {
			log.warn(String.format(
					"register fail for nacos listener ,dataId=[%s],group=[%s]", dataKey,
					groupKey), e);
		}
	}

NacosPropertySourceLocator

PropertySourceBootstrapConfiguration实现了ApplicationContextInitializer<ConfigurableApplicationContext>,所以在启动的时候会执行重写方法initialize。遍历容器中的PropertySourceLocator加载配置,在Nacos注册中心客户端中,该PropertySourceLocator就是NacosPropertySourceLocator

	public void initialize(ConfigurableApplicationContext applicationContext) {
		List<PropertySource<?>> composite = new ArrayList<>();
		AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
		boolean empty = true;
		ConfigurableEnvironment environment = applicationContext.getEnvironment();
		for (PropertySourceLocator locator : this.propertySourceLocators) {
			Collection<PropertySource<?>> source = locator.locateCollection(environment);
			if (source == null || source.size() == 0) {
				continue;
			}
			List<PropertySource<?>> sourceList = new ArrayList<>();
			for (PropertySource<?> p : source) {
				if (p instanceof EnumerablePropertySource) {
					EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) p;
					sourceList.add(new BootstrapPropertySource<>(enumerable));
				}
				else {
					sourceList.add(new SimpleBootstrapPropertySource(p));
				}
			}
			logger.info("Located property source: " + sourceList);
			composite.addAll(sourceList);
			empty = false;
		}
		if (!empty) {
			MutablePropertySources propertySources = environment.getPropertySources();
			String logConfig = environment.resolvePlaceholders("${logging.config:}");
			LogFile logFile = LogFile.get(environment);
			for (PropertySource<?> p : environment.getPropertySources()) {
				if (p.getName().startsWith(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
					propertySources.remove(p.getName());
				}
			}
			insertPropertySources(propertySources, composite);
			reinitializeLoggingSystem(environment, logConfig, logFile);
			setLogLevels(applicationContext, environment);
			handleIncludedProfiles(environment);
		}
	}

加载Nacos配置中心的配置,NacosPropertySourceLocator#locate。依次加载shared配置,ext配置,application配置。

@Override
public PropertySource<?> locate(Environment env) {
   nacosConfigProperties.setEnvironment(env);
   ConfigService configService = nacosConfigManager.getConfigService();

   if (null == configService) {
      log.warn("no instance of config service found, can't load config from nacos");
      return null;
   }
   long timeout = nacosConfigProperties.getTimeout();
   nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService,
         timeout);
   String name = nacosConfigProperties.getName();

   String dataIdPrefix = nacosConfigProperties.getPrefix();
   if (StringUtils.isEmpty(dataIdPrefix)) {
      dataIdPrefix = name;
   }

   if (StringUtils.isEmpty(dataIdPrefix)) {
      dataIdPrefix = env.getProperty("spring.application.name");
   }

   CompositePropertySource composite = new CompositePropertySource(
         NACOS_PROPERTY_SOURCE_NAME);

   loadSharedConfiguration(composite);
   loadExtConfiguration(composite);
   loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
   return composite;
}

加载application配置,NacosPropertySourceLocator#loadApplicationConfiguration。会先加载无后缀名的配置,再加载有后缀名的配置,有后缀名的配置的优先级更高。

private void loadApplicationConfiguration(
      CompositePropertySource compositePropertySource, String dataIdPrefix,
      NacosConfigProperties properties, Environment environment) {
   String fileExtension = properties.getFileExtension();
   String nacosGroup = properties.getGroup();
   // load directly once by default
   loadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup,
         fileExtension, true);
   // load with suffix, which have a higher priority than the default
   loadNacosDataIfPresent(compositePropertySource,
         dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);
   // Loaded with profile, which have a higher priority than the suffix
   for (String profile : environment.getActiveProfiles()) {
      String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;
      loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup,
            fileExtension, true);
   }

}

NacosPropertySourceLocator#loadNacosDataIfPresent

private void loadNacosDataIfPresent(final CompositePropertySource composite,
      final String dataId, final String group, String fileExtension,
      boolean isRefreshable) {
   if (null == dataId || dataId.trim().length() < 1) {
      return;
   }
   if (null == group || group.trim().length() < 1) {
      return;
   }
   NacosPropertySource propertySource = this.loadNacosPropertySource(dataId, group,
         fileExtension, isRefreshable);
   this.addFirstPropertySource(composite, propertySource, false);
}

private NacosPropertySource loadNacosPropertySource(final String dataId,
      final String group, String fileExtension, boolean isRefreshable) {
   if (NacosContextRefresher.getRefreshCount() != 0) {
      if (!isRefreshable) {
         return NacosPropertySourceRepository.getNacosPropertySource(dataId,
               group);
      }
   }
   return nacosPropertySourceBuilder.build(dataId, group, fileExtension,
         isRefreshable);
}

NacosPropertySourceBuilder#build,会调用到configService.getConfig(dataId, group, timeout)

NacosPropertySource build(String dataId, String group, String fileExtension,
      boolean isRefreshable) {
   List<PropertySource<?>> propertySources = loadNacosData(dataId, group,
         fileExtension);
   NacosPropertySource nacosPropertySource = new NacosPropertySource(propertySources,
         group, dataId, new Date(), isRefreshable);
   NacosPropertySourceRepository.collectNacosPropertySource(nacosPropertySource);
   return nacosPropertySource;
}

private List<PropertySource<?>> loadNacosData(String dataId, String group,
      String fileExtension) {
   String data = null;
   try {
      data = configService.getConfig(dataId, group, timeout);
      if (StringUtils.isEmpty(data)) {
         log.warn(
               "Ignore the empty nacos configuration and get it based on dataId[{}] & group[{}]",
               dataId, group);
         return Collections.emptyList();
      }
      if (log.isDebugEnabled()) {
         log.debug(String.format(
               "Loading nacos data, dataId: '%s', group: '%s', data: %s", dataId,
               group, data));
      }
      return NacosDataParserHandler.getInstance().parseNacosData(dataId, data,
            fileExtension);
   }
   catch (NacosException e) {
      log.error("get data from Nacos error,dataId:{} ", dataId, e);
   }
   catch (Exception e) {
      log.error("parse data from Nacos error,dataId:{},data:{}", dataId, data, e);
   }
   return Collections.emptyList();
}

NacosConfigService#getConfigInner核心主要是:LocalConfigInfoProcessor.getFailover(this.agent.getName(), dataId, group, tenant);,通过LocalConfigInfoProcessor 加载本地缓存,如果存在就把内容读取出来直接返回,就不走http请求了;this.worker.getServerConfig(dataId, group, tenant, timeoutMs);,通过worker.getServerConfig加载远程变量

private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException {
    group = this.null2defaultGroup(group);
    ParamUtils.checkKeyParam(dataId, group);
    ConfigResponse cr = new ConfigResponse();
    cr.setDataId(dataId);
    cr.setTenant(tenant);
    cr.setGroup(group);
    String content = LocalConfigInfoProcessor.getFailover(this.agent.getName(), dataId, group, tenant);
    if (content != null) {
        LOGGER.warn("[{}] [get-config] get failover ok, dataId={}, group={}, tenant={}, config={}", new Object[]{this.agent.getName(), dataId, group, tenant, ContentUtils.truncateContent(content)});
        cr.setContent(content);
        this.configFilterChainManager.doFilter((IConfigRequest)null, cr);
        content = cr.getContent();
        return content;
    } else {
        try {
            String[] ct = this.worker.getServerConfig(dataId, group, tenant, timeoutMs);
            cr.setContent(ct[0]);
            this.configFilterChainManager.doFilter((IConfigRequest)null, cr);
            content = cr.getContent();
            return content;
        } catch (NacosException var9) {
            if (403 == var9.getErrCode()) {
                throw var9;
            } else {
                LOGGER.warn("[{}] [get-config] get from server error, dataId={}, group={}, tenant={}, msg={}", new Object[]{this.agent.getName(), dataId, group, tenant, var9.toString()});
                LOGGER.warn("[{}] [get-config] get snapshot ok, dataId={}, group={}, tenant={}, config={}", new Object[]{this.agent.getName(), dataId, group, tenant, ContentUtils.truncateContent(content)});
                content = LocalConfigInfoProcessor.getSnapshot(this.agent.getName(), dataId, group, tenant);
                cr.setContent(content);
                this.configFilterChainManager.doFilter((IConfigRequest)null, cr);
                content = cr.getContent();
                return content;
            }
        }
    }
}

总结

  1. 启动的时候,执行NacosPropertySourceLocator.locate来获取配置
  2. 启动的时候,会开启定时任务,发起长轮询请求监听配置,一旦配置变化,触发获取配置的请求,执行监听器

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

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

相关文章

String类详解

在Java编程中&#xff0c;除Object类外&#xff0c;最常用的类就是String类了。本文将从String类源码出发&#xff0c;对String类进行一个全面的分析&#xff0c;以帮忙我们更好的理解和使用String类。 String类概述 Java 使用 String 类代表字符串。Java 中的所有字符串字面…

已解决MemoryError

已解决Python读取20GB超大文件报错&#xff1a;MemoryError 文章目录报错问题报错翻译报错原因解决方法1解决方法2&#xff08;推荐使用&#xff09;帮忙解决报错问题 日常数据分析工作中&#xff0c;难免碰到数据量特别大的情况&#xff0c;动不动就2、3千万行&#xff0c;…

Linux——网络配置篇

1、前情提要&#xff1a; 今晚在配置Linux &#xff08;CentOS7完整版&#xff09;的时候 明明已经配好了网络环境&#xff0c;重启虚拟机后&#xff0c;又出现了Ping不通 主机、Ping不通网关&#xff0c;外网的情况 &#xff08;NAT&#xff09;。 让我很费解的一个情况是&am…

数据结构与算法基础(王卓)(8):线性表的应用

PPT&#xff1a;第二章P173&#xff1b; 并集集合&#xff1a;线性表的合并&#xff08;无需有序&#xff0c;不能重复&#xff09; 线性表&#xff1a; Status Union(Sqlist& A, Sqlist& B)//并集 {int len_A A.length;int len_B B.length;for (int i 1; i < …

SpringCloud学习(1)

SpringCloud学习 软件架构演进之路 对于单体架构&#xff0c;我们根据设计期和开发实现期的不同模式和划分结构&#xff0c;可以分为&#xff1a; 简单单体模式&#xff1a; 代码层面没有拆分&#xff0c;所有的业务逻辑都在一个项目&#xff08;Project&#xff09;里打包…

​力扣解法汇总2293. 极大极小游戏

目录链接&#xff1a; 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目&#xff1a; https://github.com/September26/java-algorithms 原题链接&#xff1a; 力扣 描述&#xff1a; 给你一个下标从 0 开始的整数数组 nums &#xff0c;其长度是 2 的幂。 对 n…

伪随机码序列

伪随机码又称伪随机序列&#xff0c;是用确定性方法产生的在一段周期内具有类似白噪声的随机特性的二&#xff08;或多&#xff09;进制数据序列。 伪随机序列在码分复用、码分多址和扩频通信中都有重要应用。常用的伪随机序列有m序列、M序列和Gold序列。 作为地址码和扩频码…

pycharm中commit/push撤销+分支合并

一、现在想撤销到打印3怎么操作&#xff1f; 1复制修订号 2鼠标右键 3去项目下执行git bash here&#xff0c;再输入git push -f 4此时&#xff0c;github中的push就已经回退成功&#xff1b;再进行一次新代码的commit和push&#xff0c;master分支的颜色就变成黄色了&#x…

Windows中安装 MySQL8.0.30 数据库

下载安装文件 访问MySQL官网下载安装文件https://downloads.mysql.com/archives/community/。 如下图所示&#xff0c;点击页面中的“DOWNLOAD”按钮。 然后&#xff0c;会出现如下所示页面&#xff0c;点击页面底部的“No thanks, just start my download”&#xff0c;就可…

Vue3商店后台管理系统设计文稿篇(四)

记录使用vscode构建Vue3商店后台管理系统&#xff0c;这是第四篇&#xff0c;主要记录使用git与vscode将代码同步提交到GitHub上面 文章目录一、Git与Github建立连接二、配置开发工具三、实用Git命令仓库相关命令分支相关命令正文内容&#xff1a; 一、Git与Github建立连接 使…

从0到1【建站:AWS+Ubuntu+Python+Django+uwsgi+nginx+ssl】

目录一、创建服务器1、进入AWS官网2、启动实例3、创建新密钥对4、选择密钥对5、网络设置6、配置存储7、启动实例8、查看实例9、配置安全组二、连接服务器1、在AWS官网进行连接2、使用Xshell7进行连接3、设置允许root登录三、域名解析1、进行腾讯云官网2、管理域名解析3、绑定公…

xilinx ZYNQ 7000 XADC 片上模拟转数字模块

上图所示&#xff0c;XADC 属于 PL部分的资源 XADC是一种硬逻辑实现&#xff0c;位于PL功率域。PS- xadc接口是PS的一部分&#xff0c;可以被PS APU访问&#xff0c;而不需要对PL进行编程。PL必须上电才能配置PS-XADC接口、使用PL- jtag或DRP接口以及操作XADC。 上面的机构图能…

通关算法题之 ⌈回溯算法⌋

回溯算法 子集组合排列 78. 子集 给你一个整数数组 nums&#xff0c;数组中的元素互不相同 &#xff0c;返回该数组所有可能的子集&#xff08;幂集&#xff09;。解集不能包含重复的子集&#xff0c;你可以按任意顺序 返回解集。 输入&#xff1a;nums [1,2,3] 输出&…

LeetCode 5. 最长回文子串

&#x1f308;&#x1f308;&#x1f604;&#x1f604; 欢迎来到茶色岛独家岛屿&#xff0c;本期将为大家揭晓LeetCode 5. 最长回文子串&#xff0c;做好准备了么&#xff0c;那么开始吧。 &#x1f332;&#x1f332;&#x1f434;&#x1f434; 一、题目名称 LeetCode 5…

1590_AURIX_TC275_PMU_Flash的操作

全部学习汇总&#xff1a; GreyZhang/g_TC275: happy hacking for TC275! (github.com) 这里提到的这个复位读取操作&#xff0c;复位的对象其实是当前的操作&#xff0c;也就是命令序列。主要是命令序列的最后一个命令还没有被接收到&#xff0c;都可以被这个命令中断。在复位…

目标检测:特征金字塔网络(Feature Pyramid Network)

目标检测&#xff1a;特征金字塔网络&#xff08;Feature Pyramid Network&#xff09;概述核心思想概述 由于在目标检测任务中&#xff0c;对与大目标的检测&#xff0c;需要feature map每个点的感受野大一点&#xff08;高层语义特征&#xff09;&#xff0c;对于小目标&…

GPIO 八种工作模式及其硬件框图

参考资料: STM32F1xx 官方资料:《STM32中文参考手册V10》- 第 8 章通用和复用功能 IO(GPIO 和 AFIO) GPIO 是通用输入/输出端口的简称,是 STM32 可控制的引脚。GPIO 的引脚与外部硬件设备连接,可实现与外部通讯、控制外部硬件或者采集外部硬件数据的功能。 STM32 的八种 …

图像融合笔记(一):RFN-Nest

RFN-NestAbstractIntroductionRelated worksThe proposed fusion frameworkThe architecture of the fusion networkTwo-stage training strategy论文&#xff1a; RFN-Nest: An end-to-end residual fusion network for infrared and visible images代码&#xff1a; https://…

[Android Input系统]MotionEvent的序列化传送

这里从云游戏的触控操作看起&#xff0c;PC端的客户端支持按键和鼠标滑动操作&#xff0c;手机上的云游戏客户端则是和手机游戏一样的touch触控&#xff0c;客户端的touch操作是怎样处理给服务端的呢&#xff0c;猜测是把touch操作“实时”的传送给了服务器&#xff0c;Android…

COM,Component Object Model 简介

COM&#xff0c;Component Object Model 简介 1. COM 是什么 COM 的英文全称是&#xff0c;Component Object Model&#xff0c;中文译为&#xff0c;组件对象模型。它官方的概念是&#xff1a; The Microsoft Component Object Model (COM) is a platform-independent, dis…