網(wǎng)站首頁 編程語言 正文
移動(dòng)端開發(fā)之Jetpack?Hilt技術(shù)實(shí)現(xiàn)解耦_Android
作者:守住Android最后的光 ? 更新時(shí)間: 2023-05-16 編程語言Hilt是什么
Hilt 是基于 Dagger2 的針對 Android場景定制化 的框架。
這有點(diǎn)像什么? RxAndroid 是 RxJava 的Android平臺定制化擴(kuò)展。Andorid雖然由Java、Kotlin構(gòu)成,但是它有很多平臺的特性,比如它有 Java開發(fā) 所不知道的 Context 等。
Dagger框架雖然很出名,在國外也很流行,但是在國內(nèi)使用其的App少之又少,列舉一些缺點(diǎn):
- 上手難,眾多Android應(yīng)用框架中,Dagger必定是最難學(xué)的那一檔;
- 它就是一個(gè)復(fù)雜框架,沒有針對任何平臺,所以對于所有平臺來說會(huì)難用;
- 在Android Studio4.0版本以前,無法追蹤Dagger的依賴關(guān)系(就類比IDE無法通過快捷鍵知道一個(gè)接口有哪些實(shí)現(xiàn)類) 開發(fā)者不知道為啥要做依賴注入
- 對于第三點(diǎn),Android Studio4.1已經(jīng)支持了該功能,但是4.1有許多Bug,很多開發(fā)者都沒有升級 。
Hilt的出現(xiàn)解決前兩點(diǎn)問題,因?yàn)?Hilt 是 Dagger 針對Android平臺的場景化框架,比如Dagger需要我們手動(dòng)聲明注入的地方,而Android聲明的地方不都在 onCreate()嗎,所以Hilt就幫我們做了,除此之外還做了很多事情,這樣一來,相較于Dagger,我們能用更少代碼、更容易、更輕松的配置依賴注入。
Hilt使用地方
Google認(rèn)為移動(dòng)端應(yīng)用的架構(gòu)設(shè)計(jì),最重要的 Separation of concerns(分離關(guān)注點(diǎn))。上網(wǎng)找解釋,其實(shí)它就是 模塊解耦。下面是Google官方推薦的Android應(yīng)用架構(gòu)圖:
依賴注入(DI)概念
而 Hilt 是被定義為 依賴注入框架而被發(fā)布。什么?又是依賴注入框架?不是之前已經(jīng)有了一個(gè) Dagger2 了嗎?除了 Dagger2, 還有 ButterKnife ,Kotlin甚至還有輕量級的 Koin。
什么是依賴注入?先來看下面代碼:
class MyClass {
val user = User()
}
我們在一個(gè)類 MyClass 中 聲明了一個(gè)變量 user,并初始化-------調(diào)用 User 構(gòu)造函數(shù),創(chuàng)建一個(gè)對象。
上面這段代碼就產(chǎn)生了一個(gè)依賴關(guān)系。
我們要先看懂誰依賴了誰?首先 MyClass 是我們的類,User 可以是我們自己寫的類,也可以是通過第三方Jar包或SDK里面的類。 在我們寫的 MyClass 的代碼里面,我們需要一個(gè) User 對象來完成一些任務(wù),所以我們創(chuàng)建了 User 對象,這就說明 MyClass 依賴了 User。對于 MyClass來說,User是外面之物,但是又需要依賴它。
如果上面這個(gè) User,不是由自己創(chuàng)建,而是由外部創(chuàng)建,然后在本類只做賦值工作 ,這個(gè)過程就是 依賴注入。
有一個(gè)我們非常熟悉的設(shè)計(jì)模式,就使用了依賴注入的方法—工廠模式:
class UserFactory {
fun newUser(): User{
return User()
}
}
class MyClass {
val user = UserFactory.newUser()
}
我們的 MyClass 類需要使用 User 類,但是這次沒有自己來創(chuàng)建(沒有自己new出來),而是交由給 UserFactory 來創(chuàng)建出來,MyClass就做了最后的賦值工作。
對于 MyClass 來說,這就是一次依賴注入,和上面例子相比,把對象創(chuàng)建的過程交由給了別的類。
所以我們通過上面兩個(gè)例子就能知道依賴注入的本質(zhì)是什么:借由外部得到對象。依賴注入框架就是這個(gè)外部
現(xiàn)在流行的 Dagger2、Koin框架,只是讓我們更輕松、更容易的去得到對象。
Dagger的中文翻譯是 “刀”,它就像一把刀,插進(jìn)我們代碼中。那相信你也知道 ButterKnife 為什么這么取名了吧。
Hilt使用
導(dǎo)入
在 app 的 build.gradle 中加入:
plugins {
? ? ...
? ? id 'kotlin-kapt'
? ? id 'dagger.hilt.android.plugin'
}
dependencies {
? ? ...
? ? implementation 'com.google.dagger:hilt-android:2.28-alpha'
? ? kapt 'com.google.dagger:hilt-android-compiler:2.28-alpha'
}
在 project的 build.gradle 中加入:
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
一個(gè)簡單的例子 Hilt 需要 AndroidManifest 使用帶有 @HiltAndroidApp 注解的 Application 類,所以我們的 Application需要這樣:
@HiltAndroidApp
class HiltApp : Application() {
...
}
然后在 AndroidManifest 文件中聲明:
<application android:name=".HiltApp" ... </application>
假如我們要在 MainActivity 中注入一個(gè) User 對象, 我們首先編寫一個(gè) User 類,User類有兩個(gè)屬性 name 和 age 。 誒,這個(gè)時(shí)候有同學(xué)就會(huì)問了:我通過 @Inject 聲明一個(gè)User,Hilt就能給我創(chuàng)建一個(gè) User 對象,那這個(gè)User對象里面的name和age是啥?答案是:編譯會(huì)報(bào)錯(cuò),因?yàn)槲覀冏约憾疾恢肋@兩個(gè)參數(shù)是啥,Hilt怎么可能會(huì)知道,所以這兩個(gè)屬性也要通過依賴注入的方式來注入。
為了簡化這個(gè)問題,我定義一個(gè)默認(rèn)的無參構(gòu)造函數(shù),反正創(chuàng)建之后,里面的值也是可以修改的嘛。
data class User(var name: String, var age: Int) {
// 定義一個(gè)默認(rèn)的無參構(gòu)造函數(shù),并使用 @Inject 注解修飾
@Inject
constructor() : this("Rikka", 23)
}
接著我們在 MainActivity 中聲明一個(gè) User,通過 @Inject 來修飾,并且MainActivity 需要通過 @AndroidEntryPoint 修飾:
// 1
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
// 2
@Inject
lateinit var user: User
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d(TAG, "user name:${user.name} age:${user.age}")
}
}
Logcat 打印結(jié)果如下:
代碼解析:
注釋1: 為 MainActivity 修飾 @AndroidEntryPoint,該注解表明 該類為需要進(jìn)行依賴注入的 Android類,是Dagger針對Android場景化的地方。當(dāng)我們類中需要進(jìn)行依賴注入,我們?yōu)樵擃惣尤脒@個(gè)注解,它會(huì)幫助創(chuàng)建一個(gè)單獨(dú)的 Hilt組件。它不能修飾Abstract,它只能修飾:
- ComponentActivity
- (Support)Fragment
- View
- Service
- BroadcastReceiver
注釋2:我們需要注入一個(gè) User,所以我們給它加一個(gè) @Inject 注解,告訴 Hilt。因?yàn)镵otlin的語法問題,這里不得不聲明 lateinit(而這里Koin的寫法更加優(yōu)雅),接下來步驟大概是這樣的:
- Hilt 會(huì)去找User 這個(gè)類的構(gòu)造函數(shù),以此來創(chuàng)建一個(gè)對象
- Hilt 發(fā)現(xiàn) 有兩個(gè)個(gè)構(gòu)造函數(shù),而無參構(gòu)造函數(shù)被@Inject 聲明
- Hilt 會(huì)去調(diào)用被@Inject 的構(gòu)造函數(shù),創(chuàng)建一個(gè)User(“Rikka”, 23) 對象
- 返回這個(gè)對象, MainActivity 實(shí)現(xiàn)外部幫忙創(chuàng)建 User對象,實(shí)現(xiàn) User 的依賴注入。
Inject 的中文翻譯是 “注入、注射”,所以可以形象的認(rèn)為, @Inject 修飾的變量是被外界通過針筒注入進(jìn)來的。
- @Inject 可以修飾
- 構(gòu)造函數(shù) Constructors
- 變量 Fields
- 方法 Methods
構(gòu)造函數(shù)是最先被注解的,然后再是變量和方法。所以它修飾構(gòu)造函數(shù)和修飾變量,其實(shí)是不同的作用。但為了便于理解,我們可以把它看成是一個(gè)插眼工具,便于Hilt去尋找要注入的地方。
我們上面的 User 類是無參構(gòu)造函數(shù),這次假設(shè)我們要有參數(shù)的呢?其實(shí)就是參數(shù)也要注入嘛,這就是套娃來的,來看看我們給User新增一個(gè) 屬性:Clothes:
class Clothes @Inject constructor() {
}
class User @Inject constructor(var clothes: Clothes){
}
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
...
Log.d(TAG, "user clothes:${user.clothes}")
}
打印結(jié)果:
PS:大家不要太拘泥于有參構(gòu)造函數(shù)的創(chuàng)建,我認(rèn)為注入的作用是創(chuàng)建出一個(gè)對象,這個(gè)對象里面的內(nèi)容可以后續(xù)再傳入,它更多的體現(xiàn)、或者我們需要注意的是 “分離關(guān)注點(diǎn)”
實(shí)現(xiàn)接口實(shí)例注入
因?yàn)榻涌跊]有構(gòu)造函數(shù),所以當(dāng)我們想要依賴一些接口時(shí),該怎么辦。
我們來下面的示例,我們寫一個(gè) Profession 接口,代表職業(yè):
interface Profession {
fun doJob()
}
假設(shè)我們有兩個(gè)實(shí)現(xiàn)接口的類:醫(yī)生類和程序猿類:
class Doctor : Profession{
override fun doJob() {
Log.d("Doctor", "doctor do job")
}
}
class Programmer : Profession{
override fun doJob() {
Log.d("Programmer", "programmer do job")
}
}
這個(gè)時(shí)候我給 User 類添加一個(gè)職業(yè)的屬性,并希望它能夠自動(dòng)注入:
class User @Inject constructor(var clothes: Clothes){
@Inject
lateinit var profession: Profession
}
因?yàn)?Profession 是一個(gè)接口,它有兩個(gè)實(shí)現(xiàn)類,所以這樣 Hilt 并不能知道我們要實(shí)例化哪個(gè)具體的實(shí)現(xiàn)類,所以編譯的時(shí)候就會(huì)報(bào)錯(cuò)。
而 Hilt 也解決這種問題,首先我們要在每個(gè)實(shí)現(xiàn)類上注入構(gòu)造函數(shù):
class Doctor @Inject constructor() : Profession{
...
}
class Programmer @Inject constructor() : Profession{
...
}
接著我們需要實(shí)現(xiàn)一個(gè)和該接口有關(guān)的 XXXModule類,它被 @Module 修飾,這個(gè)和 Dagger 中的一樣,代表它會(huì)為接口提供一個(gè)創(chuàng)建實(shí)例的工廠,同時(shí)需要加上 @InstallIn 注解,用來聲明它是被安裝到哪個(gè)組件中,該注解后面會(huì)說到。 代碼如下:
@Module
@InstallIn(ActivityComponent::class)
abstract class ProfessionModule { // 1
// 2
@Binds
abstract fun bindDoctor(doctor: Doctor): Profession
}
注釋1: 我們寫出來的類是一個(gè)抽象類,因?yàn)槲覀儾恍枰唧w的實(shí)現(xiàn)它,而且它沒有具體的命名規(guī)則,因?yàn)槲覀円膊粫?huì)在代碼中直接調(diào)用它,但是為了便于理解,我們起名一般叫 接口名 + Module。
注釋2: 我們假設(shè)該Module提供一個(gè) Doctor 的職業(yè),那我們需要定義一個(gè) 抽象方法 來獲取一個(gè)Doctor類。 并且 該方法需要被 @Binds 注解修飾。這樣就能被 Hilt 識別。
這樣一來,我們就實(shí)現(xiàn)了接口的一個(gè)實(shí)例化的注入,我們來實(shí)驗(yàn)一下,在 User 中去展示它:
class User @Inject constructor(var clothes: Clothes){
@Inject
lateinit var profession: Profession
fun showMyself() {
profession.doJob()
}
}
// MainActivity
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
lateinit var user: User
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
user.showMyself()
}
}
打印結(jié)果為:
可以看到我們的 Doctor 成功的注入了。
OK,我們了解了接口某一個(gè)實(shí)現(xiàn)類的注入 (Doctor),那假設(shè)這個(gè)時(shí)候,另外一個(gè)類需要注入接口的另一個(gè)實(shí)現(xiàn)類 Programmer,那我們是不是也得按照同樣的做法,在 Module類中添加呢?
@Module
@InstallIn(ActivityComponent::class)
abstract class ProfessionModule {
@Binds
abstract fun bindDoctor(doctor: Doctor): Profession
@Binds
abstract fun bindProgrammer(programmer: Programmer): Profession
}
這個(gè)時(shí)候發(fā)現(xiàn)運(yùn)行,編譯也會(huì)報(bào)錯(cuò):
提示我們被綁定多次了。
這是因?yàn)?Doctor 和 Programmer 都是相同類型,當(dāng)他們一起被 Binds 注解,那 Hilt 不知道要去綁定哪一個(gè)。
這個(gè)時(shí)候就需要使用 @Qualifier 注解來幫助我們了,它就是為了這種 相同類型 依賴注入而產(chǎn)生的:
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BindDoctor
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class
我們創(chuàng)建了新的注解 BindDoctor 和 BindProgrammer,他們都被 @Qualifier 修飾,表示他們用來作用在同種類型上, @Retention 選擇使用 BINARY類型,表明該注解保留到編譯后,但無法通過反射來得到,是比較適合的注解。
接下來,我們要將這些注解作用在被 Binds 注解的抽象方法上:
@Module
@InstallIn(ActivityComponent::class)
abstract class ProfessionModule {
@BindDoctor
@Binds
abstract fun bindDoctor(doctor: Doctor): Profession
@BindProgrammer
@Binds
abstract fun bindProgrammer(programmer: Programmer): Profession
}
最后,在 User 中聲明使用哪一個(gè)類型的注入:
class User @Inject constructor(var clothes: Clothes){
@BindProgrammer // 這次注入一個(gè) Programmer
@Inject
lateinit var profession: Profession
fun showMyself() {
profession.doJob()
}
}
打印結(jié)果如下所示:
這下我們就實(shí)現(xiàn)了具體某個(gè)實(shí)例的注入啦。
實(shí)現(xiàn)第三方依賴注入
假設(shè)一些類不是由我們自己寫的,而是由第三方庫導(dǎo)入的。比如 OkHttp ,我們在使用網(wǎng)絡(luò)請求的時(shí)候,需要使用它,為了分離關(guān)注點(diǎn),我們需要對他進(jìn)行依賴注入。
但是 OkHttp 是我們不能修改的類,所以我們不能在它的構(gòu)造函數(shù)上加入 @Inject, 這個(gè)時(shí)候該怎么辦呢?
Dagger 中也有類似的場景,我們需要 @Providers 來幫助我們。除此之外,我們也需要 @Module 注解來聲明一個(gè) Module 類, 基于上面的例子,我們可以把這種 Module 類看成是一個(gè)工廠:
@Module
@InstallIn(ApplicationComponent::class)
class NetModule { // 1
// 2
@Provides
fun provideOkHttpClient(): OkHttpClient {
// 3
return OkHttpClient.Builder().build()
}
}
注釋1: 聲明一個(gè) NetModule,提供網(wǎng)絡(luò)庫相關(guān)的組件,并沒有和上面例子一樣聲明為抽象函數(shù),這是因?yàn)槔锩娴亩加芯唧w的實(shí)現(xiàn)方法。
注釋2: 編寫一個(gè) provideOkHttpClient() 方法,返回一個(gè) OkHttpClient對象。 聲明一個(gè) @Providers 注解,表示這個(gè)提供的依賴對象,是第三方的類或者系統(tǒng)類,我們因?yàn)椴荒苤苯痈钠錁?gòu)造函數(shù),所以得加上這個(gè)注解。
注釋3:new 一個(gè)對象,并返回。
這樣,我們就能在我們代碼中直接使用了:
@Inject
lateinit var okHttpClient:
@Providers 的本質(zhì)是什么? 第三方類因?yàn)槠渲蛔x性,Hilt不能找到其構(gòu)造函數(shù),所以需要我們自己手動(dòng)的創(chuàng)建,創(chuàng)建的方法被 @Providers 修飾, Hilt 找到這個(gè)方法,并提供由我們手動(dòng)創(chuàng)建的對象。
所以 @Providers 的本質(zhì),是由我們自己創(chuàng)建對象, Hilt 幫我們注入。
現(xiàn)在大家都不會(huì)直接使用 OkHttp,而是使用 Retrofit,所以我們來提供一個(gè) Retrofit 把:
...
@Provides
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build()
}
因?yàn)?Retrofit的創(chuàng)建需要依賴一個(gè) OkHttpClient 對象,所以我們需要?jiǎng)?chuàng)建一個(gè),但是我們也可以注入一個(gè),因?yàn)槲覀冎耙呀?jīng)有 provideOkHttpClient,所以它就能提供一個(gè)實(shí)例,我們不用在擔(dān)心什么了。
Hilt 的內(nèi)置組件和作用域
@InstallIn 注解
我們之前看到了 @InstallIn 這個(gè)注解,它的作用是用來表明 Module 作用的地方,它的參數(shù)時(shí) xxxComponent格式,前面xx代表作用域。
因?yàn)?Hilt 是Dagger的Android場景化,所以它能作用的地方和我們Android息息相關(guān),有下面幾處:
- Application ->ApplicationComponent
- ViewModel ->ActivityRetainedComponent
- Activity ->ActivityComponent
- Fragment ->FragmentComponent
- View ->ViewComponent
- Service ->ServiceComponent
- View Annotation with@WithFragmentBindings ->ViewWithFragemntComponent
除了最后一個(gè),別的作用域還是挺常見的。他們都要通過 @InstallIn 注入。
比如我們ProfessionModule是聲明成 @InstallIn(ActivityComponent::class),這就說明只有在 Activity 的代碼中可以使用它,而在 Service 中是不能使用的。 而 NetModule 則是聲明成 InstallIn(ApplicationComponent::class)的,這說明在全局都可以使用它提供的 OkHttpClient 和 Retrofit 對象。
使注入對象單例
像 Retrofit 、 OkHttpClient 這樣的全局都需要使用到的對象,我們希望它的作用域是全局,并且單例的。
但是 Hilt 提供的 @Inject 對象并不是單例的,每次 注入時(shí)都會(huì)重新生成一個(gè)新的實(shí)例,這就說明,假設(shè)我們要使用 Retrofit 來做網(wǎng)絡(luò)請求, @Providers 每次提供的都是不一樣的,這樣對性能來說很不友好,而且不符合常規(guī)的邏輯設(shè)定。
按照以往,我們可以通過給 NetModule 聲明一個(gè) @Singleton 注解,來讓這個(gè)類實(shí)現(xiàn)單例,來解決這個(gè)問題。
Hilt 也有自己的解決方案,那就是使用 @xxxScope 注解,它和上面的 xxxComponent所對應(yīng),表示 在這個(gè)作用域內(nèi)單例,來看看對應(yīng)關(guān)系:
- Application ->ApplicationComponent ->@Singleton
- ViewModel ->ActivityRetainedComponent ->@ActivityRetainedScoped
- Activity ->ActivityComponent ->@ActivityScoped
- Fragment ->FragmentComponent ->@FragmentScoped
- View ->ViewComponent ->@ViewScoped
- Service ->ServiceComponent ->@ServiceScoped
- View Annotation with@WithFragmentBindings ->ViewWithFragemntComponent ->@ViewScoped
使用 @xxScoped 來替代 @InstallIn(xxxComponent::class) 聲明組件為單例。
因?yàn)?Application 是作用于全局,所以它的注解是 @Singleton,比較好理解。
作用域的包含關(guān)系
作用域也有自己的包含關(guān)系,比如被 @ActivityScoped聲明的組件,可以在 Fragment 或者 View 中使用,他們的具體包含關(guān)系如下圖所示:
Hilt 預(yù)置的 Qualifier
我介紹過 Hilt 是 Dagger針對Android的場景化,所以它低層做了很多事情,使得在Android上更好的使用。除了上面介紹過的那些注解外,還有很多別的東西,可以讓我們?nèi)ヌ剿鳎瑫r(shí)也了解了Dagger本身。
Context 上下文是Android 獨(dú)特的存在,它代表著 Application、Activity、Service的一些fwk層的東西。
而我們的代碼中經(jīng)常會(huì)需要 Context 來創(chuàng)建一些東西:
class A @Inject constructor(context: Context) {
...
}
但是我們知道,它是系統(tǒng)類,我們無法注入 Context。那我們可以通過使用 @Providers 來創(chuàng)建嗎?
@Providers
fun provideContext() {
???
}
很明顯,Context 是由AMS來創(chuàng)建的,我們無法直接創(chuàng)建一個(gè)上下文出來。這個(gè)問題該如何解決呢?
答案是:我們不用解決,Hilt 為我們提供了它自己預(yù)置的注解 @ApplicationContext 和 @ActivityContext,我們直接使用,Hilt會(huì)幫我們注入上下文。
class A @Inject constructor(@ApplicaitonContext context: Context)
而現(xiàn)在沒有 ServiceContext,可能是用的比較少吧?
@ApplicationContext 提供的類型是 Application, 而不是我們自己的 App 自定義的 Application,加入我們要使用自己的該怎么辦呢?答案是也很簡單:
@Providers
fun provideApplicaiton(application: Application): MyApplication {
return applicaiton as MyApplication
}
直接在 Module 中提供一個(gè),并強(qiáng)轉(zhuǎn)就 OK啦。
注意
ApplicationContext 的作用域是全局, 所以它修飾的類的作用只能是 @InstallIn(Applicaiton) 或 @Singleton,其他的也同理。
之前沒有用過 Dagger,因?yàn)轫?xiàng)目不需要,且難學(xué),問題多。 Hilt 出來之后解決了大部分的痛點(diǎn),再不上車屬實(shí)就有點(diǎn)說不過去了。
Hilt 相較與 Dagger,肯定是更好用,更適合Android來使用。 它和 Koin的比較,只是性能上的差異,網(wǎng)上大部分的文章都認(rèn)為 Hilt 性能更優(yōu),但是代碼量更多,在大的項(xiàng)目使用 Hilt 會(huì)更好,而小的項(xiàng)目兩者差別不會(huì)太大。具體還請開發(fā)者自己研究。
Hilt 作用是 提供依賴注入,幫助程序分離關(guān)注點(diǎn),幫助搭建低耦合高內(nèi)聚的框架,學(xué)習(xí)它,有利于我們學(xué)習(xí) Android應(yīng)用架構(gòu) 方面的技能。
原文鏈接:https://blog.csdn.net/Androidxiaofei/article/details/129028075
相關(guān)推薦
- 2024-03-05 git的使用
- 2023-07-16 uniapp 微信小程序獲取當(dāng)前位置的坐標(biāo)
- 2022-09-08 python如何將mat文件轉(zhuǎn)為png_python
- 2022-02-17 uni-app的 tabBar添加陰影
- 2023-12-26 Description:Web server failed to start. Port 8080
- 2022-07-22 CSS3:盒陰影、邊界圖片、指定每一個(gè)圓角、背景、過度、動(dòng)畫、
- 2022-03-13 C語言實(shí)現(xiàn)求最大公約數(shù)的三種方法_C 語言
- 2023-04-06 C#中的那些警告該如何去除(完全去除C#警告)_C#教程
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支