kotlin的dagger hilt依赖注入
依赖注入(dependency injection, di)是设计模式的一种,它的实际作用是给对象赋予实例变量。
基础认识
class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// 初始化变量val person = Person("Miirym")setContent {DiStudyTheme {}}}
}class Person(val name: String)
以上就是一个基本的di过程:我们创建了一个对象person,并传递了名为Miirym的Test实例。
在此情形下,Miirym的名称是依赖,因为类Person依赖于这个名字,在此之后才有了实例化的变量。
那么di的好处体现在什么地方?我们使用错误的方式展开:
class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// 初始化变量val person = Person()setContent {DiStudyTheme {}}}
}class Person {// 去除了主构造函数val name = "Miirym"
}
我们移除了传递name的主构造函数,在此情形下,我们无法向构造函数传递参数,例如name。显然,对于每个Person的实例化对象,它们都会包含相同的名称:Miirym。
更复杂的情形是,我们可能有一个依赖于特定repository(存储库)实例的viewmodel,此时这个repository就成为了我们注入这个viewmodel的依赖。
可能我们会有疑问,为什么在一个viewmodel中,我们会需要不同类型的repository?最常见的一个理由就是测试,同时为了保持灵活性,你需要能够传递任何类型的repository。在我给出的例子中,我传递的是简单的name,但是对于viewmodel而言,传入的repository可能会是任何类型的。
简而言之,传入viewmodel的内容由外部决定。对于这个传入的操作,最简单的方式就是构造函数注入(constructor inject),而这正是我们在第一个示例中做的操作。
dagger hilt简介
dagger hilt帮助我们以简单的方式注入了依赖,省去了构造函数注入的操作(如大量的声明传参的依赖),因此我们能更为集中地管理依赖。
更为重要的一点是,我们可以更便捷的控制依赖的寿命。以单例为例,在一个完整的项目中,例如数据库的单例只能存在一个,但是我们不会只有一个单例;同时我们可以确定特定依赖项的范围,比如限定某个依赖只能依附于某个activity,这样当activity被销毁时,该依赖的内存也会被清理,进而用于其他地方。
接下来我们尝试创建依赖于一个repository的viewmodel,对于app级别的build.gradle引入viewmodel和dagger hilt依赖如下。
// ViewModel Composeimplementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.4.1"//Dagger - Hiltimplementation "com.google.dagger:hilt-android:2.40.5"kapt "com.google.dagger:hilt-android-compiler:2.40.5"implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03"kapt "androidx.hilt:hilt-compiler:1.0.0"implementation 'androidx.hilt:hilt-navigation-compose:1.0.0'
同时需要引入kapt和dagger hilt插件。
plugins {...id 'kotlin-kapt'id 'dagger.hilt.android.plugin'
}
引入kapt插件是为了让kotlin处理注解,因为在使用dagger hilt生成代码时,会用到注解。dagger hilt是在编译时注入的,因此在app被启动时、也就是在编译时,我们就知道依赖流向何处了。
对于项目级别的build.gradle我们需要添加依赖。
buildscript {...dependencies {...classpath "com.google.dagger:hilt-android-gradle-plugin:2.40.5"}
}
项目结构示例
项目结构如下。
此处我们模拟一个请求网络的api。
interface MyApi {@GET("test")suspend fun doNetworkCall()
}
在domain层(包含业务实体,用于封装、传输数据)创建 MyRepository接口,用于抽象具体功能。
interface MyRepository {suspend fun doNetworkCall()
}
接着我们创建一个MyRepositoryImpl类,用于在data层实现MyRepository接口。
class MyRepositoryImpl : MyRepository {override suspend fun doNetworkCall() {TODO("Not yet implemented")}}
在此处我希望从MyApi中调用api请求,将内容传递到repository中,这就是di开始的地方:我们希望把MyApi的实例化对象,传递到实现了MyRepository接口的MyRepositoryImpl类中。此处我们继续使用构造方法。
class MyRepositoryImpl(private val api: MyApi // 主构造方法传递依赖
) : MyRepository {override suspend fun doNetworkCall() {TODO("Not yet implemented")}}
但是在dagger hilt中,它是如何知道我们想将api委托给repository的呢?
创建Module提供依赖项
在app中,在涉及到di时,我们使用多种多样的modules(模块)来作为特定类型依赖项的容器。因此当我们要将api交给repository时,我们要做的操作是新建一个模块来实现这一内容。这样做的好处是,存储在模块中的依赖项会和application存活时长相同,这也使得它们成为效率极高的单例。
因此在实际的app中,如果我们使用较多的依赖,我们可以为其创建相应的模块。例如包含某种广播功能的广播模块,或者是用于提供身份验证存储库的身份验证模块。总而言之,最好是每个模块都能拥有其清晰的职能。
根据以上理论,我们创建一个AppModule。
@Module
@InstallIn(SingletonComponent::class)
object AppModule {}
在此处我们添加了属于dagger hilt的注解:Module和InstallIn。前者表明这是一个用于装载依赖项的容器,后者可以设置这个模块的存活时长。在此处我们设置的是SingletonComponent,代表它的存活时长会如同被注入的application,同时还有其他多种多样的component如下。
SingletonComponent | 和被注入的application的存活时长相同 |
ActivityComponent | 和被注入的activity的存活时长相同 |
ViewModelComponent | 和被注入的view model的存活时长相同 |
ActivityRetainedComponent | 当activity被recreate、如做旋转屏幕的操作时,该模块不会被销毁 |
ServiceComponent | 和被注入的service的存活时长相同 |
基于我们试图注入MyApi接口,dagger hilt需要知晓如何创建一个retrofit的实例化对象并返回,这就是我们在模块中需要做的事情。
@Module
@InstallIn(SingletonComponent::class)
object AppModule {@Provides@Singletonfun provideMyApi() : MyApi {return Retrofit.Builder().baseUrl("https://test.com").build().create(MyApi::class.java)}
}
根据以上代码,dagger hilt知道了如何创建一个MyApi的实例化对象,每当我们向MyApi请求一个对象时(如在MyRepositoryImpl的主构造方法中),dagger hilt会在模块中尝试寻找一个这样的对象。
我们加入了两个注解:前者告诉dagger hilt该方法提供了依赖项,后者表示这个方法是一个单例。
此处的Singleton和类注解中的SingletonComponent的区别在于:
类注解的决定了该模块下的依赖项的寿命,所有模块下的依赖项的存活时长都如同application;
方法注解的被称为作用域(scope),在此处我们声明该方法返回的MyApi对象是单例的,如果没有这个注解,那么有多个repository要调用这个方法时,返回的MyApi对象就是不唯一的了。
注入依赖项到ViewModel
在一般情况下,创建一个viewmodel可能需要使用ViewModelProvider下的factory类,用于更详细的定义一个viewmodel,但是这样较为复杂。使用dagger hilt可以避免这一情况。
首先创建ViewModel如下,传入依赖项repository。
class MyViewModel(private val repository: MyRepository
) : ViewModel() {}
若使用dagger hilt来寻找主构造函数中需要的依赖项,需要改造如下。
@HiltViewModel
class MyViewModel @Inject constructor(private val repository: MyRepository
) : ViewModel() {}
- 在viewmodel外部添加注解@HiltViewModel,声明我们需要使用dagger hilt为该viewmodel注入依赖;
- 在主构造函数添加注入注解@Inject和构造函数关键字constructor,向dagger hilt表明我们需要在构造函数中注入这些依赖
经过如上处理,dagger hilt便会从module中寻找是否能提供这些依赖项。所以接下来我们需要在module中添加能够提供viewmodel的方法。
首先在module中创建一个不完整的提供MyRepository方法,因为构建MyRepository需要传入一个MyApi的实例,所以出现了报错;同时注意到,我们先前写的provideMyApi方法,正好返回了MyApi实例。
那么我们是否需要在某个地方调用provideMyApi方法,以此方式来传入MyApi实例呢?
@Module
@InstallIn(SingletonComponent::class)
object AppModule {@Provides@Singletonfun provideMyApi(): MyApi {return Retrofit.Builder().baseUrl("https://test.com").build().create(MyApi::class.java)}@Provides@Singletonfun provideMyRepository(api: MyApi): MyRepository {return MyRepositoryImpl(api)}
}
实际上是不需要的,我们只需要简单的声明需要传入MyApi实例,而dagger hilt会在后台查询是否有能够提供该实例的方法,最终会定位到provideMyApi方法。
经过以上步骤,dagger hilt会将它作为参数传递到我们的provideMyRepository方法,并且创建我们需要的repository;同样的,provideMyRepository方法因为提供了MyRepository实例,同时在我们先前创建viewmodel的主构造函数中请求了MyRepository实例、并使用了依赖注入,所以这个方法也会在后台被dagger hilt调用。
将ViewModel注入UI层
在使用dagger hilt创建viewmodel时,简单的直接创建是不会生效的,我们需要做以下两件事情:
- 添加注解@AndroidEntryPoint:当我们需要为安卓组件类(如activity、fragment、service等来自Android Framework的东西)注入依赖时,需要添加注解@AndroidEntryPoint;
- 创建application类:当repository需要获取context的时候,dagger hilt需要从application获取之,因为我们无法直接在module获取这个context。此时我们需要创建application并为其添加注解@HiltAndroidApp
@AndroidEntryPoint
class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {DaggerHiltCourseTheme {val viewModel = hiltViewModel<MyViewModel>()}}}
}
@HiltAndroidApp
class MyApp: Application()
对于后者dagger hilt是否能真正获取到这一context,我们可以修改MyRepositoryImpl这一实现类来打印context。
@Module
@InstallIn(SingletonComponent::class)
object AppModule {...@Provides@Singletonfun provideMyRepository(api: MyApi, app: Application): MyRepository {return MyRepositoryImpl(api, app)}
}class MyRepositoryImpl(private val api: MyApi,private val appContext: Application
) : MyRepository {init {val appName = appContext.getString(R.string.app_name)Log.i("MyRepositoryImpl", "Hello from the repository: this app name is $appName")}...}
可以看到我们正确的打印了AndroidManifest.xml中的正确默认名称,这也代表我们正确获取了app的context。
dagger hilt对于相同类型依赖项的区分
如果有两个相同类型的依赖项,dagger hilt是怎么区分我们需要哪一个的?
@Module
@InstallIn(SingletonComponent::class)
object AppModule {...@Provides@Singletonfun provideMyRepository(api: MyApi, app: Application, hello1: String): MyRepository {return MyRepositoryImpl(api, app)}@Provides@Singletonfun provideString1() = "Hello 1"@Provides@Singletonfun provideString2() = "Hello 2"
}
对AppModule修改如上,模拟两个获取字符串的方法,并在provideMyRepository中注入依赖。
此时直接运行会报错,提示该字符串被绑定多次。
对此情况我们可以添加注解@Named。
@Module
@InstallIn(SingletonComponent::class)
object AppModule {...@Provides@Singletonfun provideMyRepository(api: MyApi,app: Application,@Named("hello1") hello1: String): MyRepository {return MyRepositoryImpl(api, app)}@Provides@Singleton@Named("hello1")fun provideString1() = "Hello 1"@Provides@Singleton@Named("hello2")fun provideString2() = "Hello 2"
}
更为直接的方式:绑定抽象类
在AppModule中我们使用了MyRepositoryImpl类来提供MyRepository接口的实现,但实际上如果想要注入一个接口或抽象类,我们有更简便的方法。
首先去除AppModule中提供MyRepository的方法,并新建一个抽象Module用于直接提供MyRepository。这样做的好处在于dagger hilt会生成较少的代码而获得更佳的性能。
@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {// 这是提供依赖项的另一种方式,所以实际上不命名为provide,而是bind@Binds@Singletonabstract fun bindMyRepository(myRepositoryImpl: MyRepositoryImpl) : MyRepository
}
其次修改MyRepositoryImpl类,即MyRepository接口的实现类。为其添加注解@Inject和构造方法关键字。
只要dagger hilt知道如何创建构造函数中的这些依赖项,它也会自动地知道如何创建这个抽象类或结构的实现类。
class MyRepositoryImpl @Inject constructor(private val api: MyApi,private val appContext: Application
) : MyRepository {init {val appName = appContext.getString(R.string.app_name)Log.i("MyRepositoryImpl", "Hello from the repository: this app name is $appName")}override suspend fun doNetworkCall() {TODO("Not yet implemented")}}
因此,无论app中任何类型的类,只要它有一个包含了@Inject注解的构造方法,我们都不需要为它创建provide方法(例如我们去除掉的)。
因此,在抽象module中,我们传入抽象的bind方法中的依赖项,只是为了在每次注入一个抽象类时,dagger hilt可以确定是使用哪一个特定的实现类。
Service注入的方式——字段注入
通常情况下的注入是这样的。
@AndroidEntryPoint
class MyService @Inject constructor(private val myRepository: MyRepository
): Service() {override fun onBind(p0: Intent?): IBinder? {TODO("Not yet implemented")}}
在activity中,我们会为它写一个注入构造函数,在其中传入repository;但是对于Service来说,它不能拥有构造函数。那么我们要如何在活动中获取repository?
@AndroidEntryPoint
class MyService: Service() {@Injectlateinit var repository: MyRepositoryoverride fun onCreate() {super.onCreate() // 我们的repository注入实际上就发生在super.onCreate()中repository.doNetworkCall()}override fun onBind(p0: Intent?): IBinder? {TODO("Not yet implemented")}}
答案是使用字段注入。我们使用lateinit关键字定义它、并使用@Inject注解,此后在super.onCreate方法中,dagger hilt就会完成这个变量的依赖注入,无需我们手动初始化。
Lazy Injection
指的是延迟注入依赖项。
修改MyViewModel,使用dagger包下的lazy修饰依赖项。
通常情况下,依赖项会在我们注入时立刻构建,但在使用lazy修饰后,它会在我们第一次使用时创建。
修改后我们去运行代码,会发现 MyRepositoryImpl类的init代码块中的打印不出现了,这是因为我们没有使用它。我们可以简单地在MyViewModel的init代码块获取注入的依赖项repository,这样打印就会重新出现了。
@HiltViewModel
class MyViewModel @Inject constructor(private val repository: Lazy<MyRepository>
) : ViewModel() {init {repository.get() // 获取MyRepositoryImpl的实例化对象,此时被使用了}}
对于惰性注入的常见场景是身份验证,比如用户在输入用户名和密码之后,我们才开始启用repository的获取。
参考文献:The Ultimate Dagger-Hilt Guide (Dependency Injection) - Android Studio Tutorial
相关文章:
kotlin的dagger hilt依赖注入
依赖注入(dependency injection, di)是设计模式的一种,它的实际作用是给对象赋予实例变量。 基础认识 class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceSta…...
Flink类加载机制详解
1. 总览 在运行Flink应用时,它会加载各种类,另外我们用户代码也会引入依赖,由于他们依赖版本以及加载顺序等不同,就可能会导致冲突,所以很要必要了解Flink是如何加载类的。 根据加载的来源的不同,我们可以将类分为三种: Java Classpath:Java类路径下,这是Java通用的…...
在VSCode中设置bash命令行内容简写
在VSCode中设置bash命令行内容简写 1、打开shell配置文件2、在配置文件的末尾,添加以下行来创建别名3、重新加载配置文件以使更改生效4、在命令行使用缩写执行命令 比如,在VSCode的bash中输入 gc daily,而实际执行 git checkout daily. 1、…...
特制一个自己的UI库,只用CSS、图标、emoji图 第二版
图: 代码: index.html <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>M…...
51_Lua面向对象编程
面向对象编程(Object Oriented Programming,OOP)是一种非常流行的计算机编程架构。像C++、Java、Objective-C、Smalltalk、C#、Ruby等编程语言都支持面向对象编程。 1.面向对象编程特性 面向对象编程是一种编程范式,它使用“对象”来设计软件。对象是数据和行为的封装单元…...
Kafka——两种集群搭建详解 k8s
1、简介 Kafka是一个能够支持高并发以及流式消息处理的消息中间件,并且Kafka天生就是支持集群的,今天就主要来介绍一下如何搭建Kafka集群。 Kafka目前支持使用Zookeeper模式搭建集群以及KRaft模式(即无Zookeeper)模式这两种模式搭…...
OPT: Open Pre-trained Transformer语言模型
摘要 大规模语言模型通常需要数十万计算日的训练时间,展现了在零样本和小样本学习中的显著能力。鉴于其计算成本之高,这些模型在没有大量资本投入的情况下难以复现。对于那些通过API提供的少数模型,研究者无法获取完整的模型权重,…...
开源模型应用落地-LangChain高阶-记忆组件-RedisChatMessageHistory正确使用(八)
一、前言 LangChain 的记忆组件发挥着至关重要的作用,其旨在协助大语言模型(LLM)有效地留存历史对话信息。通过这一功能,使得大语言模型在对话过程中能够更出色地维持上下文的连贯性和一致性,进而能够像人类的记忆运作方式那样,进行更为自然、流畅且智能化的交互。 它仿佛…...
http和https有哪些不同
http和https有哪些不同 1.数据传输的安全性:http非加密,https加密 2.端口号:http默认80端口,https默认443端口 3.性能:http基于tcp三次握手建立连接,https在tcp三次握手后还有TLS协议的四次握手确认加密…...
UML系列之Rational Rose笔记七:状态图
一、新建状态图 依旧是新建statechart diagram; 二、工作台介绍 接着就是一个状态的开始:开始黑点依旧可以从左边进行拖动放置: 这就是状态的开始,和活动图泳道图是一样的;只能有一个开始,但是可以有多个…...
一个使用 Golang 编写的新一代网络爬虫框架,支持JS动态内容爬取
大家好,今天给大家分享一个由ProjectDiscovery组织开发的开源“下一代爬虫框架”Katana,旨在提供高效、灵活且功能丰富的网络爬取体验,适用于各种自动化管道和数据收集任务。 项目介绍 Katana 是 ProjectDiscovery 精心打造的命令行界面&…...
mycat介绍与操作步骤
文章目录 1.分库分表2.mycat 入门2.1 概述2.2 案例:水平分表1)准备工作2)配置3)启动并测试 3.mycat 配置详解3.1 schema.xml3.2 rule.xml3.3 server.xml 4.mycat 分片:垂直拆分1)准备工作2)配置…...
【Go】:图片上添加水印的全面指南——从基础到高级特性
前言 在数字内容日益重要的今天,保护版权和标识来源变得关键。为图片添加水印有助于声明所有权、提升品牌认知度,并防止未经授权的使用。本文将介绍如何用Go语言实现图片水印,包括静态图片和带旋转、倾斜效果的文字水印,帮助您有…...
R语言的语法糖
R语言的语法糖 引言 在编程语言中,所谓的“语法糖”是指那些使得程序员能够以更简洁、直观的方式书写代码的语法形式。R语言作为一种用于统计分析和数据可视化的编程语言,具有丰富的功能和灵活的语法。本文将深入探讨R语言中的语法糖,帮助读…...
乙游的尽头是虚拟偶像吗?
眼花了,竟然看到二次元乙游男主角走红毯了。 12月20日,某国际知名奢侈品品牌宣布,《恋与深空》四位男主将受邀出席品牌在上海举办的TF戏瘾之夜活动,并公开了四位男主的红毯照片。 没有真人实体的乙游男主走红毯?这是…...
【源码+文档+调试讲解】农产品研究报告管理系统
摘 要 农产品研究报告管理系统是一个旨在收集、整理、存储和分析农产品相关研究数据的综合性平台。农产品研究报告管理系统通常包含一个强大的数据库,它能够处理大量的研究数据,并对这些数据进行有效的管理和备份。农产品研究报告管理系统是现代农业科学…...
SQL UNION 操作符
SQL UNION 操作符 SQL UNION 操作符用于合并两个或多个 SELECT 语句的结果集。它将多个结果集组合成一个单独的结果集,并去除重复的行。为了使用 UNION,每个 SELECT 语句必须具有相同的列数,并且对应列的数据类型必须兼容。 语法 SELECT c…...
springboot vue uniapp 仿小红书 1:1 还原 (含源码演示)
线上预览: 移动端 http://8.146.211.120:8081/ 管理端 http://8.146.211.120:8088/ 小红书凭借优秀的产品体验 和超高人气 目前成为笔记类产品佼佼者 此项目将详细介绍如何使用Vue.js和Spring Boot 集合uniapp 开发一个仿小红书应用,凭借uniapp 可以在h5 小程序 app…...
扩散模型学习
扩散模型学习 DDPM(参考1) DDIM(参考1,参考2)...
【面试】MySQL 最左匹配原则
MySQL的最左匹配原则是在使用联合索引时非常重要的概念,理解并合理运用该原则能显著提升查询性能。以下从多个方面详细介绍: 1. 联合索引结构基础 联合索引是对多个列创建的索引。在MySQL中,联合索引以多列值的组合形式,按照创建…...
Mac 删除ABC 输入法
参考链接:百度安全验证 Mac下删除系统自带输入法ABC,正解!_mac删除abc输入法-CSDN博客 ABC 输入法和搜狗输入法等 英文有冲突~~ 切换后还会在英文状态,可以删除 ;可能会对DNS 输入有影响,但是可以通过复…...
Nginx代理同域名前后端分离项目的完整步骤
前后端分离项目,前后端共用一个域名。通过域名后的 url 前缀来区别前后端项目。 以 vue php 项目为例。直接上 server 模块的 nginx 配置。 server{ listen 80; #listen [::]:80 default_server ipv6onlyon; server_name demo.com;#二配置项目域名 index index.ht…...
21、Transformer Masked loss原理精讲及其PyTorch逐行实现
1. Transformer结构图 2. python import torch import torch.nn as nn import torch.nn.functional as Ftorch.set_printoptions(precision3, sci_modeFalse)if __name__ "__main__":run_code 0batch_size 2seq_length 3vocab_size 4logits torch.randn(batch…...
CNN张量输入形状和特征图
CNN张量输入形状和特征图 这个是比较容易理解的张量的解释,比较直观 卷积神经网络 在这个神经网络编程系列中,我们正在逐步构建一个卷积神经网络(CNN),所以让我们看看CNN的张量输入。 在最后两篇文章中&…...
RK3399开发板Linux实时性改造
本次测试基于NanoPC-T4开发板(国产化处理器RK3399),4.19.111内核Xenomai实时性改造测试。 Xenomai下载网站:https://xenomai.org/downloads/ NanoPC-T4网站:https://wiki.friendlyarm.com/wiki/index.php/NanoPC-T4/z…...
ASP.NET Core 中的高效后台任务管理
一、引言 在当今快速发展的 Web 开发领域,ASP.NET Core 凭借其卓越的性能、强大的功能和高度的灵活性,已然成为众多开发者构建现代 Web 应用程序的首选框架。它不仅能够高效地处理各种复杂的业务逻辑,还为开发者提供了丰富多样的工具和功能&…...
Spring Boot 2 学习指南与资料分享
Spring Boot 2 学习资料 Spring Boot 2 学习资料 Spring Boot 2 学习资料 在当今竞争激烈的 Java 后端开发领域,Spring Boot 2 凭借其卓越的特性,为开发者们开辟了一条高效、便捷的开发之路。如果你渴望深入学习 Spring Boot 2,以下这份精心…...
知识追踪模型DKT,DLKT详解及代码复现
定义与应用 知识追踪是一种 教育技术领域的重要方法 ,旨在通过分析学生的学习行为和表现,预测其掌握特定知识点的程度。这种方法的核心在于建立能够反映学习者认知状态的动态模型,从而实现对学生学习进度的实时监控和个性化指导。 DKT模型 DKT模型 是最早的知识追踪模型之…...
一类特殊积分的计算
一类特殊积分的计算 前言一、第一个引理二、第二个引理三、积分的计算后记 前言 今天讨论的这类积分是十分有趣的,在 Mathematics Stack Exchange 看见后,便打算在此将其中的计算过程完善一下。在本篇文章中,我们重点考虑求解如下积分&#…...
VScode 配置 C语言环境
遇到的问题集合 mingw官方下载网站(https://sourceforge.net/projects/mingw-w64/files/)更新之后,与网上大多数教程上写的界面不同了。 网上大多数教程让下载这个: 但是现在找不到这个文件。 写hello.c文件时,报错&…...
spring mvc源码学习笔记之十一
pom.xml 内容如下 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/P…...
小结:路由器和交换机的指令对比
路由器和交换机的指令有一定的相似性,但也有明显的区别。以下是两者指令的对比和主要差异: 相似之处 基本操作 两者都支持类似的基本管理命令,比如: 进入系统视图:system-view查看当前配置:display current…...
ffmpeg7.0 aac转pcm
#pragma once #define __STDC_CONSTANT_MACROS #define _CRT_SECURE_NO_WARNINGSextern "C" { #include "libavcodec/avcodec.h" }//缓冲区大小(缓存5帧数据) #define AUDIO_INBUF_SIZE 40960 /*name depthu8 8s16 …...
C#读取本地网络配置信息全攻略
一、引言 在当今数字化时代,网络已深度融入我们生活与工作的方方面面。对于软件开发而言,掌握本地计算机的网络配置信息显得尤为关键。想象一下,你正在开发一款网络诊断工具,需要精准定位网络连接问题,此时 IP 地址、…...
解决aerich init -t xx 报错ModuleNotFoundError: No module named ‘tomli_w‘
今天在学习fastapi的时候,发现一款数据库迁移工具,通过这个工具可以根据模型类来对数据库做出改变。 随跟着学: 在执行 aerich init -t settings.TORTOISE_ORM的时候, 彼其娘之。。 报了一些错误: Traceback (most recent ca…...
python检测gitlab中某个标签在一个月内添加和移除了多少次
可以通过 Python 脚本和 GitLab API 检测一个标签在一个月内被添加和移除的次数。以下是实现的步骤和示例代码: 步骤 获取 GitLab API 访问令牌:在 GitLab 中生成一个 Personal Access Token。设置时间范围:确定一个月的时间范围。调用 Git…...
学习模板之一
学习目标: 提示:这里可以添加学习目标 例如: 一周掌握 Java 入门知识 学习内容: 提示:这里可以添加要学的内容 例如: 搭建 Java 开发环境掌握 Java 基本语法掌握条件语句掌握循环语句 学习时间&#x…...
2025-微服务—SpringCloud-1~3
2025-微服务—SpringCloud 第一章、从Boot和Cloud版本选型开始说起1、Springboot版本2、Springcloud版本3、Springcloud Alibaba4、本次讲解定稿版 第二章 关于Cloud各种组件的停更/升级/替换1、微服务介绍2、SpringCloud是什么?能干吗?产生背景…...
centos7.6 安装nginx 1.21.3与配置ssl
1 安装依赖 yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel2 下载Nginx wget http://nginx.org/download/nginx-1.21.3.tar.gz3 安装目录 mkdir -p /data/apps/nginx4 安装 4.1 创建用户 创建用户nginx使用的nginx用户。 #添加www组 # groupa…...
PL/SQL语言的数据库交互
PL/SQL语言的数据库交互 引言 在当今的信息化时代,数据库管理系统(DBMS)在各行各业中扮演着至关重要的角色。为了高效地与数据库进行交互,许多程序员、数据库管理员和系统分析师选择使用PL/SQL(Procedural Language/…...
ARP欺骗
文章目录 ARP协议ARP欺骗原理断网攻击ARP欺骗(不断网) ARP协议 在局域网中,网络传输的是帧,帧里面有目标主机的MAC地址。arp协议负责将IP地址解析成对应的MAC地址 ARP欺骗原理 即通过伪造IP地址和MAC地址实现ARP欺骗,能够在网络中产生大量…...
Genymotion配套VirtualBox所在地址
在 Genymotion打开虚拟机前需要先打开VirtualBox中的虚拟机 C:\Program Files\Oracle\VirtualBox\VirtualBox.exe 再开启genymotion中的虚拟机开关...
TPS61022 PFM的机制以及TPS61xxx转换器的PFM与PWM之间的负载阈值
引言 TI 的大多数 TPS61xxx 低压升压转换器都配备了 PSM(省电模式),以帮助提高轻负载效率。但是,当它处于重负载状态时,输出纹波通常会高于 PWM。此外,PSM 和 PWM 之间的负载电流阈值不会直观地写入数据表中…...
1. npm 常用命令详解
npm 常用命令详解 npm(Node Package Manager)是 Node.js 的包管理工具,用于安装和管理 Node.js 应用中的依赖库。下面是 npm 的一些常用命令及其详细解释和示例代码。 镜像源 # 查询当前使用的镜像源 npm get registry# 设置为淘宝镜像源 …...
黑马linux入门笔记(01)初始Linux Linux基础命令 用户和权限 实用操作
B站 黑马程序员 的视频 BV1n84y1i7td 黑马程序员新版Linux零基础快速入门到精通,全涵盖linux系统知识、常用软件环境部署、Shell脚本、云平台实践、大数据集群项目实战等 增强自控力 冥想慢呼吸绿色锻炼充分休息减少决策次数优先做重要的事情(早晨)融入强自控群控…...
Markdown中甘特图的使用
Markdown中甘特图的使用 1. 前言2. 语法详解2.1 甘特图语法 3. 使用场景及实例4. 小结5. 其他文章快来试试吧🖊️ Markdown中甘特图的使用 👈点击这里也可查看 1. 前言 Markdown 的原生语法不支持绘制图形,但通过扩展模块,我们可…...
Django创建数据表、模型、ORM操作
1、创建项目 django-admin startproject PersonInfosProject 2、创建项目应用,进入PersonInfosProject文件夹,新建index应用,使用命令 cd PersonInfosProject python manage.py startapp 新建完成之后的目录结构 3、新建数据模型…...
No. 31 笔记 | Web安全-SQL手工注入技术学习 Part 2
一、研究背景 背景介绍 SQL注入是一种常见且高危的Web安全漏洞。攻击者可以通过构造恶意SQL查询语句来绕过验证机制,执行未授权操作,如获取敏感信息、篡改数据库内容甚至控制服务器。 研究内容 本笔记探讨以下数据库的手工注入技术: MySQLAc…...
Spring Boot中的扫描注解如何使用
在 Spring Boot 中,扫描注解是指通过注解来告诉 Spring 框架应该扫描哪些包、哪些类或哪些特定的组件,并将其作为 Spring 容器中的 bean 进行管理。Spring Boot 主要通过以下几种注解来实现自动扫描: ComponentScanSpringBootApplicationCom…...
用 Python 从零开始创建神经网络(十九):真实数据集
真实数据集 引言数据准备数据加载数据预处理数据洗牌批次(Batches)训练(Training)到目前为止的全部代码: 引言 在实践中,深度学习通常涉及庞大的数据集(通常以TB甚至更多为单位)&am…...