【BlossomConfig】SpringBoot如何实现配置的管控?

文章目录

  • ConfigurableEnvironment
  • 事件监听完成配置变更
  • 使用Scope来管控Bean的生命周期
  • 什么是配置中心?以及如何实现一个配置中心?
  • SpringBoot如何实现配置的管控?
  • SpringCloud项目是如何对bootstrap配置文件进行加载的?
  • Nacos是如何实现配置文件的读取加载的?
  • 开发配置中心前必须了解的前置知识
  • 配置中心Server和Client端代码的编写
  • 配置中心Core核心功能代码的编写
  • 配置中心源码优化---本地缓存与读写锁

网关项目源码
RPC项目源码
配置中心项目源码

ConfigurableEnvironment

ConfigurableEnvironment 是 Spring Framework 中的一个接口,它继承自 Environment 接口。这个接口的主要职责是提供对环境配置的可编程接口,允许在应用运行期间动态调整环境和配置属性。
主要功能

  1. 属性源管理:ConfigurableEnvironment 允许你添加、移除或重新排序属性源(PropertySources)。属性源是配置数据的来源,比如properties文件、环境变量、命令行参数等。
  2. 激活和禁用配置文件:支持基于不同的环境激活或禁用特定的配置文件(如 application-dev.yml, application-prod.yml 等)。
  3. 属性解析:提供了解析属性值的方法,支持占位符解析(例如 ${property.name})和类型转换。
  4. 配置覆盖:能够覆盖现有的属性源中的配置,实现动态配置更新。

基于上面的点,我们知道,在开发我们自己的配置中心时,ConfigurableEnvironment 可以提供以下帮助:

  1. 动态配置更新:通过修改 ConfigurableEnvironment 中的属性源,可以实现配置的动态加载和更新,无需重启应用。
  2. 环境适配:可根据不同的环境(开发、测试、生产)动态调整配置,支持多环境管理。
  3. 配置的灵活管理:可以灵活地添加或移除配置来源,如将配置中心作为一个新的属性源集成到应用中。
  4. 统一配置接入点:通过 ConfigurableEnvironment,可以将来自不同来源的配置统一管理和访问,提高配置管理的一致性和可维护性。
    在自研配置中心的集成中,可以通过实现一个 EnvironmentPostProcessor,在应用启动阶段动态添加自定义的属性源:
public class CustomConfigCenterPostProcessor implements EnvironmentPostProcessor {@Overridepublic void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {// 添加自定义的属性源到环境中Map<String, Object> customConfig = loadConfigFromCustomConfigCenter();environment.getPropertySources().addLast(new MapPropertySource("customConfig", customConfig));}private Map<String, Object> loadConfigFromCustomConfigCenter() {// 加载配置中心的配置// ...}
}package blossom.project.config.core;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.MapPropertySource;
import org.springframework.stereotype.Component;import java.util.HashMap;
import java.util.Map;@Component
public class ConfigurableEnvironmentTest
{@Autowiredprivate ConfigurableEnvironment environment;// 添加或更新配置项public void addOrUpdateProperty(String key, String value) {MutablePropertySources propertySources = environment.getPropertySources();Map<String, Object> properties = new HashMap<>();properties.put(key, value);PropertySource<?> propertySource = new MapPropertySource("customPropertySource", properties);if (propertySources.contains("customPropertySource")) {// 更新现有的属性源((MapPropertySource) propertySources.get("customPropertySource")).getSource().putAll(properties);} else {// 添加新的属性源propertySources.addLast(propertySource);}}// 获取配置项public String getProperty(String key) {return environment.getProperty(key);}// 删除配置项public void removeProperty(String key) {MutablePropertySources propertySources = environment.getPropertySources();if (propertySources.contains("customPropertySource")) {MapPropertySource propertySource = (MapPropertySource) propertySources.get("customPropertySource");propertySource.getSource().remove(key);}}// 列出所有配置项public Map<String, Object> listProperties() {Map<String, Object> properties = new HashMap<>();environment.getPropertySources().forEach(propertySource -> {if (propertySource instanceof MapPropertySource) {properties.putAll(((MapPropertySource) propertySource).getSource());}});return properties;}
}

在这个例子中,loadConfigFromCustomConfigCenter 方法负责从自研配置中心加载配置,然后将其作为一个新的属性源添加到环境中。这样,应用就可以像访问其他任何属性源一样访问这些配置了。
因此,通过上面的例子,我们就已经知道了如何在项目运行过程中实现配置文件的增删改查功能了。
接下来我们需要解决第二个问题,如何动态修改@Value以及@ConfigurationProperties等注解修饰的属性的对应的值?
这里首先了解一个前提:
对于 @Value 注解,我们可能需要手动刷新相关的Bean,因为 @Value 注解通常在Bean初始化时解析一次,之后不会自动更新。这可以通过实现自定义的更新逻辑来完成。
对于 @ConfigurationProperties 注解,如果配置的Bean是一个 @Component,并且配置项是在运行时可变的(如使用了 @RefreshScope),那么当环境中的属性更新后,这些配置项将被自动更新。
所以,我们重点需要解决的就是@Value注解的解析问题。

事件监听完成配置变更

这里我想到的思路是:结合 Environment 的动态更新特性和 Spring 的事件监听机制。
基本的思路是当配置发生变化时,触发一个事件,然后处理这个事件来更新相关的属性。这通常包括以下几个步骤:

  1. 监听配置变更事件:当配置源(例如您的配置中心)中的配置项发生变化时,需要有机制触发一个事件。
  2. 处理事件:当事件被触发时,获取变更的配置项,并更新 Environment 中相应的配置。
  3. 通知属性更新:在配置更新后,通知所有依赖这些配置的Bean,使它们重新加载新的配置值。
    大概的实现代码如下:
  4. 首先编写一个事件,当前事件用来存储配置变更时候的信息。
@Component
public class ConfigChangeEvent extends ApplicationEvent {private final Map<String, Object> changedProperties;/*** 构造配置更改事件。** @param source 事件源* @param changedProperties 包含变更的配置项及其值的映射*/public ConfigChangeEvent(Object source, Map<String, Object> changedProperties) {super(source);this.changedProperties = Collections.unmodifiableMap(changedProperties);}/*** 获取发生变更的配置项及其值。** @return 变更的配置项映射*/public Map<String, Object> getChangedProperties() {return changedProperties;}
}
  1. 之后我们需要编写一个事件监听器,来监听配置变更的事件。

并且在这个监听器中,我们需要解析@Value注解,同时,扫描我们的Spring容器,得到所有存在有@Value注解的Field字段,然后判断这次变更事件中的心配置信息,是否存在这些字段的@Value注解对应的属性值,如果存在,我们就进行更新。

@Component
public class ConfigChangeListener implements ApplicationListener<ConfigChangeEvent> {private final ConfigurableApplicationContext applicationContext;public ConfigChangeListener(ConfigurableApplicationContext applicationContext) {this.applicationContext = applicationContext;}@Overridepublic void onApplicationEvent(ConfigChangeEvent event) {// 获取发生变更的配置项Map<String, Object> changedProperties = event.getChangedProperties();// 更新应用上下文中所有Bean的@Value注解字段updateValueAnnotatedFields(changedProperties);}private void updateValueAnnotatedFields(Map<String, Object> changedProperties) {ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();String[] beanNames = beanFactory.getBeanDefinitionNames();for (String beanName : beanNames) {Object bean = beanFactory.getBean(beanName);Class<?> targetClass = bean.getClass();// 遍历所有字段,查找@Value注解ReflectionUtils.doWithFields(targetClass, field -> {Value valueAnnotation = field.getAnnotation(Value.class);if (valueAnnotation != null) {String key = extractKeyFromValueAnnotation(valueAnnotation);if (changedProperties.containsKey(key)) {field.setAccessible(true);Object newValue = changedProperties.get(key);ReflectionUtils.setField(field, bean, newValue);}}});}}private String extractKeyFromValueAnnotation(Value valueAnnotation) {// 提取和解析@Value注解的值,可能需要解析Spring表达式String value = valueAnnotation.value();// TODO: 实现对Spring表达式的解析,这里只处理简单情况return value.startsWith("${") && value.endsWith("}") ?value.substring(2, value.length() - 1) : value;}
}
  1. 最后,我们编写一个发布事件的生产者
@Component
public class ConfigChangePublisher {private final ApplicationEventPublisher eventPublisher;public ConfigChangePublisher(ApplicationEventPublisher eventPublisher) {this.eventPublisher = eventPublisher;}public void publishConfigChange() {Map<String, Object> changedProperties = new HashMap<>();changedProperties.put("test.property", "updatedValue");ConfigChangeEvent event = new ConfigChangeEvent(this, changedProperties);eventPublisher.publishEvent(event);}
}
  1. 测试一下代码可用性
@SpringBootTest
public class ConfigChangeTest {@Autowiredprivate TestConfig testConfig;@Autowiredprivate ConfigChangePublisher configChangePublisher;@Testpublic void testConfigChange() {assertEquals("default", testConfig.getTestProperty());configChangePublisher.publishConfigChange();// 这里可能需要一些时间来等待配置更新// 可能需要使用 Thread.sleep 或其他机制来等待assertEquals("updatedValue", testConfig.getTestProperty());}
}

我们对上面的代码进行debug运行,发现是可以通过的。
因此,我们就简单的完成了配置中心对配置变更时候的处理。

使用Scope来管控Bean的生命周期

当然,上面的方法其实就是通过遍历存在有@Value注解的属性,然后重新进行属性值的设置,这种方式用到了反射,因此在安全性和性能上有略有问题。
因此,我又继续思考了第二种解决方案。
从上面我们知道,@Value注解不能动态修改值的原因是因为当类初始化的时候,这个值就已经被设置上去了,所以我们运行时修改配置值,是不能修改@Value对应的值的。
那么解决思路也很简单,就是我们能不能重新初始化一下这个类?
也就是我们先将当前类销毁,然后重新创建一个当前的类就好了。
同时,为了保证不“误伤”其他的类,我们肯定得有一个机制来保证只刷新指定的类。
还记得SpringCloud中提供的@RefreshScope注解嘛?
当配置更改时,标有 @RefreshScope 的bean会被Spring Cloud Context特殊处理,这些bean在下一次访问时会被重新初始化,从而获取最新的配置值。
我们要做的,其实就是管理Bean的生命周期,使得我们可以控制bean的创建和销毁规则。
而如何实现上面的功能呢?
在Spring框架中,自定义作用域(Scope)允许你控制Bean的生命周期,即定义Bean的创建、存在和销毁的规则。Spring默认提供了几个内置作用域,如单例(singleton)、原型(prototype)、请求(request)、会话(session)和应用(application)作用域。但在某些特定场景下,这些默认作用域可能不足以满足需求,这时你就可以通过实现自定义作用域来扩展Spring的功能。
因此,我们首先需要实现一下Scope接口,首先介绍一下该接口内部的几个方法的含义。

  1. Object get(String name, ObjectFactory<?> objectFactory) ● 作用:这是最关键的方法。当请求作用域内的Bean时,Spring容器会调用此方法。 ● 参数解释: ○ String name:Bean的名称。 ○ ObjectFactory<?> objectFactory:如果请求的Bean在作用域内不存在,则使用这个工厂来创建新的Bean实例。
    ● 行为:通常,此方法会检查是否已经为当前作用域创建了具有指定名称的Bean。如果是,返回现有的Bean;如果不是,使用objectFactory来创建新的Bean,并将其存储在作用域内以供后续使用。
  2. Object remove(String name)
    ● 作用:从当前作用域中移除具有指定名称的Bean。
    ● 行为:此方法通常在Bean不再需要时调用,例如,当作用域结束时。移除操作通常涉及到清理资源和执行任何必要的销毁回调。
  3. void registerDestructionCallback(String name, Runnable callback)
    ● 作用:为作用域内的Bean注册一个销毁回调。
    ● 参数解释:
    ○ String name:Bean的名称。
    ○ Runnable callback:当Bean被销毁时需要执行的回调。
    ● 行为:在Bean不再需要时,这些注册的回调将被调用。这是用于资源清理和其他销毁逻辑的地方。
  4. Object resolveContextualObject(String key)
    ● 作用:解析作用域相关的对象。这是一个高级功能,通常用于解析与当前作用域特定相关的对象。
    ● 行为:例如,如果你的作用域与Web请求相关,此方法可能用于解析与当前HTTP请求相关的信息。
  5. String getConversationId()
    ● 作用:获取当前作用域的会话ID(如果有的话)。
    ● 行为:这主要用于支持会话或对话类型的作用域,例如,在Web应用程序中跟踪用户会话。

这个Scope在开发中,我最经常用在多租户的开发。
假设我们创建了一个自定义作用域,比如“租户作用域”(tenant scope),用于多租户应用中。当一个租户的请求进入应用时,我们可能希望所有的Bean都是针对这个特定租户的。这时,我们的自定义作用域可以确保每个租户只看到自己的Bean实例。当租户的请求结束时,作用域内的Bean可以被清除或重置,为下一个租户请求做准备。
这里我简单的列举一下对Scope的代码实现。

public class MyRefreshScope implements Scope {private final Map<String, Object> scopeMap = new ConcurrentHashMap<>();private final Map<String, Runnable> destructionCallbacks = new ConcurrentHashMap<>();@Overridepublic Object get(String beanName, ObjectFactory<?> objectFactory) {if (scopeMap.containsKey(beanName)){return scopeMap.get(beanName);}//不存在 那么就获取(获取的时候会在内部进行创建)Object object = objectFactory.getObject();scopeMap.put(beanName,object);return object;}@Overridepublic Object remove(String beanName) {return scopeMap.remove(beanName);}
}

此时,我们还需要对Scope进行注册,来保证spring知道这个我们自己实现的scope。
有两种方法,先介绍第一种,这种比较直观。

@Data
@Configuration
public class ScopeRegistry implements BeanDefinitionRegistryPostProcessor {private BeanDefinitionRegistry beanDefinitionRegistry;@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {beanFactory.registerScope("myRefreshScope", new MyRefreshScope());}@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {this.beanDefinitionRegistry = beanDefinitionRegistry;}
}

如下是第二种,使用的是CustomScopeConfigurer。

@Configuration
public class TenantScopeConfig {@Beanpublic static CustomScopeConfigurer customScopeConfigurer() {CustomScopeConfigurer configurer = new CustomScopeConfigurer();Map<String, Object> scopes = new HashMap<>();scopes.put("tenant", new TenantScope());configurer.setScopes(scopes);return configurer;}
}

此时,我们就已经成功的完成了自定义的Scope定义。可以用它来管控我们的Bean了。
在我们的需要被管控的Bean上添加注解@Scope(scopeName = “myRefreshScope”)

@Component
@Scope(scopeName = "myRefreshScope")
public class RebuildClass {@Value("${name1:defaultValue1}")private String name1;@Value("${name2:defaultValue2}")private String name2;private long hashCode = hashCode();@PostConstructpublic void init() {System.out.println("first hashCode:"+hashCode);ScheduledExecutorService service = Executors.newScheduledThreadPool(1);service.schedule(()->{System.out.println(name1);System.out.println(name2);System.out.println("new hashCode:"+hashCode);}, 3,TimeUnit.SECONDS);}
}

此时,只要我们对bean进行销毁和创建,就会执行我们自定义Scope中的逻辑。

private void refreshValue() {String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();for (String beanDefinitionName : beanDefinitionNames) {BeanDefinition beanDefinition = beanDefinitionRegistry.getBeanDefinition(beanDefinitionName);if (REFRESH_SCOPE.equalsIgnoreCase(beanDefinition.getScope())){//如果存在当前scope属性 销毁applicationContext.getBeanFactory().destroyScopedBean(beanDefinitionName);//通过get方法就可以重建applicationContext.getBean(beanDefinitionName);}}
}

到此,我就已经介绍完毕了两种能在运行时动态修改@Value注解对应的值的方式了。
接下来的开发中,我们可以选取其中一种作为我们主要使用的方式。

了解完毕这些之后,还有一个非常重要的知识点我们需要去了解。
就是我们知道,配置中心比如Nacos启动的时候,其实他们的配置信息就已经添加到项目中去了,而上面我们的代码,@Value注解的值还是空的。
所以我们得了解一下spring的生命周期相关的知识,以及配置文件相关的知识,从而使得我们的项目启动的时候,也能优先从配置中心拿到配置之后,就能马上让这些配置生效,从而使得@Value注解能拿到配置中心中设置的值。
我们知道,配置中心的启动依赖bootstrap.yml文件,再项目启动的时候会读取当前配置文件中的配置信息用来加载配置中心的地址。
而配置中心地址加载完毕连接到配置中心之后,会通过http请求的方式/rpc请求/的方式,从配置中心中拿到所有的配置,并且将所有的配置添加到本地。这两步完成之后,我们的功能才完整。
因此,我们就必须了解上面的这些流程是如何实现的。
篇幅有限,对于bootstrap文件的解析,我们留到下一片文章。

什么是配置中心?以及如何实现一个配置中心?

SpringBoot如何实现配置的管控?

SpringCloud项目是如何对bootstrap配置文件进行加载的?

Nacos是如何实现配置文件的读取加载的?

开发配置中心前必须了解的前置知识

配置中心Server和Client端代码的编写

配置中心Core核心功能代码的编写

配置中心源码优化—本地缓存与读写锁

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

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

相关文章

vue快速入门(二)安装vue调试插件

教程很详细&#xff0c;直接上过程 上一篇 新增内容 在国内网站下载谷歌插件安装插件 点击跳转极简插件 此处我们以Chrome浏览器为例 到这里我们就成功安装了插件 使用上一篇博客的代码在浏览器F12调试一下 这样就可以使用了&#xff01;&#xff01;&#xff01;

云原生时代来了,遗留的虚拟机怎么办?红帽 OpenShift 虚拟化实现“稳敏兼得”

作者 | 宋慧 出品 | CSDN 从云原生、容器、微服务&#xff0c;到现在大热的 AIGC、GPU&#xff0c;越来越多的技术与架构正在进入数据中心与基础设施中&#xff0c;以服务数字化转型中的创新业务与应用。在成熟虚拟化传统&#xff08;稳态&#xff09;业务和容器、新&#xff…

vue获取上个月今天

vue获取上个月今天 在Vue中获取上个月的今天可以通过以下步骤实现&#xff1a; 引入moment.js库用于处理日期。 在Vue组件中定义一个方法&#xff0c;使用moment来获取上个月的今天的日期。 以下是实现的示例代码&#xff1a; 首先&#xff0c;确保安装了moment.js&#x…

Vue-05

v-model 应用于其他表单元素 常见的表单元素都可以用v-model绑定关联 → 快速获取或设置表单元素的值 它会根据控件类型自动选取正确的方法来更新元素 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name…

【PFA树脂交换柱】实验室高纯PFA材质过滤柱耐受电子级氢氟酸含氟树脂层析柱

PFA离子交换柱&#xff0c;也叫PFA层析柱、PFA过滤柱等&#xff0c;其原理是利用吸附剂对不同化合物有不同吸附作用和不同化合物在溶剂中的不同溶解度&#xff0c;用适应溶剂使混合物在填有吸附剂的柱内通过&#xff0c;使复杂的混合物达到分离和提纯的目的。 柱体为透明PFA材…

Centos7 elasticsearch-7.7.0 集群搭建,启用x-pack验证 Kibana7.4用户管理

前言 Elasticsearch 是一个分布式、RESTful 风格的搜索和数据分析引擎&#xff0c;能够解决不断涌现出的各种用例。 作为 Elastic Stack 的核心&#xff0c;它集中存储您的数据&#xff0c;帮助您发现意料之中以及意料之外的情况。 环境准备 软件 …

软考 系统架构设计师系列知识点之云原生架构设计理论与实践(14)

接前一篇文章&#xff1a;软考 系统架构设计师系列知识点之云原生架构设计理论与实践&#xff08;13&#xff09; 所属章节&#xff1a; 第14章. 云原生架构设计理论与实践 第3节 云原生架构相关技术 14.3.2 云原生微服务 1. 微服务发展背景 2. 微服务设计约束 相较于单体应…

【Linux】自定义协议+序列化+反序列化

自定义协议序列化反序列化 1.再谈 "协议"2.Cal TCP服务端2.Cal TCP客户端4.Json 喜欢的点赞&#xff0c;收藏&#xff0c;关注一下把&#xff01; 1.再谈 “协议” 协议是一种 “约定”。在前面我们说过父亲和儿子约定打电话的例子&#xff0c;不过这是感性的认识&a…

上海开放大学2024年春《机电一体化系统设计》填空题参考答案

一、填空题 1(10分)机电一体化产品不仅是人的手与肢体的延伸&#xff0c;还是人的()、() 的延伸&#xff0c;具有“智能化”的特征是机电一体化与机械电气化在功能上的本质差别。 参考答案&#xff1a; 填空 一 感官 填空 二 头脑 2(10分)根据不同的使用目的&#xff0c…

在线起诉电信诈骗

原告王某1向本院提出诉讼请求&#xff1a;请求被告返还不当得利款6000元及利息&#xff08;利息按一年期贷款市场报价利率3.85%的标准计算&#xff0c;从2022年10月2日至实际清偿为止&#xff09;。本案在审理过程中&#xff0c;原告王某1变更诉讼请求为&#xff1a;请求被告赔…

Kafka入门到实战-第二弹

Kafka入门到实战 Kafka快速开始官网地址Kafka概述Kafka术语Kafka初体验更新计划 Kafka快速开始 官网地址 声明: 由于操作系统, 版本更新等原因, 文章所列内容不一定100%复现, 还要以官方信息为准 https://kafka.apache.org/Kafka概述 Apache Kafka 是一个开源的分布式事件流…

【SQL Server】2. 将数据导入导出到Excel表格当中

最开始&#xff0c;博主介绍一下自己的环境&#xff1a;SQL Sever 2008 R2 SQL Sever 大致都差不多 1. 通过自带软件的方式 首先找到下载SQL Sever中提供的导入导出工具 如果开始界面没有找到自己下载的路径 C:\Program Files\Microsoft SQL Server\100\DTS\Binn下的DTSWiz…

整理开源资源:零代码开发灵魂——逻辑引擎,收藏吧

逻辑配置是零代码开发的业务核心功能&#xff0c;本质上是实现服务的编排&#xff0c;把原子的服务通过可视化编排&#xff0c;形成最终的业务逻辑。 经过小编的精心整理&#xff0c;把相关的资源全部汇总起来&#xff0c;收藏吧&#xff01; Drawflow 拖动节点多路输入/输出…

增强Java技能:使用OkHttp下载www.dianping.com信息

在这篇技术文章中&#xff0c;我们将探讨如何使用Java和OkHttp库来下载并解析www.dianping.com上的商家信息。我们的目标是获取商家名称、价格、评分和评论&#xff0c;并将这些数据存储到CSV文件中。此外&#xff0c;我们将使用爬虫代理来绕过任何潜在的IP限制&#xff0c;并实…

python3内置持久化模块shelve心得

python3内置持久化模块shelve心得 来自python官方网站的解释&#xff1a; https://docs.python.org/zh-cn/3.10/library/shelve.html 本文环境&#xff1a; Windows 10 专业版 64 位 Thonny 3.2.6 概述 内置模块 shelve 可以将任意 Python 对象&#xff08;即 https://docs…

07. 【Android教程】Android 线性布局 LinearLayout

在上一节中&#xff0c;我们讲到了所有的 Layout 都是从 ViewGroup 继承而来&#xff0c;它可以包含若干 View 并按照指定的规则将这个 View 摆放到屏幕上。那么接下来的章节我们就来学习一下 Android 的 UI 布局&#xff0c;Android 原生有六大布局&#xff0c;分别是: Linear…

基于FPGA的HDMI视频接口设计

HDMI介绍 HDMI(High-DefinitionMultimedia Interface)又被称为高清晰度多媒体接口,是首个支持在单线缆上传输,不经过压缩的全数字高清晰度、多声道音频和智能格式与控制命令数据的数字接口。HDMI接口由Silicon Image美国晶像公司倡导,联合索尼、日立、松下、飞利浦、汤姆逊、东…

LLM面面观之MoE

1. 背景 根据本qiang~最新的趋势观察&#xff0c;基于MoE架构的开源大模型越来越多&#xff0c;比如马斯克的Grok-1(314B), Qwen1.5-MoE-A2.7B等&#xff0c;因此想探究一下MoE里面的部分细节。 此文是本qiang~针对大语言模型的MoE的整理&#xff0c;包括原理、流程及部分源码…

Mybatis——查询数据

查询操作 根据用户id查询单条记录&#xff0c;在映射器接口(UserMapper)中定义如下方法&#xff1a; package org.example.mapper;import org.example.demo.User;import java.util.List;public interface UserMapper {//根据id查询UserUser selectUserById(Integer userId); …

对称二叉树_递归_java

对称二叉树 leetcode链接 问题描述 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 提示&#xff1a; 树中节点数目在范围 [1, 1000] 内 -100 < Node.val < 100 示例 示例 1&#xff1a; 输入&#xff1a;root [1,2,2,3,4,4,3] 输出&#xff1a;…