網站首頁 編程語言 正文
一、ViewModel
ViewModel 類旨在以注重生命周期的方式存儲和管理界面相關的數據。
ViewModel 類讓數據可在發生屏幕旋轉等配置更改后繼續留存。
簡單的說就是,在android中,當Activity重建或銷毀時,頁面上的數據會丟失。為了保存頁面的數據,我們以前通常的做法是在 onSaveInstanceState 中,將數據保存到 bundle 中,再在 onCreate 中將 bundle 中的數據取出來。
而使用 ViewModel,我們就無需再用這種方法保存,因為 ViewModel 會自動感知生命周期,處理數據的保存與恢復。即數據可在發生屏幕旋轉等配置(其它例如分辨率調整、權限變更、系統字體樣式、語言變更等)更改后繼續留存。
對于橫豎屏生命周期的總結是:先銷毀掉原來的生命周期,然后再重新跑一次。
但是,這樣子是不是會有問題呢?有些場景下: 比如說,做游戲開發 。橫豎屏的切換,生命周期重新加載,那么當前頁面的數據也會重新開始了。但是ViewModel會保存里面的數據。
在切換語言的時候ViewModel也會保存數據
Activity等視圖文件中不保存數據,在ViewModel里面保存數據
當Activity或fragment被Destory或onCreate時ViewModel數據不會丟失
ViewModel基本用法
想要使用ViewModel組件,還需要添加如下依賴:
dependencies {
? ? //ViewModel是LifeCycle的一個組件庫,所以只需要添加LifeCycle庫的依賴即可
? ? implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
}
通常來講,我們需要給每個Activity和Fragment都創建一個對應的ViewModel,因此為MainActivity創建一個對應的MainViewModel類,并讓他繼承自ViewModel,代碼如下所示:
class MainViewModel :ViewModel(){
var counter=0
}
現在我們在界面上添加一個按鈕,每點擊一次按鈕就讓計數器加1,并且把最新的計數顯示到界面上。修改布局代碼:
package com.example.JetPackTest
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import com.example.kotlintext.R
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
lateinit var viewModel: MainViewModel
private val TAG:String="MainActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d(TAG, "onCreate: ")
//viewModel= ViewModelProviders.of(this).get(MainViewModel::class.java)
viewModel=ViewModelProvider(this,ViewModelProvider.NewInstanceFactory()).get(MainViewModel::class.java)
plusOneBtn.setOnClickListener {
viewModel.counter++;
refreshCounter()
}
refreshCounter()
}
private fun refreshCounter() {
infoText.text=viewModel.counter.toString()
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "onDestroy: ")
}
override fun onStart() {
super.onStart()
Log.d(TAG, "onStart: ")
}
override fun onStop() {
super.onStop()
Log.d(TAG, "onStop: ")
}
override fun onRestart() {
super.onRestart()
Log.d(TAG, "onRestart: ")
}
override fun onPause() {
super.onPause()
Log.d(TAG, "onPause: ")
}
}
首先我們要通過ViewModelProvider來創建ViewModel的實例,之所以這么寫是ViewModel有獨立的生命周期,并且其生命周期要長于Activity。如果我們在onCreate()方法中創建ViewModel的實例,那么每次onCreate()方法執行時候,ViewModel都會創建一個新的實例,這樣當手機屏幕發生變化時候,就無法保留其中的數據了。
當我們旋轉一下屏幕,你會發現Activity雖然重新被創建了,但計數器的數據沒有丟失
向ViewModel傳遞參數
如果我們需要通過構造函數來傳遞一些參數,需要借助ViewModelProvider.Factory就可以實現。雖然計數器在屏幕旋轉的時候不會丟失數據,但是如果退出程序之后又重新打開,那么之前的計數就會被清零。這個時候我們就需要在退出程序的時候對當前的計數進行保存,然后在重新打開程序的時候讀取之前保存的計數,并傳遞給MainViewModel。修改MainViewModel中的代碼,如下所示
class MainViewModel(countReserved:Int) :ViewModel(){
var counter=countReserved
}
我們通過給MainViewModel的構造函數添加了一個countReserved參數,這個參數用于記錄之前保存的計數值,并在初始化的時候賦值給counter變量。
新建一個MainViewModelFactory類,并讓它實現ViewModelProvider.Factory接口,代碼如下:
class MainViewModelFactory(private val countReserved:Int):ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return MainViewModel(countReserved)as T
}
}
可以看到MainViewModel.Factory的構造函數中也接收了一個countReserved參數,另外ViewModelProvider.Factory接口要求我們必須實現create()方法,因此這里在create方法中我們創建了MainViewModel的實例,并將countReserved參數傳了進去。為什么這里就可以創建MainViewModel的實例了呢?因為create()方法的執行時機和Activity的生命周期無關,所以不會產生之前提到的問題。
另外,我們在界面上添加一個清零按鈕,方便用戶手動將計數器清零。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/infoText" android:layout_gravity="center_horizontal" android:textSize="32sp" /> <Button android:id="@+id/plusOneBtn" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:text="Plus One" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/clearBtn" android:layout_gravity="center_horizontal" android:text="clear" /> </LinearLayout>
最后修改MainActivity中的代碼
package com.example.JetPackTest
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.edit
import androidx.lifecycle.ViewModelProvider
import com.example.kotlintext.R
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
lateinit var viewModel: MainViewModel
lateinit var sp:SharedPreferences
private val TAG:String="MainActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d(TAG, "onCreate: ")
sp=getPreferences(Context.MODE_PRIVATE)
val countReserved = sp.getInt("count_reserved", 0)
viewModel=ViewModelProvider(this,MainViewModelFactory(countReserved)).get(MainViewModel::class.java)
plusOneBtn.setOnClickListener {
viewModel.counter++;
refreshCounter()
}
clearBtn.setOnClickListener {
viewModel.counter=0
refreshCounter()
}
refreshCounter()
}
private fun refreshCounter() {
infoText.text=viewModel.counter.toString()
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "onDestroy: ")
}
override fun onStart() {
super.onStart()
Log.d(TAG, "onStart: ")
}
override fun onStop() {
super.onStop()
Log.d(TAG, "onStop: ")
}
override fun onRestart() {
super.onRestart()
Log.d(TAG, "onRestart: ")
}
override fun onPause() {
super.onPause()
Log.d(TAG, "onPause: ")
sp.edit {
putInt("count_reserved",viewModel.counter)
}
}
}
在onCreate()方法中,我們首先獲取了SharedPreferences的實例,然后讀取之前保存的計數值,如果沒有讀到的話,就使用0作為默認值。接下來在ViewModelProvider方法傳入MainViewModelFactory(countReserved)作為參數,將讀取到的計數值傳給了MainViewModelFactory的構造函數。
并在onPause()方法中對當前的計數進行保存,這樣可以保證不管程序是退出還是進入后臺,計數都不會丟失。
二、Lifecycles
在編寫Android應用程序的時候,可能經常遇到需要感知Activity生命周期的情況。比如,某個頁面中發起了一條網絡請求,但是當請求得到響應的時候,界面或許已經關閉了,這個時候就不應該繼續對響應的結果進行處理。因此我們需要能夠時刻感知到Activity的生命周期,以便在適當的時候進行相應的邏輯控制。
比如有個問題,如果要在一個非Activity的類中去感知Activity的聲明周期,應該怎么辦?
可以通過在Activity中嵌入一個隱藏的Fragment來進行感知,或者通過手寫監聽器的方式來進行感知。
下面通過監聽器的方式來對Activity的生命周期進行感知
class MyObserver{
fun activityStart(){
}
fun activityStop(){
}
}
class MainActivity:AppCompatActivity(){
lateinit var observer:MyObserver
override fun onCreate(savedInstanceState:Bundle?){
observer=MyObserver()
}
override fun onStart(){
super.onStart()
observer.activityStart()
}
override fun onStop(){
super.onStop()
observer.activityStop()
}
}
這里我們為了讓MyObserver能夠感知到Activity的生命周期,需要專門在MainActivity中重寫相應的生命周期方法,然后再通知給MyObserver。這種實現方式需要在Activity中編寫太多額外的邏輯。
而Lifecycles組件就可以在任何一個類中都能輕松感知到Activity的生命周期,同時又不需要在Activity中編寫大量的邏輯處理。
新建一個MyObserver類,并讓它實現LifecycleObserver接口,代碼如下所示:
class MyObserver:LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun activityStart(){
Log.d("MyObserver", "activityStart")
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun activityStop(){
Log.d("MyObserver", "activityStop")
}
}
可以看到,我們在方法上使用了@OnLifecycleEvent注解,并傳入了一種生命周期事件。生命周期事件的類型一共有7種:ON_CREATE、ON_START、ON_STOP、ON_RESUEM、ON_DESTORY分別匹配Activity中相應的聲明周期回調。另外還有一種ON_ANY類型,表示可以匹配Activity的任何生命周期回調。
因此,上述代碼中的activityStart()和activityStop()方法就應該分別在Activity的onStart()和onStop()觸發的時候執行。
接下來借助LifecycleOwner,可以使用如下的語法結構讓MyObserver得到通知:
lifecycleOwner.lifecycle.addObserver(MyObserver)
首先調用lifecycleOwner的getLifecycle()方法,得到一個Lifecycle對象,然后調用它的addObserver()方法來觀察LifecyclerOwner的生命周期,再把MyObserver的實例傳進去就可以了。
又因為我們的Activity是繼承自AppCompatActivity的,或者Fragment繼承自androidx.fragment.app.Fragment,他們本身就是一個LifecycleOwner的實例,這部分工作AndroidX庫自動幫我們完成。所以可以這么寫
class MainActivity : AppCompatActivity() {
private val TAG:String="MainActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
lifecycle.addObserver(MyObserver())
}
}
加上這一行,MyObserver就能自動感知到Activity的生命周期了。不僅在Activity適用,Fragment也適用。
運行程序,然后切到后臺,再回來的打印輸出
當然MyObserver除了感知Activity的生命周期發生變化,也能夠獲知當前的生命周期狀態。只需要在MyObserver的構造函數中將Lifecycle對象傳進來,如下所示:
class MyObserver(val lifecycle:Lifecycle):LifecycleObserver{
}
有了lifecycle對象之后,我們就可以在任何地方調用lifecycle.currentState來主動獲知當前的生命周期狀態。lifecycle.currentState返回的生命周期狀態是一個枚舉類型,一共有DESTROYED,INITIALIZED,CREATED,STARTED,RESUMED這五種狀態。
也就是說,當獲取的生命周期狀態是CREATED的時候,說明onCreate()方法已經執行了,但是onStart()方法還沒有執行。當獲取的生命周期狀態是STARTED的時候,說明onStart()方法已經執行了,但是onResume()方法還沒有執行。
三、LiveData
LiveData是Jetpack提供的一種響應式編程組件,它可以包含任何類型的數據,并在數據發生變化的時候通知給觀察者。
LiveData的基本用法
之前編寫的計數器雖然功能簡單,但還是有問題。當點擊Plus One按鈕時,都會先給ViewModel中的計數加1,然后立即獲取最新的計數。這種方式雖然可以在單線程中正常工作,但如果ViewModel的內部開啟了線程去執行一些耗時邏輯,那么在點擊按鈕后就立即去獲取最新的數據,得到的肯定還是之前的數據。
之前我們使用都是在Activity中手動獲取ViewModel中的數據這種交互方式,但是ViewModel卻無法將數據的變化主動通知給Activity。
或許你會把Activity的實例傳給ViewModel,這樣ViewModel不就能主動對Activity進行通知了嗎?但是要知道ViewModel的生命周期是長于Activity的,如果把Activity的實例傳給ViewModel,就很有可能就因為Activity無法釋放而造成內存泄露。
如果我們將計數器的計數使用LiveData來包裝,然后在Activity中去觀察它,就可以主動將數據變化通知給Activity了。
修改MainViewModel中的代碼,如下所示:
class MainViewModel(countReserved:Int) :ViewModel(){
var counter=MutableLiveData<Int>()
init {
counter.value=countReserved
}
fun plusOne(){
val count=counter.value?:0
counter.value=count+1
}
fun clear(){
counter.value=0
}
}
這里我們將counter變量修改成了一個MutableLiveData對象,并指定它的泛型為Int,表示它包含的是整型數據。MutableLiveData是一種可變的LiveData,用法很簡單,主要有3種讀寫數據的方法,分別是getValue()、setValue()和postValue()方法。getValue()方法用于獲取LiveData中包含的數據;setValue()方法用于給LiveData設置數據,但是只能在主線程中調用;postValue()方法用于在非主線程中給LiveData設置數據。
這里在init結構體中給counter設置數據,這樣之前保存的計數值劇可以在初始化的時候得到恢復。接下來新增了plusOne()和clear()這兩個方法,分別用于給計數加1以及將計數清零。plusOne()方法中的邏輯是先獲取counter中包含的數據,然后給它加1,再重新設置到counter中。調用LiveData的getValue()方法獲得的數據是可能為空的,因此這里使用了一個?:操作符,當獲取到的數據為空時,就用0來作為默認計數。
修改MainActivity
class MainActivity : AppCompatActivity() {
//對變量進行延遲初始化,這樣在就不用先給全局變量賦值,而且在賦值的時候賦值為null,后面還要進行判空
// 如果變量多會比較麻煩
lateinit var viewModel: MainViewModel
lateinit var sp: SharedPreferences
private val TAG:String="MainActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
sp=getPreferences(Context.MODE_PRIVATE)
val countReserved = sp.getInt("count_reserved", 0)
viewModel= ViewModelProvider(this,MainViewModelFactory(countReserved)).get(MainViewModel::class.java)
plusOneBtn.setOnClickListener {
viewModel.plusOne()
}
clearBtn.setOnClickListener {
viewModel.clear()
}
viewModel.counter.observe(this, Observer { count->
infoText.text=count.toString()
})
}
override fun onPause() {
super.onPause()
Log.d(TAG, "onPause: ")
sp.edit {
putInt("count_reserved",viewModel.counter.value?:0)
}
}
}
這里調用了ViewModel.counter的observe()方法來觀察數據的變化。經過對MainViewModel的改造,現在counter變量已經變成了一個LiveData對象,任何LiveData對象都可以調用它的observe()方法來觀察數據的變化。observe()方法接口接收兩個參數,第一個是一個LifecycleOwner對象,Activity本身就是一個LifecycleOwner對象,因此直接傳this就好;第二個參數是一個Observer接口,當counter中包含的數據發生變化時,就會回調這里,因此這里將最新的數據更新到界面。
為了在非ViewModel中就只能觀察LiveData的數據變化,而不能給LiveData設置數據,下面改造MainViewModel:
class MainViewModel(countReserved:Int) :ViewModel(){
val counter:LiveData<Int> get() = _counter
private val _counter=MutableLiveData<Int>()
init {
_counter.value=countReserved
}
fun plusOne(){
val count=_counter.value?:0
_counter.value=count+1
}
fun clear(){
_counter.value=0
}
}
將原來的counter變量改名為_counter變量,并給它加上private修飾符,這樣_counter變量就對外部不可見了。然后又定義了一個counter變量,將它的類型聲明為不可變的LiveData,并在它的get()屬性方法中返回_counter變量。
這樣,當外部調用counter變量時,實際上獲得的就是_counter的實例,但是無法給counter設置數據,從而保證了ViewModel的數據封裝性。
map和switchMap
LiveData為了能夠應對各種不同的需求場景,提供了兩種轉換方法:map()和switchMap()方法。
map()方法,這個方法的作用是將實際包含數據的LiveData和僅用于觀察數據的LiveData進行轉換。
那么什么情況下,會用到這個方法呢?
比如說有個User類,User類包含用戶的姓名和年齡,定義如下:
data class User(var firstName:String,var lastName:String,var age:Int)
我們可以在ViewModel中創建一個相應的LiveData來包含User類型的數據,如下所示:
class MainViewModel(countReserved:Int):ViewModel(){
val userLiveData=MutableLiveData<User>()
}
如果MainActivity中明確只會顯示用戶的姓名,而完全不關心用戶的年齡,這個時候還將User類型的LiveData暴露給外部就不合適了。
而map()方法就是專門解決這個問題,它可以將User類型的LiveData自由地轉型成任意其他類型的LiveData
class MainViewModel(countReserved:Int):ViewModel(){
private val userLiveData=MutableLiveData<User>()
val userName:LiveData<String> =Transformations.map(userLiveData){user->
"${user.firstName}${user.lastName}"
}
...
}
這里我們調用了Transformations的map()方法來對LiveData的數據類型進行轉換。map()方法接收兩個參數:第一個參數是原始的LiveData對象;第二個參數是一個轉換函數,我們在轉換函數中編寫具體的轉換邏輯即可。這里的邏輯就是將user對象轉換為一個只包含用戶姓名的字符串。
另外還將userLiveData聲明成了private,以保證數據的封裝性。外部使用的時候只要觀察userName這個LiveData就可以了。當userLiveData的數據發生變化時,map()方法會監聽到變化并執行轉換函數的邏輯,然后再將轉換之后的數據通知給userName的觀察者。
switchMap()方法的使用場景比較固定:如果ViewModel中的某個LiveData對象是調用另外的方法獲取的,那么我們就可以借助switchMap()方法,將這個LiveData對象轉換成另一個可觀察的LiveData對象。
比如:LiveData對象的實例都是在ViewModel中創建的,然而在實際的項目中,不可能一直都是這種理想情況,很有可能ViewModel中的某個LiveData對象是調用另外的方法獲取的。
新建一個Repository單例類,代碼如下所示:
object Repository {
fun getUser(userId:String):LiveData<User>{
val liveData=MutableLiveData<User>()
liveData.value=User(userId,userId,0)
return liveData
}
}
這里在Repository類中添加了一個getUser()方法,這個方法接收一個userId參數。每次將傳入的userId當做用戶姓名來創建一個新的User對象。
getUser()方法返回的是一個包含User數據的LiveData對象,而且每次調用getUser()方法都會返回一個新的LiveData實例。
然后再MainViewModel中也定義一個getUser()方法,并且讓它調用Repository的getUser()方法來獲取LiveData對象:
class MainViewModel(countReserved:Int) :ViewModel(){
fun getUser(userId:String):LiveData<User>{
return Repository.getUser(userId)
}
}
接下來的問題是,在Activity中如何觀察LiveData的數據變化呢?既然getUser()方法返回的是一個LiveData對象,那么我們可不可以直接在Activity中使用如下寫法呢?
viewModel.getUser(userId).observe(this)
{user->
}
因為每次調用getUser()返回的都是一個新的LiveData實例,而上述寫法會一直觀察老的LiveData實例,從而根本無法觀察到數據的變化,會發現這種情況下LiveData是不可觀察的。
這個時候switchMap()方法就可以派上用場了。
修改MainViewModel中的代碼:
class MainViewModel(countReserved:Int) :ViewModel(){
private val userIdLiveData=MutableLiveData<String>()
val user:LiveData<User> =Transformations.switchMap(userIdLiveData){
userId -> Repository.getUser(userId)//此時的userId就是userIdLiveData的類型對象String
}
fun getUser(userId:String){
userIdLiveData.value=userId
}
}
定義了一個新的userIdLiveDat對象,用來觀察userId的數據變化,然后調用了Transformations的switchMap()方法,用來對另一個可觀察的LiveData對象進行轉換。
switchMap()方法同樣接收兩個參數:第一個參數傳入我們新增的userIdLiveData,switchMap()方法會對它進行觀察;第二個參數是一個轉換函數,注意:我們必須在這個轉換函數中返回一個LiveData對象,因為switchMap()方法的工作原理就是將轉換函數中返回LiveData對象轉換為另一個可觀察的LiveData對象。我們只需要在轉換函數中調用Respository的getUser()方法來得到LiveData對象,將其返回。
首先,當外部調用MainViewModel的getUser()方法來獲取用戶數據時,并不會發起任何請求或者函數調用,只會傳入userId的值設置到userIdLiveData中。一旦userIdLiveData的數據發生變化,那么觀察userIdLiveData的switchMap()方法就會執行,并且調用我們編寫的轉換函數。然后在轉換函數中調用Repository.getUser()方法獲取真正的用戶數據。同時,switchMap()方法會將Repository.getUser()方法返回的LiveData對象轉換成一個可觀察的LiveData對象。對于Activity只需要觀察這個LiveData對象就可以了。
修改activity_main.xml文件,新增一個Get User按鈕
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/getUserBtn" android:layout_gravity="center_horizontal" android:text="Get User" /> </LinearLayout>
修改MainActivity的代碼:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel= ViewModelProvider(this,MainViewModelFactory(countReserved)).get(MainViewModel::class.java)
getUserBtn.setOnClickListener {
val userId=(0..10000).random().toString()
viewModel.getUser(userId)
}
viewModel.user.observe(this, Observer { user ->
infoText.text=user.firstName
})
}
}
通過Get User按鈕的點擊事件中使用隨機函數生成一個userId,然后調用MainViewModel的getUser()方法來獲取用戶數據,但是這個方法不會有返回值。等數據獲取完后,可觀察LiveData對象的observe()方法將會得到通知,我們在這里將獲取的用戶名顯示到界面上。
LiveData內部不會判斷即將設置的數據和原有數據是否相同,只是調用了setValue()或postValue()方法,就一定會觸發數據變化事件。
如果Activity處于不可見狀態的時候(手機息屏,或者被其他的Activity遮擋),LiveData發生了多次數據變化,當Activity恢復可見狀態時,只有最新的那份數據才會通知給觀察者,前面的數據在這種情況下相當于已經過期了,會被直接丟棄。
原文鏈接:https://blog.csdn.net/ChenYiRan123456/article/details/123517714
相關推薦
- 2023-02-09 Flask如何獲取用戶的ip,查詢用戶的登錄次數,并且封ip_python
- 2022-02-14 小程序使用了scroll-view滾動組件時,如何判斷滾動的方向
- 2022-09-30 react中使用usestate踩坑及解決_React
- 2021-11-28 jQuery實現全部購物車功能實例_jquery
- 2021-12-09 C語言實現將double/float?轉為字符串(帶自定義精度)_C 語言
- 2022-06-23 Windows下批處理(BAT)修改文件名的一些整理_DOS/BAT
- 2023-01-05 Go語音開發中常見Error類型處理示例詳解_Golang
- 2022-12-04 關于SQL查詢語句關鍵字方法_MsSql
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支