Spring-Cloud-Loadblancer详细分析_2

@LoadBalancerClients

终于分析到了此注解的作用,它是实现不同服务之间的配置隔离的关键

@Configuration(proxyBeanMethods = false)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
@Documented
@Import(LoadBalancerClientConfigurationRegistrar.class)
public @interface LoadBalancerClients {

	LoadBalancerClient[] value() default {};

	/**
	 * {@link LoadBalancerClientConfigurationRegistrar} creates a
	 * {@link LoadBalancerClientSpecification} with this as an argument. These in turn are
	 * added as default contexts in {@link LoadBalancerClientFactory}. Configuration
	 * defined in these classes are used as defaults if values aren't defined via
	 * {@link LoadBalancerClient#configuration()}
	 * @return classes for default configurations
	 */
	Class<?>[] defaultConfiguration() default {};

}

@Configuration(proxyBeanMethods = false)
@Import(LoadBalancerClientConfigurationRegistrar.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LoadBalancerClient {

	/**
	 * Synonym for name (the name of the client).
	 *
	 * @see #name()
	 * @return the name of the load balancer client
	 */
	@AliasFor("name")
	String value() default "";

	/**
	 * The name of the load balancer client, uniquely identifying a set of client
	 * resources, including a load balancer.
	 * @return the name of the load balancer client
	 */
	@AliasFor("value")
	String name() default "";

	/**
	 * A custom <code>@Configuration</code> for the load balancer client. Can contain
	 * override <code>@Bean</code> definition for the pieces that make up the client.
	 *
	 * @see LoadBalancerClientConfiguration for the defaults
	 * @return configuration classes for the load balancer client.
	 */
	Class<?>[] configuration() default {};

}
  • LoadBalancerClients 就是其实就是多个LoadBalancerClient
  • LoadBalancerClient 相当于一个负载均衡配置,name/value 就是 serviceId,configuration 就是负载均衡配置
  • 通过@Import(LoadBalancerClientConfigurationRegistrar.class)来进行注入

LoadBalancerClientConfigurationRegistrar

public class LoadBalancerClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar {

	private static String getClientName(Map<String, Object> client) {
		if (client == null) {
			return null;
		}
		String value = (String) client.get("value");
		if (!StringUtils.hasText(value)) {
			value = (String) client.get("name");
		}
		if (StringUtils.hasText(value)) {
			return value;
		}
		throw new IllegalStateException("Either 'name' or 'value' must be provided in @LoadBalancerClient");
	}

	private static void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
			Object configuration) {
		BeanDefinitionBuilder builder = BeanDefinitionBuilder
				.genericBeanDefinition(LoadBalancerClientSpecification.class);
		builder.addConstructorArgValue(name);
		builder.addConstructorArgValue(configuration);
		//每个LoadBalancerClient其实就是LoadBalancerClientSpecification
		registry.registerBeanDefinition(name + ".LoadBalancerClientSpecification", builder.getBeanDefinition());
	}

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		Map<String, Object> attrs = metadata.getAnnotationAttributes(LoadBalancerClients.class.getName(), true);
		//获取LoadBalancerClients 注解的属性
		if (attrs != null && attrs.containsKey("value")) {
			//value属性为LoadBalancerClient注解,可以给configuration赋值,填写需要的配置类,比如RandomLoadBalancerConfig.class
			AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value");
			for (AnnotationAttributes client : clients) {
				//将配置类 注册到当前容器
				registerClientConfiguration(registry, getClientName(client), client.get("configuration"));
			}
		}
		if (attrs != null && attrs.containsKey("defaultConfiguration")) {
			String name;
			if (metadata.hasEnclosingClass()) {
				name = "default." + metadata.getEnclosingClassName();
			}
			else {
				name = "default." + metadata.getClassName();
			}
			//如果defaultConfiguration有配置,当做默认配置注册中,并赋值给所有容器
			registerClientConfiguration(registry, name, attrs.get("defaultConfiguration"));
		}
		//处理@LoadBalancerClient注解,逻辑和上面LoadBalancerClients的相同
		Map<String, Object> client = metadata.getAnnotationAttributes(LoadBalancerClient.class.getName(), true);
		String name = getClientName(client);
		if (name != null) {
			//configuration 属性的类 注册到容器中
			registerClientConfiguration(registry, name, client.get("configuration"));
		}
	}

}
  • LoadBalancerClients有一个默认的配置属性 Class<?>[] configuration() default {};这个属性的类以default. 开头来命名
  • LoadBalancerClients含有多个LoadBalancerClient,属性valueserviceId属性 Class<?>[] configuration() default {};是当前serviceId名字下的配置
  • LoadBalancerClients和每个LoadBalancerClientconfiguration都会被封装以clientName为维度的LoadBalancerClientSpecification,注册到当前容器中,最终注入到LoadBalancerAutoConfiguration配置类的属性configurations,用于LoadBalancerClientFactory 的实例化
  • LoadBalancerClientFactory实例化的时候,就会获取LoadBalancerClientSpecification的对象了
    在这里插入图片描述在这里插入图片描述
    可以看到将LoadBalancerAutoConfigurationBlockingLoadBalancerClientAutoConfiguration注入,包装成LoadBalancerClientSpecification类型

LoadBalancerClientFactory

再看一眼LoadBalancerClientFactorybean实例生成的过程
在这里插入图片描述
可以看到将LoadBalancerClientSpecification类型的LoadBalancerAutoConfigurationBlockingLoadBalancerClientAutoConfiguration通过setConfiguations方法注入了进入

setConfiguations方法是在父类NamedContextFactory中执行的,稍微会分析NamedContextFactory,这里先分析LoadBalancerClientFactory 的结构

public class LoadBalancerClientFactory extends NamedContextFactory<LoadBalancerClientSpecification>
		implements ReactiveLoadBalancer.Factory<ServiceInstance> {

	private static final Log log = LogFactory.getLog(LoadBalancerClientFactory.class);

	/**
	 * Property source name for load balancer.
	 */
	public static final String NAMESPACE = "loadbalancer";

	/**
	 * Property for client name within the load balancer namespace.
	 */
	public static final String PROPERTY_NAME = NAMESPACE + ".client.name";

	private final LoadBalancerClientsProperties properties;


	public LoadBalancerClientFactory(LoadBalancerClientsProperties properties) {
		//记住这个LoadBalancerClientConfiguration,被当成了默认配置类注入到 NamedContextFactory ,也就是每个子容器都会有这个配置类
		super(LoadBalancerClientConfiguration.class, NAMESPACE, PROPERTY_NAME);
		this.properties = properties;
	}

	public static String getName(Environment environment) {
		return environment.getProperty(PROPERTY_NAME);
	}

	@Override
	public ReactiveLoadBalancer<ServiceInstance> getInstance(String serviceId) {
		return getInstance(serviceId, ReactorServiceInstanceLoadBalancer.class);
	}

	@Override
	public LoadBalancerProperties getProperties(String serviceId) {
		if (properties == null) {
			if (log.isWarnEnabled()) {
				log.warn("LoadBalancerClientsProperties is null. Please use the new constructor.");
			}
			return null;
		}
		if (serviceId == null || !properties.getClients().containsKey(serviceId)) {
			// no specific client properties, return default
			return properties;
		}
		// because specifics are overlayed on top of defaults, everything in `properties`,
		// unless overridden, is in `clientsProperties`
		return properties.getClients().get(serviceId);
	}

}

既然LoadBalancerClientConfiguration注入到NamedContextFactory中,我们就分析此配置类

LoadBalancerClientConfiguration

@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
public class LoadBalancerClientConfiguration {

	private static final int REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER = 193827465;
    
    /**
     * 怕读者忘了此配置的注入过程,这里再说一下
     * 上文说的LoadBalancerAutoConfiguration配置类中,创建了 LoadBalancerClientFactory 的bean对象,存在于父容器中
     * 在此bean创建的过程中,会通过构造方法将此配置LoadBalancerClientConfiguration转入到LoadBalancerClientFactory中
     * 然后会当做默认配置类注册到 NamedContextFactory 的子容器中,这样每个子容器都拥有
     * 我们也可以自己实现这个ReactorLoadBalancer类型的bean,覆盖此配置
     *
     * */
	@Bean
	@ConditionalOnMissingBean
	public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment,
			LoadBalancerClientFactory loadBalancerClientFactory) {
        // 获取当前子容器的名称(也是服务名)
		String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
		return new RoundRobinLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
	}

	//省略...

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnBlockingDiscoveryEnabled
	@Order(REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER + 1)
	public static class BlockingSupportConfiguration {
        //ServiceInstanceListSupplier来查询ServiceInstance服务列表,能看到这里是DiscoveryClient提供的。这样就和注册中心关联了
		@Bean
		@ConditionalOnBean(DiscoveryClient.class)
		@ConditionalOnMissingBean
		@Conditional(DefaultConfigurationCondition.class)
		public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
				ConfigurableApplicationContext context) {
			return ServiceInstanceListSupplier.builder().withBlockingDiscoveryClient().withCaching().build(context);
		}

	}

	//省略...

}

LoadBalancerClientConfiguration的作用分析完了,在上文中

public LoadBalancerClientFactory(LoadBalancerClientsProperties properties) {
	//记住这个LoadBalancerClientConfiguration,被当成了默认配置类注入到 NamedContextFactory ,也就是每个子容器都会有这个配置类
	super(LoadBalancerClientConfiguration.class, NAMESPACE, PROPERTY_NAME);
	this.properties = properties;
}

LoadBalancerClientConfiguration是传给了父类,LoadBalancerClientFactory 的结构

public class LoadBalancerClientFactory extends NamedContextFactory<LoadBalancerClientSpecification>
		implements ReactiveLoadBalancer.Factory<ServiceInstance> {..}

LoadBalancerClientFactory生成bean的过程中,调用完构造方法后,又执行了clientFactory.setConfigurations(this.configurations.getIfAvailable(Collections::emptyList)),此方法是在父类NamedContextFactory执行的

看一下clientFactory.setConfigurations(this.configurations.getIfAvailable(Collections::emptyList))执行结果
在这里插入图片描述

下面我们要分析父类NamedContextFactory,非常的重要

NamedContextFactory

public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
		implements DisposableBean, ApplicationContextAware {

	private final String propertySourceName;

	private final String propertyName;

	private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();

	private Map<String, C> configurations = new ConcurrentHashMap<>();

	private ApplicationContext parent;

	private Class<?> defaultConfigType;

	/**
	 * defaultConfigType是刚才文中说的子类LoadBalancerClientFactory创建时注入的。类型为LoadBalancerClientConfiguration
	 * */
	public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName, String propertyName) {
		this.defaultConfigType = defaultConfigType;
		this.propertySourceName = propertySourceName;
		this.propertyName = propertyName;
	}

	@Override
	public void setApplicationContext(ApplicationContext parent) throws BeansException {
		this.parent = parent;
	}

	public ApplicationContext getParent() {
		return parent;
	}
	/**
	 * configurations为LoadBalancerAutoConfiguration和BlockingLoadBalancerClientAutoConfiguration,
	 * 文中刚才分析过
	 * */

	public void setConfigurations(List<C> configurations) {
		for (C client : configurations) {
			this.configurations.put(client.getName(), client);
		}
	}

	public Set<String> getContextNames() {
		return new HashSet<>(this.contexts.keySet());
	}

	@Override
	public void destroy() {
		Collection<AnnotationConfigApplicationContext> values = this.contexts.values();
		for (AnnotationConfigApplicationContext context : values) {
			// This can fail, but it never throws an exception (you see stack traces
			// logged as WARN).
			context.close();
		}
		this.contexts.clear();
	}

	protected AnnotationConfigApplicationContext getContext(String name) {
		if (!this.contexts.containsKey(name)) {
			synchronized (this.contexts) {
				if (!this.contexts.containsKey(name)) {
					//如果不存在则先创建容器
					this.contexts.put(name, createContext(name));
				}
			}
		}
		return this.contexts.get(name);
	}

	protected AnnotationConfigApplicationContext createContext(String name) {
		AnnotationConfigApplicationContext context;
		//创建容器
		if (this.parent != null) {
			DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
			if (parent instanceof ConfigurableApplicationContext) {
				beanFactory.setBeanClassLoader(
						((ConfigurableApplicationContext) parent).getBeanFactory().getBeanClassLoader());
			}
			else {
				beanFactory.setBeanClassLoader(parent.getClassLoader());
			}
			context = new AnnotationConfigApplicationContext(beanFactory);
			context.setClassLoader(this.parent.getClassLoader());
		}
		else {
			context = new AnnotationConfigApplicationContext();
		}
		//configurations就是LoadBalancerClientSpecification类型的LoadBalancerAutoConfiguration和BlockingLoadBalancerClientAutoConfiguration
		//这里是将@LoadBalancerClient对应的负载均衡配置注册到对应的容器中
		//由以上可知通过此步我们可以使用@LoadBalancerClient自定义负载均衡策略
		//如果不自定义的话,这里为false
		if (this.configurations.containsKey(name)) {
			for (Class<?> configuration : this.configurations.get(name).getConfiguration()) {
				context.register(configuration);
			}
		}
		//也就是将configurations中LoadBalancerClientSpecification类型的LoadBalancerAutoConfiguration和BlockingLoadBalancerClientAutoConfiguration注册到容器中
		//这样每个容器就拥有了
		for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
			if (entry.getKey().startsWith("default.")) {
				for (Class<?> configuration : entry.getValue().getConfiguration()) {
					context.register(configuration);
				}
			}
		}
		//defaultConfigType就是LoadBalancerClientConfiguration,在子类LoadBalancerClientFactory的构造方法传入
		//在刚才分析LoadBalancerClientFactory的时候介绍过
		context.register(PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType);
		context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(this.propertySourceName,
				Collections.<String, Object>singletonMap(this.propertyName, name)));
		//将父容器添加进去
		if (this.parent != null) {
			// Uses Environment from parent as well as beans
			context.setParent(this.parent);
		}
		context.setDisplayName(generateDisplayName(name));
		context.refresh();
		return context;
	}

	protected String generateDisplayName(String name) {
		return this.getClass().getSimpleName() + "-" + name;
	}
	/**
	 * 每个LoadBalancerClientSpecification都创建一个AnnotationConfigApplicationContext
	 * 也就是每个LoadBalancerClient会对应一个容器,其中的配置就对应容器中bean实例
	 * */
	public <T> T getInstance(String name, Class<T> type) {
		//获取AnnotationConfigApplicationContext类型的容器
		AnnotationConfigApplicationContext context = getContext(name);
		try {
			//从容器中获取对应的实例
			return context.getBean(type);
		}
		catch (NoSuchBeanDefinitionException e) {
			// ignore
		}
		return null;
	}

	public <T> ObjectProvider<T> getLazyProvider(String name, Class<T> type) {
		return new ClientFactoryObjectProvider<>(this, name, type);
	}

	public <T> ObjectProvider<T> getProvider(String name, Class<T> type) {
		AnnotationConfigApplicationContext context = getContext(name);
		return context.getBeanProvider(type);
	}

	public <T> T getInstance(String name, Class<?> clazz, Class<?>... generics) {
		ResolvableType type = ResolvableType.forClassWithGenerics(clazz, generics);
		return getInstance(name, type);
	}

	@SuppressWarnings("unchecked")
	public <T> T getInstance(String name, ResolvableType type) {
		AnnotationConfigApplicationContext context = getContext(name);
		String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, type);
		if (beanNames.length > 0) {
			for (String beanName : beanNames) {
				if (context.isTypeMatch(beanName, type)) {
					return (T) context.getBean(beanName);
				}
			}
		}
		return null;
	}

	public <T> Map<String, T> getInstances(String name, Class<T> type) {
		AnnotationConfigApplicationContext context = getContext(name);

		return BeanFactoryUtils.beansOfTypeIncludingAncestors(context, type);
	}

	/**
	 * Specification with name and configuration.
	 */
	public interface Specification {

		String getName();

		Class<?>[] getConfiguration();

	}

}

到这里将每个@LoadBalancerClient都创建了AnnotationConfigApplicationContext的容器,然后放到了 LoadBalancerClientFactory

到这里bean的生成过程分析完毕,下一篇文章会分析整个执行过程

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

电脑开不了机如何解锁BitLocker硬盘锁

事情从这里说起&#xff0c;不想看直接跳过 早上闲着无聊&#xff0c;闲着没事干&#xff0c;将win11的用户名称改成了含有中文字符的用户名&#xff0c;然后恐怖的事情发生了&#xff0c;蓝屏了… 然后就是蓝屏收集错误信息&#xff0c;重启&#xff0c;蓝屏收集错误信息&…

基于深度学习的3D城市模型增强【Mask R-CNN】

在这篇文章中&#xff0c;我们描述了一个为阿姆斯特丹 3D 城市模型自动添加门窗的系统&#xff08;可以在这里访问&#xff09;。 计算机视觉用于从城市全景图像中提取有关门窗位置的信息。 由于这种类型的街道级图像广泛可用&#xff0c;因此该方法可用于较大的地理区域。 推荐…

如何把图片转成gif?一分钟学会在线一键生成gif

平时我们在聊天的时候&#xff0c;经常会发送一下有趣的表情包&#xff0c;这些表情包是怎么做出来的呢&#xff1f;其实可以使用在线gif生成的方法&#xff0c;下面就来给大家演示一下图片制作gif&#xff08;https://www.gif.cn&#xff09;的具体步骤&#xff0c;一起来看看…

Flink学习记录

可以快速搭建一个Flink编写程序 mvn archetype:generate \-DarchetypeGroupIdorg.apache.flink \-DarchetypeArtifactIdflink-quickstart-java \-DarchetypeVersion1.17.1 \-DgroupIdcom.zxx.langhuan \-DartifactIdlanghuan-flink \-Dversion1.0.0-SNAPSHOT \-Dpackagecom.zx…

《全生命周期眼健康管理》助力健康科学用眼

8月8日下午&#xff0c;烟台正大光明眼科医院眼健康管理中心张提主任受邀来到烟台市残疾人事务综合服务中心&#xff0c;为残联康复训练教师及相关工作人员进行了《全生命周期眼健康管理》讲座。 烟台正大光明眼科医院眼健康管理中心张提主任 “全生命周期眼健康”这一理念其宗…

流水线时序调度之规避冲突

1 写在前面的&#xff1a; 其实略微一个大点的机器&#xff0c;一个测试流程需要若干个步骤&#xff0c;都可以用流水线的思维去看待它&#xff1b; 我之前也没往流水线的角度去考虑&#xff0c;那有些机器的时序调度是不好理解的&#xff0c;甚至计算个通量都很麻烦&#xff…

15-1_Qt 5.9 C++开发指南_Qt多媒体模块概述

多媒体功能指的主要是计算机的音频和视频的输入、输出、显示和播放等功能&#xff0c;Qt 的多媒体模块为音频和视频播放、录音、摄像头拍照和录像等提供支持&#xff0c;甚至还提供数字收音机的支持。本章将介绍 Qt 多媒体模块的功能和使用。 文章目录 1. Qt 多媒体模块概述2. …

pgAdmin开发工具之ERD

pgAdmin 是一个免费的 PostgreSQL 管理与开发平台&#xff0c;这篇文章介绍了它的安装与使用。 今天我们要介绍的是它的一个开发功能&#xff1a;实体关系图&#xff08;ERD&#xff09;工具。 ERD 工具可以使用图形化的方式表示数据库中的表、字段以及表之间的关系。 pgAdmin…

vue svg画渐变色线条

基于业务需求需要&#xff0c;需要使用svg画渐变色弧线并且采用虚线。并且封装成组件。 一、path路径 path路径是svg中最强大的图形&#xff0c;可以绘制各种svg所有能画的图形。 路径中的线是由d属性来绘制&#xff0c;属性参数由各种命令组成&#xff0c;以下是它的基本命…

selenium.webdriver Python爬虫教程

文章目录 selenium安装和使用 selenium安装和使用 pip install selenium 下载对应的浏览器驱动 实例化浏览器 from selenium import webdriverbrowser webdriver.Chrome()元素定位 控制浏览器

Jmeter设置中文的两种方式,建议使用第二种

方案一 进入jmeter图像化界面&#xff0c;选择Options下的Choose Language&#xff0c;再选择Chinese(Simplified)。这个就是选择语言为简体中文&#xff08;缺陷&#xff1a;这个只是在本次使用时为中文&#xff0c;下次打开默认还是英文的&#xff09; 方案二&#xff08;…

时序预测 | Python实现LSTM长短期记忆网络时间序列预测(电力负荷预测)

时序预测 | Python实现LSTM长短期记忆网络时间序列预测(电力负荷预测) 目录 时序预测 | Python实现LSTM长短期记忆网络时间序列预测(电力负荷预测)效果一览基本描述模型结构程序设计参考资料效果一览

Redis安装配置远程连接

1. yum 安装 redis&#xff1a; 直接使用命令&#xff0c;将 redis 安装到 linux 服务器中&#xff1a; yum -y install redis 2. 启动 redis&#xff1a; 在 xshell 里&#xff0c;可以使用下面命令&#xff0c;以后台方式启动 redis&#xff1a; [rootVM-8-17-centos /]…

Vue3+Ts+Vite项目全局配置Element-Plus主题色

概述 我找了很多博客&#xff0c;想全局配置Elmenet-Plus组件主题色&#xff0c;但都没有效果。所以有了这篇博客&#xff0c;希望能对你有所帮助&#xff01;&#xff01;&#xff01; 文章目录 概述一、先看效果二、创建全局颜色文件2.1 /src/styles 下新建 element-plus.sc…

gitlab-Runner搭建

root wget https://packages.gitlab.com/runner/gitlab-runner/packages/fedora/29/gitlab-runner-12.6.0-1.x86_64.rpm/download.rpm rpm -ivh download.rpm ---- 安装 rpm -Uvh download.rpm -----更新升级 然后运行&#xff1a; gitlab-runner register --url https://git…

虚拟展览馆有哪些优势?如何打造自己的虚拟展览馆

引言&#xff1a; 随着科技的不断创新与发展&#xff0c;虚拟展览馆作为一种全新的文化体验方式&#xff0c;正逐渐引起人们的关注。虚拟展览馆以其便捷、创新、可定制的特点&#xff0c;为参观者提供了前所未有的沉浸式体验。 一&#xff0e;什么是虚拟展览馆&#xff1f; 虚…

C语言学习系列-->看淡指针(1)

文章目录 一、概述二、指针变量和地址2.1 取地址操作符2.2 指针变量和解引用操作符2.2.1 指针变量2.2.2 拆解指针类型2.2.4 解引用操作符 2.3 指针变量的大小 三、指针变量的意义3.1 指针的解引用指针-整数 四、 const修饰指针五、指针运算5.1 指针- 整数5.2 指针-指针5.3 指针…

音视频研发分享:关键帧截图+wasm快照--我又做了一件有益于社会的事情

音视频研发分享&#xff1a;关键帧截图wasm快照--我又做了一件有益于社会的事情 简单的一个视频设备快照功能到底有多费事多费电&#xff1f;新的方法有方法&#xff01; 省了多少电&#xff1f; 简单的一个视频设备快照功能到底有多费事多费电&#xff1f; 以前&#xff0c;我…

C++模板,STL(Standard Template Library)

这篇文章的主要内容是C中的函数模板、类模板、STL的介绍。 希望对C爱好者有所帮助&#xff0c;内容充实且干货&#xff0c;点赞收藏防止找不到&#xff01; 再次感谢每个读者和正在学习编程的朋友莅临&#xff01; 更多优质内容请点击移驾&#xff1a; C收录库&#xff1a;重生…

HTML5 Canvas和Svg:哪个简单且好用?

HTML5 Canvas 和 SVG 都是基于标准的 HTML5 技术&#xff0c;可用于创建令人惊叹的图形和视觉体验。 首先&#xff0c;让我们花几句话介绍HTML5 Canvas和SVG。 什么是Canvas? Canvas&#xff08;通过 标签使用&#xff09;是一个 HTML 元素&#xff0c;用于在用户计算机屏幕…