.NET IoC 容器(三)Autofac

目录

  • .NET IoC 容器(三)Autofac
    • Autofac
    • Nuget 安装
    • 实现DI
      • 定义接口
      • 定义实现类
      • 依赖注入
    • 注入方式
      • 构造函数注入 | 属性注入 | 方法注入
      • 注入实现
    • 接口注册
      • 重复注册
      • 指定参数注册
    • 生命周期
      • 默认生命周期
      • 单例生命周期
      • 每个周期范围一个生命周期
    • 依赖配置
      • Nuget
      • 配置文件
    • AOP
      • Nuget安装
      • 定义接口、实现类
      • 定义切面
      • 容器配置
    • 参考资料

.NET IoC 容器(三)Autofac

Autofac

Autofac 是一个用于 .NET 应用程序的依赖注入 (Dependency Injection, DI) 容器。它帮助开发人员管理对象的创建和生命周期,使得依赖项的注入更加灵活和可维护。以下是 Autofac 的主要功能和特性概述:

依赖注入 (Dependency Injection)

Autofac 允许你通过构造函数、属性或方法注入依赖项。这样,你可以轻松地将对象的依赖关系传递给需要它们的类,从而提高代码的可测试性和可维护性。

模块化设计 (Modular Design)

Autofac 支持模块化设计,可以将相关的依赖项注册逻辑分组到模块中。这使得大型应用程序的配置更加简洁和易于管理。

生命周期管理 (Lifecycle Management)

Autofac 提供多种对象生命周期管理方式,例如单例 (Singleton)、每次请求 (Instance Per Dependency)、每个生命周期范围 (Instance Per Lifetime Scope) 等,允许你精确控制对象的创建和销毁时机。

注册与解析 (Registration and Resolution)

Autofac 通过流畅的 API 提供了多种注册组件的方式。你可以注册类型、实例、工厂方法,甚至通过扫描程序集自动注册组件。此外,Autofac 的解析功能支持构造函数参数注入、命名参数注入等高级特性。

支持 AOP (Aspect-Oriented Programming)

Autofac 与 AOP 框架(如 Castle Windsor)集成,支持横切关注点(如日志记录、事务管理)的处理,从而使业务逻辑代码更加简洁。

集成支持

Autofac 与许多流行的 .NET 库和框架集成良好,包括 ASP.NET Core、WCF、Web API、MVC 等。通过这些集成,Autofac 可以轻松地管理 web 应用程序中的依赖项。

扩展性

Autofac 具有很高的扩展性,可以通过自定义注册源、生命周期、解析器等方式扩展其功能,以满足特定应用的需求。

Nuget 安装

在这里插入图片描述

实现DI

定义接口

internal interface IComputer
{
}
internal interface IKeyboard
{
}
internal interface IMouse
{
}
internal interface IPerson
{IComputer Computer { get; set; }IKeyboard Keyboard { get; set; }IMouse Mouse { get; set; }void Work();
}

定义实现类

internal class ConstructBase
{public ConstructBase() {Console.WriteLine($"{this.GetType().Name} - 被构造了");}
}
internal class LenovoComputer : ConstructBase, IComputer
{public LenovoComputer() : base() { }
}
internal class TogarKeyboard : ConstructBase, IKeyboard
{public TogarKeyboard() : base() { }
}
internal class LogitechMouse : ConstructBase, IMouse
{public LogitechMouse() : base() { }
}
internal class Programmer : ConstructBase, IPerson
{public IComputer Computer { get; set; }public IKeyboard Keyboard { get; set; }public IMouse Mouse { get; set; }public Programmer(IComputer computer, IKeyboard keyboard, IMouse mouse) : base(){Computer = computer;Keyboard = keyboard;Mouse = mouse;}public void Work(){Console.WriteLine("Programmers are building software...");}
}

依赖注入

var builder = new ContainerBuilder();
builder.RegisterType<LenovoComputer>().As<IComputer>();
builder.RegisterType<TogarKeyboard>().As<IKeyboard>();
builder.RegisterType<LogitechMouse>().As<IMouse>();
builder.RegisterType<Programmer>().As<IPerson>();
var container = builder.Build();
IPerson programmer = container.Resolve<IPerson>();
programmer.Work();

输出

Zhy.IoC.Autofac.LenovoComputer - 被构造了
Zhy.IoC.Autofac.TogarKeyboard - 被构造了
Zhy.IoC.Autofac.LogitechMouse - 被构造了
Zhy.IoC.Autofac.Programmer - 被构造了
Programmers are building software...

注入方式

构造函数注入 | 属性注入 | 方法注入

定义特性用来筛选属性注入

public class InjectPropertyAttribute : Attribute
{}

定义实现类

public class Gamer : ConstructBase, IPerson
{public IComputer Computer { get; set; }[InjectProperty]public IKeyboard Keyboard { get; set; }public IMouse Mouse { get; set; }public Gamer(IComputer computer) : base(){Computer = computer;}public void Inject(IMouse mouse){Mouse = mouse;}public void Work(){Console.WriteLine("The player is playing...");}
}

注入实现

// 注入顺序(与编码顺序无关):构造函数注入 > 属性注入 > 方法注入
var builder = new ContainerBuilder();
builder.RegisterType<LenovoComputer>().As<IComputer>();
builder.RegisterType<TogarKeyboard>().As<IKeyboard>();
builder.RegisterType<LogitechMouse>().As<IMouse>();
builder.RegisterType<Gamer>()// 方法注入.OnActivating(e =>{IMouse mouse = e.Context.Resolve<IMouse>();e.Instance.Inject(mouse);})// 属性注入.PropertiesAutowired((g, o) => g.Name == "Keyboard")// 构造函数注入.UsingConstructor(typeof(IComputer)).As<IPerson>();
var container = builder.Build();
IPerson gamer = container.Resolve<IPerson>();
gamer.Work();

输出

Zhy.IoC.Autofac.LenovoComputer - 被构造了
Zhy.IoC.Autofac.Gamer - 被构造了
Zhy.IoC.Autofac.TogarKeyboard - 被构造了
Zhy.IoC.Autofac.LogitechMouse - 被构造了
The player is playing...

结果

注入顺序:构造函数注入 > 属性注入 > 方法注入

接口注册

重复注册

var builder = new ContainerBuilder();
builder.RegisterType<LenovoComputer>().As<IComputer>();
builder.RegisterType<TogarKeyboard>().As<IKeyboard>();
builder.RegisterType<LogitechMouse>().As<IMouse>();
builder.RegisterType<Programmer>().As<IPerson>();
builder.RegisterType<Gamer>()// 方法注入.OnActivating(e =>{IMouse mouse = e.Context.Resolve<IMouse>();e.Instance.Inject(mouse);})// 属性注入.PropertiesAutowired((g, o) => g.Name == "Keyboard")// 构造函数注入.UsingConstructor(typeof(IComputer)).As<IPerson>();
var container = builder.Build();
IPerson programmer = container.Resolve<IPerson>();
programmer.Work();

输出

Zhy.IoC.Autofac.LenovoComputer - 被构造了
Zhy.IoC.Autofac.Gamer - 被构造了
Zhy.IoC.Autofac.TogarKeyboard - 被构造了
Zhy.IoC.Autofac.LogitechMouse - 被构造了
The player is playing...

调换Gamer和Programmer的注册顺序:

var builder = new ContainerBuilder();
builder.RegisterType<LenovoComputer>().As<IComputer>();
builder.RegisterType<TogarKeyboard>().As<IKeyboard>();
builder.RegisterType<LogitechMouse>().As<IMouse>();
builder.RegisterType<Gamer>()// 方法注入.OnActivating(e =>{IMouse mouse = e.Context.Resolve<IMouse>();e.Instance.Inject(mouse);})// 属性注入.PropertiesAutowired((g, o) => g.Name == "Keyboard")// 构造函数注入.UsingConstructor(typeof(IComputer)).As<IPerson>();
builder.RegisterType<Programmer>().As<IPerson>();
var container = builder.Build();
IPerson programmer = container.Resolve<IPerson>();
programmer.Work();

输出

Zhy.IoC.Autofac.LenovoComputer - 被构造了
Zhy.IoC.Autofac.TogarKeyboard - 被构造了
Zhy.IoC.Autofac.LogitechMouse - 被构造了
Zhy.IoC.Autofac.Programmer - 被构造了
Programmers are building software...

结论

同一接口多次注册时,后注册的会覆盖前面注册的,若需要实现多重注册,需要指定名称

var builder = new ContainerBuilder();
builder.RegisterType<LenovoComputer>().As<IComputer>();
builder.RegisterType<TogarKeyboard>().As<IKeyboard>();
builder.RegisterType<LogitechMouse>().As<IMouse>();
builder.RegisterType<Gamer>()// 方法注入.OnActivating(e =>{IMouse mouse = e.Context.Resolve<IMouse>();e.Instance.Inject(mouse);})// 属性注入.PropertiesAutowired((g, o) => g.Name == "Keyboard")// 构造函数注入.UsingConstructor(typeof(IComputer)).Named<IPerson>("person");
builder.RegisterType<Programmer>().Named<IPerson>("programmer");
var container = builder.Build();
IPerson programmer = container.ResolveNamed<IPerson>("programmer");
IPerson person = container.ResolveNamed<IPerson>("person");
programmer.Work();
person.Work();

输出

Zhy.IoC.Autofac.LenovoComputer - 被构造了
Zhy.IoC.Autofac.TogarKeyboard - 被构造了
Zhy.IoC.Autofac.LogitechMouse - 被构造了
Zhy.IoC.Autofac.Programmer - 被构造了
Zhy.IoC.Autofac.LenovoComputer - 被构造了
Zhy.IoC.Autofac.Gamer - 被构造了
Zhy.IoC.Autofac.TogarKeyboard - 被构造了
Zhy.IoC.Autofac.LogitechMouse - 被构造了
Programmers are building software...
The player is playing...

指定参数注册

修改接口

internal interface IMouse
{string Type { get; set; }
}

修改实现类

internal class LogitechMouse : ConstructBase, IMouse
{public LogitechMouse() : base() { }public LogitechMouse(string type) : base(){Type = type;}public string Type { get; set; }
}

注入

var builder = new ContainerBuilder();
builder.RegisterType<LogitechMouse>().WithParameter("type","502").As<IMouse>();
var container = builder.Build();
IMouse mouse = container.Resolve<IMouse>();
Console.WriteLine("mouse type: " + mouse.Type);

输出

Zhy.IoC.Autofac.LogitechMouse - 被构造了
mouse type: 502

生命周期

Instance Per Dependency(默认) 每次请求组件时都会创建一个新的实例。这是默认的生命周期管理模式。

builder.RegisterType<MyService>().As<IMyService>();

Singleton 在整个容器生命周期内,只有一个实例。

builder.RegisterType<MyService>().As<IMyService>().SingleInstance();

Instance Per Lifetime Scope 在每个生命周期范围内,共享一个实例。每个生命周期范围都会创建一个新的实例,但在该范围内共享该实例。

builder.RegisterType<MyService>().As<IMyService>().InstancePerLifetimeScope();

Instance Per Matching Lifetime Scope 在特定的生命周期范围内共享实例,需要指定标记。

builder.RegisterType<MyService>().As<IMyService>().InstancePerMatchingLifetimeScope("my-scope");

Instance Per Request 通常用于 web 应用程序,在每个 HTTP 请求期间共享一个实例。

builder.RegisterType<MyService>().As<IMyService>().InstancePerRequest();

Externally Owned 组件由外部代码管理生命周期,Autofac 不会在容器释放时释放该实例。

builder.RegisterType<MyService>().As<IMyService>().ExternallyOwned();

Instance Per Dependency 每次依赖请求时都会创建一个新实例。

builder.RegisterType<MyService>().As<IMyService>().InstancePerDependency();

默认生命周期

var builder = new ContainerBuilder();
builder.RegisterType<LenovoComputer>().As<IComputer>();
var container = builder.Build();
IComputer computer0 = container.Resolve<IComputer>();
IComputer computer1 = container.Resolve<IComputer>();
Console.WriteLine("computer0: " + computer0.GetHashCode());
Console.WriteLine("computer1: " + computer1.GetHashCode());
Console.WriteLine($"computer0 == computer1: {computer0 == computer1}");

输出

Zhy.IoC.Autofac.LenovoComputer - 被构造了
Zhy.IoC.Autofac.LenovoComputer - 被构造了
computer0: 32347029
computer1: 22687807
computer0 == computer1: False

单例生命周期

var builder = new ContainerBuilder();
builder.RegisterType<LenovoComputer>().As<IComputer>().SingleInstance();
var container = builder.Build();
IComputer computer0 = container.Resolve<IComputer>();
IComputer computer1 = container.Resolve<IComputer>();
Console.WriteLine("computer0: " + computer0.GetHashCode());
Console.WriteLine("computer1: " + computer1.GetHashCode());
Console.WriteLine($"computer0 == computer1: {computer0 == computer1}");

输出

Zhy.IoC.Autofac.LenovoComputer - 被构造了
computer0: 32347029
computer1: 32347029
computer0 == computer1: True

每个周期范围一个生命周期

var builder = new ContainerBuilder();
builder.RegisterType<LenovoComputer>().As<IComputer>().InstancePerLifetimeScope();
var container = builder.Build();
IComputer computer0 = container.Resolve<IComputer>();
using (var scope = container.BeginLifetimeScope())
{IComputer computer1 = scope.Resolve<IComputer>();IComputer computer2 = scope.Resolve<IComputer>();Console.WriteLine("computer0: " + computer0.GetHashCode());Console.WriteLine("computer1: " + computer1.GetHashCode());Console.WriteLine("computer2: " + computer2.GetHashCode());Console.WriteLine($"computer0 == computer1: {computer0 == computer1}");Console.WriteLine($"computer1 == computer2: {computer1 == computer2}");
}

输出

Zhy.IoC.Autofac.LenovoComputer - 被构造了
Zhy.IoC.Autofac.LenovoComputer - 被构造了
computer0: 32347029
computer1: 22687807
computer2: 22687807
computer0 == computer1: False
computer1 == computer2: True

依赖配置

Nuget

在这里插入图片描述

配置文件

Json

{"components": [{"type": "Zhy.IoC.Autofac.LenovoComputer,Zhy.IoC.Autofac","services": [{"type": "Zhy.IoC.Core.IComputer,Zhy.IoC.Core"}],"autoActivate": true,"instanceScope": "singleinstance"},{"type": "Zhy.IoC.Autofac.TogarKeyboard,Zhy.IoC.Autofac","services": [{"type": "Zhy.IoC.Core.IKeyboard,Zhy.IoC.Core"}],"autoActivate": true,"instanceScope": "singleinstance"},{"type": "Zhy.IoC.Autofac.LogitechMouse,Zhy.IoC.Autofac","services": [{"type": "Zhy.IoC.Core.IMouse,Zhy.IoC.Core"}],"parameters": {"places": 4},"autoActivate": true,"instanceScope": "singleinstance"},{"type": "Zhy.IoC.Autofac.Programmer,Zhy.IoC.Autofac","services": [{"type": "Zhy.IoC.Core.IPerson,Zhy.IoC.Core"}],"autoActivate": true,"instanceScope": "singleinstance"}]
}

XML

<?xml version="1.0" encoding="utf-8" ?>
<autofac><components name="0"><type>Zhy.IoC.Autofac.LenovoComputer,Zhy.IoC.Autofac</type><services name="0" type="Zhy.IoC.Core.IComputer,Zhy.IoC.Core" /></components><components name="1"><type>Zhy.IoC.Autofac.TogarKeyboard,Zhy.IoC.Autofac</type><services name="0" type="Zhy.IoC.Core.IKeyboard,Zhy.IoC.Core" /></components><components name="2"><type>Zhy.IoC.Autofac.LogitechMouse,Zhy.IoC.Autofac</type><services name="0" type="Zhy.IoC.Core.IMouse,Zhy.IoC.Core" /></components><components name="3"><type>Zhy.IoC.Autofac.Programmer,Zhy.IoC.Autofac</type><services name="0" type="Zhy.IoC.Core.IPerson,Zhy.IoC.Core" /></components>
</autofac>

调用

var config = new ConfigurationBuilder();
config.AddJsonFile("DI-Autofac.json");
// config.AddXmlFile("DI-Autofac.xml");
var module = new ConfigurationModule(config.Build());
var builder = new ContainerBuilder();
builder.RegisterModule(module);
var container = builder.Build();
IPerson programmer = container.Resolve<IPerson>();
programmer.Work();

输出

Zhy.IoC.Autofac.LenovoComputer - 被构造了
Zhy.IoC.Autofac.TogarKeyboard - 被构造了
Zhy.IoC.Autofac.LogitechMouse - 被构造了
Zhy.IoC.Autofac.Programmer - 被构造了
Programmers are building software...

AOP

Nuget安装

在这里插入图片描述

定义接口、实现类

public interface IMyService
{void DoWork();
}
public void DoWork()
{Console.WriteLine("Doing work...");
}

定义切面

using Castle.DynamicProxy;public class LoggingInterceptor : IInterceptor
{public void Intercept(IInvocation invocation){Console.WriteLine($"Invoking method {invocation.Method.Name} at {DateTime.Now}");invocation.Proceed();Console.WriteLine($"Method {invocation.Method.Name} has completed at {DateTime.Now}");}
}

容器配置

var builder = new ContainerBuilder();
builder.Register(c => new LoggingInterceptor());
builder.RegisterType<MyService>().As<IMyService>().EnableInterfaceInterceptors().InterceptedBy(typeof(LoggingInterceptor));
var container = builder.Build();
var service = container.Resolve<IMyService>();
service.DoWork();

输出

Invoking method DoWork at 2024/5/30 16:49:34
Doing work...
Method DoWork has completed at 2024/5/30 16:49:34

参考资料

IOC容器之Unity与AutoFac_unity autofac-CSDN博客

Autofac/src/Autofac at develop · autofac/Autofac (github.com)

控制容器的反转和依赖关系注入模式 (martinfowler.com)

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

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

相关文章

0基础学习区块链技术——推演猜想

在《0基础学习区块链技术——入门》一文中&#xff0c;我们结合可视化工具&#xff0c;直观地感受了下区块的结构&#xff0c;以及链式的前后关系。 本文我们将抛弃之前的知识&#xff0c;从0开始思考和推演&#xff0c;区块链技术可能是如何构思出来的。 去中心 在一般的思维…

浅谈配置元件之随机变量

浅谈配置元件之随机变量 1.概述 为了增强测试的真实性和多样性&#xff0c;JMeter 提供了多种配置元件来生成动态数据&#xff0c;其中“随机变量”(Random Variable) 就是一种常用的配置元件&#xff0c;用于生成随机数值、字符串等&#xff0c;以模拟不同用户请求中的变化参…

认识meta

目录 认识meta camera_metadata的存储结构 camera_metadata的基本操作 申请camera_metadata 增加entry 查找entry 更新entry 删除entry 对tag的查找操作 vendor_tag_ops和vendor_cache_ops是Andriod提供的接口 propertyID Camxhal3metadatautil.cpp文件理解 Initia…

Redisson 分布式锁 - RLock、RReadWriteLock、RSemaphore、RCountDownLatch(配置、使用、原理)

目录 前言 Redisson 分布式锁 环境配置 1&#xff09;版本说明 2&#xff09;依赖如下 3&#xff09;配置文件如下 4&#xff09;项目配置 RLock 1&#xff09;使用方式 2&#xff09;加锁解释 3&#xff09;加锁时手动设置时间 4&#xff09;加锁时&#xff0c;到…

揭秘FL Studio21.2.8中文版一键解锁音乐创作新境界!

在音乐制作的广阔天地里&#xff0c;随着技术的不断进步和数字音频工作站&#xff08;DAW&#xff09;软件的普及&#xff0c;越来越多的音乐爱好者和专业制作人开始涉足音乐创作的奇妙旅程。其中&#xff0c;FL Studio以其强大的功能、直观的操作界面和丰富的音色资源&#xf…

LED驱动IC:HC2106,1W升压型DC/DC白光LED驱动器HC2106系列,供应给大功率白光LED灯提供能源、恒流源

LED驱动IC&#xff1a; HC2106&#xff1a;1W升压型DC/DC白光LED驱动器HC2106系列 概述&#xff1a;HC2106系列芯片是针对LED应用设计的PFM 控制模式的开关型DC/DC 升压恒流芯片&#xff0c;通过外接电阻可使输出电流值恒定在0mA&#xff5e;500mA。 HC2106可以给一个、多个…

算法004:盛水最多的容器

这道题比较简单&#xff0c;使用双指针。 要求的是最大面积&#xff0c;对于一个水桶&#xff08;水杯来说&#xff09;&#xff0c;面积的算法是固定的&#xff0c;就是底乘以高。 在这个题中&#xff0c;我们把左边的位置设为left&#xff0c;右边的位置设为right&#xff…

一个月飙升 9k star!打破常规的 git 客户端

作为一名程序员&#xff0c;想必大家每天都要使用 git 来管理自己的代码吧。有些大佬喜欢使用命令行来进行 git 的操作&#xff0c;有些新入门的小白程序员则比较喜欢使用各种 git 客户端来可视化的管理代码&#xff0c;而有些程序员则喜欢使用 IDE 中集成的 git 功能来做代码的…

力扣234. 回文链表

给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为回文链表。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,2,1] 输出&#xff1a;true # Definition for singly-linked list. # c…

R语言探索与分析17-CPI的分析和研究

一、选题背景 CPI&#xff08;居民消费价格指数&#xff09;作为一个重要的宏观经济指标&#xff0c;扮演着评估通货膨胀和居民生活水平的关键角色。在湖北省这个经济活跃的地区&#xff0c;CPI的波动对于居民生活、企业经营以及政府宏观经济政策制定都具有重要的影响。因此&a…

打造卓越任务调度体系:实用攻略与技巧解析

写这篇文章&#xff0c;想和大家从头到脚说说任务调度&#xff0c;希望大家读完之后&#xff0c;能够理解实现一个任务调度系统的核心逻辑。 1 Quartz Quartz 是一款 Java 开源任务调度框架&#xff0c;也是很多 Java 工程师接触任务调度的起点。 下图显示了任务调度的整体流…

基于STC12C5A60S2系列1T 8051单片机实现一主单片机与一从单片机相互发送数据的RS485通信功能

基于STC12C5A60S2系列1T 8051单片机实现一主单片机与一从单片机相互发送数据的RS485通信功能的RS485通信功能 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机串口通信介绍STC12C5A60S2系列1T 8051单片机串口通信的结构基于STC12C5A60S2系列1T 8051单片机串…

zdppy_api 中间件请求原理详解

单个中间件的逻辑 整体执行流程&#xff1a; 1、客户端发起请求2、中间件拦截请求&#xff0c;在请求开始之前执行业务逻辑3、API服务接收到中间件处理之后的请求&#xff0c;和数据库交互&#xff0c;请求数据4、数据库返回数据5、API处理数据库的数据&#xff0c;然后给客户…

【第十一课】空间数据基础与处理——属性数据管理

一、前言 Arcgis分析离不开两大主体数据&#xff0c;一是空间&#xff0c;二是经济属性。在运用 Aecgis 进行分析时&#xff0c;经常会碰到一些涉及多要素的属性更改或填写&#xff0c; 如果按照普通的方法&#xff0c;每个属性进行修改或填写的话&#xff0c;工作量是很大的&…

Apache OFBiz 路径遍历导致RCE漏洞复现(CVE-2024-36104)

0x01 产品简介 Apache OFBiz是一个电子商务平台,用于构建大中型企业级、跨平台、跨数据库、跨应用服务器的多层、分布式电子商务类应用系统。是美国阿帕奇(Apache)基金会的一套企业资源计划(ERP)系统。该系统提供了一整套基于Java的Web应用程序组件和工具。 0x02 漏洞概…

【深入理解计算机系统第3版】补码加法

感觉这部分有点难&#xff0c;所以稍微整理记一下。 抱歉中英混合&#xff0c;来回切换输入法真的很折磨人。 负溢出 正常 正溢出 以4位补码加法为例&#xff0c;理解下表(书中P64) 补码最大值Tmax 2^3 - 1 7, 补码最小值Tmin -2^3 -8 xyz x yz z mod 2^4zU2Tw(z)溢…

超燃混剪热门视频素材去哪里找? 爆款超燃网站合集分享

在今天的数字时代&#xff0c;短视频已成为传播信息和个人表达的主流方式。无论你是混剪爱好者还是自媒体创作者&#xff0c;掌握如何获取和利用高质量的视频素材是关键。本文将介绍几个顶级的视频素材网站&#xff0c;包括国内外的平台&#xff0c;帮助你创建引人入胜的视频作…

【数据结构】二叉搜索树--BST,Binary Search Tree

文章目录 二叉搜索树1. 二叉搜索树的概念2. 二叉搜索树的接口2.1 查找非递归查找递归查找 2.2 中序遍历2.3 插入非递归插入递归插入 2.4 删除非递归删除递归删除 3. 二叉搜索树的应用key搜索模型kv搜索模型 5. oj题 二叉搜索树 1. 二叉搜索树的概念 二叉搜索树又称二叉排序树…

12-学生们参加各科测试的次数(高频 SQL 50 题基础版)

12-学生们参加各科测试的次数 -- 学生表中&#xff0c;id是唯一的&#xff0c;将他作为主表 -- CROSS JOIN产生了一个结果集&#xff0c;该结果集是两个关联表的行的乘积 -- 2行表,与3行表使用cross join,得到2*36行数据 select st.student_id, st.student_name,su.subject_na…

Vxe UI vxe-upload vue上传组件,显示进度条的方法

vxe-upload vue 上传组件 查看官网 https://vxeui.com 显示进度条很简单&#xff0c;需要后台支持进度就可以了&#xff0c;后台实现逻辑具体可以百度&#xff0c;这里只介绍前端逻辑。 vue 上传附件 相关参数说明&#xff0c;具体可以看文档&#xff1a; multiple 是否允许…