先導(dǎo)知識
?我們所說的委托倘潜,其實就是一種設(shè)計模式:有兩個對象參與處理同一個請求漆际,接受請求的對象將請求委托給另一個對象來處理意蛀。換一種方法來說就是耸别,我們(調(diào)用方)操作的對象不會自己去處理邏輯,而會把工作委托給另外一個輔助對象去處理县钥。委托模式使得我們可以使用聚合來替代繼承秀姐,更加靈活。舉個簡單的例子若贮,朋友圈的微商賣貨就是一種委托模式省有,“微商”代替廠家賣貨痒留,廠家“委托”他們進行銷售。在這種關(guān)系里蠢沿,“微商”就相當于“代理類”伸头,而廠家則是“委托類”。
?Java在語法層面上是沒有支持委托模式的舷蟀,但是可以通過代理模式來實現(xiàn)委托恤磷。Java的代理模式分為靜態(tài)代理和動態(tài)代理,我們簡單過一下野宜。
靜態(tài)代理
?所謂靜態(tài)代理扫步,是指代理類在編譯時就已經(jīng)創(chuàng)建好了。
public class ProxyDemo {
public static void main(String[] args) {
BaseImp imp = new BaseImp();
Proxy proxy = new Proxy(imp);
proxy.doSomething();
}
}
interface Base {
void doSomething();
}
//委托類
class BaseImp implements Base {
@Override
public void doSomething() {
System.out.println("BaseImp do something...");
}
}
//代理類
class Proxy implements Base {
private final Base baseImp;
public Proxy(Base base) {
this.baseImp = base;
}
@Override
public void doSomething() {
baseImp.doSomething();
}
}
?在這里匈子,Proxy就是代理類锌妻,BaseImp則是委托類。當我們調(diào)用Proxy的方法時執(zhí)行邏輯時旬牲,Proxy就會去調(diào)用BaseImp實例來處理仿粹。在靜態(tài)代理中,代理類和委托類擁有共同的父類或者父接口原茅。這里要提一嘴的是吭历,關(guān)于兩個類的叫法問題。我們調(diào)用Proxy類擂橘,而Proxy再去調(diào)用(這里我理解為委托)BaseImp去做晌区,而BaseImp卻叫做委托類,感覺是不是有點怪怪的(我覺得應(yīng)該叫被委托類更合適)通贞。所以這時候我們可以反過來想朗若,就是因為BaseImp去委托了Proxy來代理它的事務(wù),所以它理所當然就是委托方啦昌罩。當然了哭懈,這里我們不用去糾結(jié),概念本身創(chuàng)造出來就是幫助我們來理解的茎用,而不是讓我們鉆牛角尖的遣总,所以我們清楚這個模式所要表達(傳遞)的意思就行。
?既然靜態(tài)代理在編譯時就產(chǎn)生對應(yīng)的字節(jié)碼文件轨功,那也就意味著效率會比較高旭斥。但也是因此靜態(tài)代理只能為一個目標對象服務(wù),如果目標對象過多古涧,就會對象產(chǎn)生較多的代理類垂券。
動態(tài)代理
?跟靜態(tài)代理不同的是,動態(tài)代理的代理類是在運行時生成的羡滑,我們看名字也看得出來菇爪。也就是說卒暂,動態(tài)代理類是在程序運行時由Java反射機制動態(tài)生成的,我們不需要去編寫代理類的邏輯代碼娄帖。
?實現(xiàn)動態(tài)代理的步驟為:
(1)定義公共接口及實現(xiàn)委托類也祠。
(2)實現(xiàn)Java反射包的InvocationHandler接口,來創(chuàng)建代理類的調(diào)用處理器近速。
(3)動態(tài)生成代理對象诈嘿。
(4)通過代理對象調(diào)用方法。
public class DynamicProxyDemo {
public static void main(String[] args) {
BaseDynamicImpl dynamicImpl = new BaseDynamicImpl();
//通過委托類來構(gòu)造調(diào)用處理器
ProxyHandler proxyHandler = new ProxyHandler(dynamicImpl);
//通過Java反射包的Proxy類來動態(tài)生成代理對象
BaseDynamic proxy = (BaseDynamic) Proxy.newProxyInstance(
BaseDynamicImpl.class.getClassLoader(),
BaseDynamicImpl.class.getInterfaces(),
proxyHandler);
//通過代理對象調(diào)用方法
proxy.doSomething();
}
}
//公共接口
interface BaseDynamic {
void doSomething();
}
//委托類
class BaseDynamicImpl implements BaseDynamic {
@Override
public void doSomething() {
System.out.println("BaseDynamicImpl do something...");
}
}
//代理類的調(diào)用處理器
class ProxyHandler implements InvocationHandler {
//一樣削葱,我們需要持有共同的父類接口
private final BaseDynamic baseDynamic;
public ProxyHandler(BaseDynamic baseDynamic) {
this.baseDynamic = baseDynamic;
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
return method.invoke(baseDynamic, objects);
}
}
?動態(tài)代理涉及到兩個重要的Java API:
(1)java.lang.reflect.InvocationHandler:調(diào)用處理器接口奖亚,它的invoke方法用于集中處理在動態(tài)代理類對象上的方法調(diào)用,通常在該方法中實現(xiàn)對委托類的代理訪問析砸。
(2)java.lang.reflect.Proxy:Java動態(tài)代理機制生成的所有動態(tài)代理類的父類昔字,提供了一組靜態(tài)方法來為一組接口動態(tài)地生成代理類及其對象。
?很明顯首繁,既然通過了反射代理方法作郭,那么也就會意味著性能問題。但相應(yīng)的弦疮,動態(tài)代理的優(yōu)勢也很明顯夹攒,可以顯著地減少代理類的數(shù)量,我們不需要每次都去為目標對象創(chuàng)建相應(yīng)的代理類胁塞,使用起來更加靈活咏尝。Retrofit就是通過動態(tài)代理對上層接口的封裝,讓我們可以用更加面向?qū)ο蟮倪壿嬓グ眨p松地訪進行網(wǎng)絡(luò)操作编检。
Kotlin的委托模式
?說完了Java的委托,我們再回過頭來看看Kotlin的委托扰才。
?作為極具創(chuàng)新的現(xiàn)代化語音允懂,Kotlin直接在語法層面上支持了委托模式,那就意味著我們使用起來更加的方便了训桶。Kotlin的委托分為類委托和委托屬性累驮。
類委托
?我們還是用剛剛的例子:
interface Base {
fun doSomething()
}
//委托類
class BaseImpl : Base {
override fun doSomething() {
println("BaseImpl do something...")
}
}
//通過by關(guān)鍵字完成委托酣倾,Derived相當于代理類
class Derived(base: Base) : Base by base
fun main(args: Array<String>) {
val base = BaseImpl()
val derived = Derived(base)
derived.doSomething()
}
?你會發(fā)現(xiàn)舵揭,非常簡單,我們僅僅通過by關(guān)鍵字躁锡,便將職責(zé)委托給了BaseImpl類午绳,當然我們自己也要實現(xiàn)Base接口,才能實現(xiàn)相對應(yīng)方法的委托映之。其實我們寫的時候也可以發(fā)現(xiàn)拦焚,當我們只寫到繼承Base接口還沒寫by的時候蜡坊,IDE是會報錯的,提示你沒有去重寫對應(yīng)的方法赎败。但當我們通過by關(guān)鍵字實現(xiàn)委托之后秕衙,IDE就正常了。而這么做的好處我們剛剛也說過了僵刮,通過委托來替代繼承是一個很好的選擇据忘,而Kotlin又給予了我們語法層面的支持,簡直不要太爽搞糕!
?比如我們要實現(xiàn)一個Set類勇吊,但是大部分邏輯都跟HashSet差不多,這個時候我們就可以利用委托來實現(xiàn)窍仰。我們只需要重寫自己需要的方法即可:
class MySet<T>(private val helperSet: HashSet<T>) : Set<T> by helperSet {
fun helloWorld() = println("Hello Kotlin World")
override fun isEmpty(): Boolean {
return false
}
}
?同樣汉规,我們通過by把邏輯委托給了helperSet,除了我們自己重寫的isEmpty()方法驹吮,當然這里沒有實際意義针史,只是為了演示,然后我們又添加了一個自己獨有的helloWorld()方法碟狞。這樣一來悟民,我們就擁有了HashSet的全部能力以及我們自己獨有的方法,一個全新的數(shù)據(jù)結(jié)構(gòu)類就誕生了篷就。
?既然by這么神奇射亏,我們就來看看它背后是怎么實現(xiàn)的。我們先把文件編譯成Kotlin字節(jié)碼竭业,再反編譯回來:
public final class MySet implements Set, KMappedMarker {
private final HashSet helperSet;
public final void helloWorld() {
String var1 = "Hello Kotlin World";
System.out.println(var1);
}
public boolean isEmpty() {
return false;
}
public MySet(@NotNull HashSet helperSet) {
Intrinsics.checkNotNullParameter(helperSet, "helperSet");
super();
this.helperSet = helperSet;
}
//省略其他方法...
}
?你會發(fā)現(xiàn)智润,和我們剛剛Java的靜態(tài)代理一模一樣,只不過是Kotlin幫我們默默地把事情都做了(它真的未辆,我哭死)窟绷。
委托屬性
?類委托的核心思想是將一個類的具體實現(xiàn)委托給另外一個類去完成,而委托屬性的核心思想是將一個屬性(字段)的具體實現(xiàn)委托給另外一個類去完成咐柜。先看下委托屬性的語法結(jié)構(gòu):
class PropertyProxy {
var p by Delegate()
}
?同樣的兼蜈,通過by關(guān)鍵字來實現(xiàn),這意味著將p屬性的具體實現(xiàn)委托給Delegate類來完成拙友。當然這個時候为狸,IDE會報錯,提示我們創(chuàng)建帶有g(shù)etValue和setValue方法的Delegate類遗契,根據(jù)提示去創(chuàng)建這兩個方法即可辐棒,我們來具體實現(xiàn)一下:
class Delegate {
var propertyValue: Any? = null
operator fun getValue(proxy: PropertyProxy, property: KProperty<*>): Any? {
return propertyValue
}
operator fun setValue(proxy: PropertyProxy, property: KProperty<*>, any: Any?) {
propertyValue = any
}
}
?可以看到,我們重寫了getValue()和setValue()方法,并用operator關(guān)鍵字進行聲明漾根。我們直接看setValue()方法泰涂,第一個參數(shù)就是我們的代理類,也就是聲明該類(Delegate)的委托功能可以在哪個類中使用辐怕;第二個參數(shù)KProperty< * >是Kotlin的一個屬性操作類逼蒙,可用于獲取各種屬性相關(guān)的值,當前場景下用不上寄疏,而< * >表示我們不關(guān)心泛型的具體類型其做,類似于Java中的<?>。
?委托屬性的工作流程就是當我們?nèi)ソoPropertyProxy類的p屬性賦值的時候赁还,就會調(diào)用Delegate類的setValue方法妖泄,而當我們獲取p屬性的值時,就會去調(diào)用Delegate類的getValue方法艘策。就是這么簡單蹈胡,真正的難點在于如何靈活應(yīng)用。
by lazy
?既然談到委托屬性朋蔫,那總是繞不開by lazy的罚渐。相信大家也很熟悉,平常幾乎都用得飛起驯妄。by lazy其實是屬于延遲屬性荷并,我們經(jīng)常用的lateinit也是(具體來說是通過它們來修飾),而延遲屬性就是委托屬性的一種青扔。
?說回by lazy源织,其中l(wèi)azy并不是關(guān)鍵字,而是一個高階函數(shù)微猖,可以接收一個lambda表達式作為參數(shù)谈息,我們來看下面的例子:
class PropertyProxy {
var p by Delegate()
val str: String by lazy {
println("enter lazy()")
"Value"
}
}
fun main(args: Array<String>) {
val proxy = PropertyProxy()
println(proxy.str)
println("------")
println(proxy.str)
}
?新增一個str屬性,然后我們調(diào)用它凛剥,把結(jié)果打印出來:
enter lazy()
Value
------
Value
?我們會發(fā)現(xiàn)侠仇,調(diào)用了兩次str屬性,而"enter lazy()"只打印了一次犁珠。這是因為lazy函數(shù)只有在第一次調(diào)用時執(zhí)行整個Lambda表達式逻炊,而后調(diào)用則會直接返回第一次調(diào)用返回的結(jié)果。也就是說lazy()方法會把最后一行作為返回值犁享,當我們第一次調(diào)用str完成初始化之后余素,后面再調(diào)用的話就直接以最后一行作為返回值返回,這其實也是延遲屬性(懶加載)的奧義所在饼疙∧缟看下它的源碼:
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
?這里的actual是Kotlin的關(guān)鍵字慕爬,表示多平臺項目中的一個平臺相關(guān)實現(xiàn)窑眯。lazy()方法接收了一個lambda參數(shù)屏积,然后會返回一個Lazy對象,這里返回了SynchronizedLazyImpl實例磅甩。很明顯SynchronizedLazyImpl類是Lazy的子類炊林。
?先看下Lazy,它是一個接口:
/**
* 表示具有延遲初始化的值.
*
* To create an instance of [Lazy] use the [lazy] function.
*/
public interface Lazy<out T> {
/**
* 獲取當前 Lazy 實例的延遲初始化值.
* 初始化該值后卷要,在此延遲實例(Lazy instance)的剩余生命周期內(nèi)不得更改.
*/
public val value: T
/**
* 如果此延遲實例的值已初始化渣聚,則返回“true”,否則返回“false”.
* 一旦此函數(shù)返回“true”僧叉,它將在此 Lazy 實例的剩余生命周期內(nèi)保持“true”.
*/
public fun isInitialized(): Boolean
}
?很簡單奕枝,持有一個value字段,然后一個isInitialized()方法瓶堕,官方注釋也寫得很清楚隘道。這里的<out T>是表示Lazy在泛型T上是協(xié)變的,具體可以看Kotlin的協(xié)變和逆變郎笆。提一嘴谭梗,Google爸爸的注釋寫得都非常詳細,一定要去看宛蚓。接著詳細來看下SynchronizedLazyImpl類的實現(xiàn):
private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
private var initializer: (() -> T)? = initializer
@Volatile private var _value: Any? = UNINITIALIZED_VALUE
// final field is required to enable safe publication of constructed instance
private val lock = lock ?: this
override val value: T
get() {
val _v1 = _value
if (_v1 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST")
return _v1 as T
}
return synchronized(lock) {
val _v2 = _value
if (_v2 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST") (_v2 as T)
} else {
val typedValue = initializer!!()
_value = typedValue
initializer = null
typedValue
}
}
}
override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."
private fun writeReplace(): Any = InitializedLazyImpl(value)
}
?可以看到SynchronizedLazyImpl類實現(xiàn)了Lazy和Serializable接口激捏,接收兩個參數(shù)initializer和lock,initializer就是我們的初始化邏輯函數(shù)凄吏,lock則是鎖远舅,用于獲取值時的同步操作,默認為空痕钢。重載了Lazy接口的value(和isInitialized()方法)表谊,Lazy接口的value屬性用于獲取當前Lazy實例的延遲初始化值,一旦初始化之后盖喷,就不能在改實例的剩余生命周期內(nèi)更改爆办。所以重載的value屬性只有g(shù)et()方法,并沒有set()方法课梳。
?然后自己還持有了一個私有的_value值距辆,并設(shè)置了初始值:
@Volatile private var _value: Any? = UNINITIALIZED_VALUE
?_value使用了@Volatile注解,相當于Java的volatile關(guān)鍵字暮刃,具有可見性和有序性跨算,因此一旦_value值修改了,其他線程就可以看到其最新的值椭懊。
?value屬性在get()時诸蚕,會先判斷當前的_value還是不是初始值(UNINITIALIZED_VALUE),如果不是就說明已經(jīng)初始化過了,直接返回即可背犯;否則就走初始化邏輯來初始化_value值坏瘩,我們看到使用了synchronized方法(類似Java的synchronized關(guān)鍵字)來保證線程安全,然后又判斷了一次_v2 !== UNINITIALIZED_VALUE漠魏,進行雙重檢驗倔矾。最后執(zhí)行initializer()函數(shù)進行初始化,更新_value柱锹,再進行返回哪自。整個邏輯其實很清晰。
?我們上面分析的這個lazy()方法其實是最常用的一個禁熏,它還有另外兩個實現(xiàn):
public actual fun <T> lazy(lock: Any?, initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer, lock)
public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
when (mode) {
LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
}
?第一個其實就是把SynchronizedLazyImpl里的lock參數(shù)提到lazy()方法里來壤巷,本質(zhì)上和我們前面提到的沒區(qū)別,我們直接看第二個lazy()方法瞧毙,多出來了一個LazyThreadSafetyMode類型的參數(shù)胧华,然后再根據(jù)不同模式去創(chuàng)建不同的實例。
?SYNCHRONIZED返回的也是我們剛剛分析的SynchronizedLazyImpl類升筏;PUBLICATION則是SafePublicationLazyImpl類撑柔;而NONE是UnsafeLazyImpl類。其實我們通過名字也可以分辨出來您访,UnsafeLazyImpl是非線程安全的铅忿,而其他兩個是線程安全的,我們來簡單看下SafePublicationLazyImpl的源碼:
private class SafePublicationLazyImpl<out T>(initializer: () -> T) : Lazy<T>, Serializable {
@Volatile private var initializer: (() -> T)? = initializer
@Volatile private var _value: Any? = UNINITIALIZED_VALUE
// this final field is required to enable safe initialization of the constructed instance
private val final: Any = UNINITIALIZED_VALUE
override val value: T
get() {
val value = _value
if (value !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST")
return value as T
}
val initializerValue = initializer
// if we see null in initializer here, it means that the value is already set by another thread
if (initializerValue != null) {
val newValue = initializerValue()
if (valueUpdater.compareAndSet(this, UNINITIALIZED_VALUE, newValue)) {
initializer = null
return newValue
}
}
@Suppress("UNCHECKED_CAST")
return _value as T
}
override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."
private fun writeReplace(): Any = InitializedLazyImpl(value)
companion object {
private val valueUpdater = java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater(
SafePublicationLazyImpl::class.java,
Any::class.java,
"_value"
)
}
}
?這邊邏輯判斷上和前面有些不同灵汪,我們可以看到initializer屬性也被加上了 @Volatile注解:
@Volatile private var initializer: (() -> T)? = initializer
?然后接著我們判斷完“value !== UNINITIALIZED_VALUE”如果不成立檀训,也就是value還沒被初始化時,我們再往下走享言。這個時候峻凫,拿initializer來做判斷,“if we see null in initializer here, it means that the value is already set by another thread”览露,大意就是:假如我們這時發(fā)現(xiàn)initializer是空的荧琼,那么意味著value值已經(jīng)在別的線程被初始化了。為什么這么說呢差牛,我們接下來看:
if (initializerValue != null) {
val newValue = initializerValue()
if (valueUpdater.compareAndSet(this, UNINITIALIZED_VALUE, newValue)) {
initializer = null
return newValue
}
}
return _value as T
?也就是說命锄,如果initializer(initializerValue)為空,那么我們就直接跳過做返回操作了偏化。那不為空的時候呢脐恩,我們是不是就得去初始化,進到if邏輯里侦讨,先初始化拿到值(newValue)驶冒,然后再通過valueUpdater的compareAndSet()做判斷苟翻,如果邏輯成立(我們先不管怎么成立),那我們就會把initializer賦值為null骗污,完成初始化崇猫。這也就是為什么說在判斷的時候發(fā)現(xiàn)initializer為空,那么就意味著value值已經(jīng)在別的線程被初始化了的原因身堡。那valueUpdater是啥邓尤?
companion object {
private val valueUpdater = java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater(
SafePublicationLazyImpl::class.java,
Any::class.java,
"_value"
)
}
?是Java并發(fā)包的AtomicReferenceFieldUpdater類拍鲤,newUpdater是它的一個靜態(tài)方法贴谎,返回自身。那么其實上面的操作也就是通過AtomicReferenceFieldUpdater的CAS邏輯來保證了_value屬性的原子操作季稳,因為@Volatile是不具備原子性的擅这。
?這也就意味著SafePublicationLazyImpl類是支持多個線程同時調(diào)用,并且可以在全部或部分線程上同時進行初始化景鼠。但是仲翎,如果某個值已經(jīng)由另外一個線程初始化,則將返回該值铛漓,不再進行初始化溯香。
?UnsafeLazyImpl類因為是非線程安全的,所以就是對value直接操作判斷浓恶,這里就不再分析了玫坛。總結(jié)下三種模式的區(qū)別:
SYNCHRONIZED => lazy()方法的默認模式包晰,初始化操作僅僅在首先調(diào)用的第一個線程上執(zhí)行湿镀,其他線程將引用緩存后的值。
PUBLICATION => 支持同時多個線程調(diào)用伐憾,并且可以在全部或者部分線程上同時進行初始化勉痴。如果某個值已由另一個線程初始化,則將返回該值树肃,而不執(zhí)行初始化蒸矛。
NONE => 不是線程安全的。
結(jié)語
?本篇文章主要圍繞委托來簡單介紹了Java的靜態(tài)代理和動態(tài)代理胸嘴,再到Kotlin的類委托和委托屬性雏掠,最后分析了by lazy延遲屬性。其實整體分析下來筛谚,我們會發(fā)現(xiàn)并沒有太多的難點磁玉,Kotlin精妙的設(shè)計讓我們在處理事務(wù)上更加簡潔明了。