先导知识
我们所说的委托,其实就是一种设计模式:有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。换一种方法来说就是,我们(调用方)操作的对象不会自己去处理逻辑,而会把工作委托给另外一个辅助对象去处理。委托模式使得我们可以使用聚合来替代继承,更加灵活。举个简单的例子,朋友圈的微商卖货就是一种委托模式,“微商”代替厂家卖货,厂家“委托”他们进行销售。在这种关系里,“微商”就相当于“代理类”,而厂家则是“委托类”。
Java在语法层面上是没有支持委托模式的,但是可以通过代理模式来实现委托。Java的代理模式分为静态代理和动态代理,我们简单过一下。
静态代理
所谓静态代理,是指代理类在编译时就已经创建好了。
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则是委托类。当我们调用Proxy的方法时执行逻辑时,Proxy就会去调用BaseImp实例来处理。在静态代理中,代理类和委托类拥有共同的父类或者父接口。这里要提一嘴的是,关于两个类的叫法问题。我们调用Proxy类,而Proxy再去调用(这里我理解为委托)BaseImp去做,而BaseImp却叫做委托类,感觉是不是有点怪怪的(我觉得应该叫被委托类更合适)。所以这时候我们可以反过来想,就是因为BaseImp去委托了Proxy来代理它的事务,所以它理所当然就是委托方啦。当然了,这里我们不用去纠结,概念本身创造出来就是帮助我们来理解的,而不是让我们钻牛角尖的,所以我们清楚这个模式所要表达(传递)的意思就行。
既然静态代理在编译时就产生对应的字节码文件,那也就意味着效率会比较高。但也是因此静态代理只能为一个目标对象服务,如果目标对象过多,就会对象产生较多的代理类。
动态代理
跟静态代理不同的是,动态代理的代理类是在运行时生成的,我们看名字也看得出来。也就是说,动态代理类是在程序运行时由Java反射机制动态生成的,我们不需要去编写代理类的逻辑代码。
实现动态代理的步骤为:
(1)定义公共接口及实现委托类。
(2)实现Java反射包的InvocationHandler接口,来创建代理类的调用处理器。
(3)动态生成代理对象。
(4)通过代理对象调用方法。
public class DynamicProxyDemo {
public static void main(String[] args) {
BaseDynamicImpl dynamicImpl = new BaseDynamicImpl();
//通过委托类来构造调用处理器
ProxyHandler proxyHandler = new ProxyHandler(dynamicImpl);
//通过Java反射包的Proxy类来动态生成代理对象
BaseDynamic proxy = (BaseDynamic) Proxy.newProxyInstance(
BaseDynamicImpl.class.getClassLoader(),
BaseDynamicImpl.class.getInterfaces(),
proxyHandler);
//通过代理对象调用方法
proxy.doSomething();
}
}
//公共接口
interface BaseDynamic {
void doSomething();
}
//委托类
class BaseDynamicImpl implements BaseDynamic {
@Override
public void doSomething() {
System.out.println("BaseDynamicImpl do something...");
}
}
//代理类的调用处理器
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);
}
}
动态代理涉及到两个重要的Java API:
(1)java.lang.reflect.InvocationHandler:调用处理器接口,它的invoke方法用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。
(2)java.lang.reflect.Proxy:Java动态代理机制生成的所有动态代理类的父类,提供了一组静态方法来为一组接口动态地生成代理类及其对象。
很明显,既然通过了反射代理方法,那么也就会意味着性能问题。但相应的,动态代理的优势也很明显,可以显著地减少代理类的数量,我们不需要每次都去为目标对象创建相应的代理类,使用起来更加灵活。Retrofit就是通过动态代理对上层接口的封装,让我们可以用更加面向对象的逻辑,轻松地访进行网络操作。
Kotlin的委托模式
说完了Java的委托,我们再回过头来看看Kotlin的委托。
作为极具创新的现代化语音,Kotlin直接在语法层面上支持了委托模式,那就意味着我们使用起来更加的方便了。Kotlin的委托分为类委托和委托属性。
类委托
我们还是用刚刚的例子:
interface Base {
fun doSomething()
}
//委托类
class BaseImpl : Base {
override fun doSomething() {
println("BaseImpl do something...")
}
}
//通过by关键字完成委托,Derived相当于代理类
class Derived(base: Base) : Base by base
fun main(args: Array<String>) {
val base = BaseImpl()
val derived = Derived(base)
derived.doSomething()
}
你会发现,非常简单,我们仅仅通过by关键字,便将职责委托给了BaseImpl类,当然我们自己也要实现Base接口,才能实现相对应方法的委托。其实我们写的时候也可以发现,当我们只写到继承Base接口还没写by的时候,IDE是会报错的,提示你没有去重写对应的方法。但当我们通过by关键字实现委托之后,IDE就正常了。而这么做的好处我们刚刚也说过了,通过委托来替代继承是一个很好的选择,而Kotlin又给予了我们语法层面的支持,简直不要太爽!
比如我们要实现一个Set类,但是大部分逻辑都跟HashSet差不多,这个时候我们就可以利用委托来实现。我们只需要重写自己需要的方法即可:
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的全部能力以及我们自己独有的方法,一个全新的数据结构类就诞生了。
既然by这么神奇,我们就来看看它背后是怎么实现的。我们先把文件编译成Kotlin字节码,再反编译回来:
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;
}
//省略其他方法...
}
你会发现,和我们刚刚Java的静态代理一模一样,只不过是Kotlin帮我们默默地把事情都做了(它真的,我哭死)。
委托属性
类委托的核心思想是将一个类的具体实现委托给另外一个类去完成,而委托属性的核心思想是将一个属性(字段)的具体实现委托给另外一个类去完成。先看下委托属性的语法结构:
class PropertyProxy {
var p by Delegate()
}
同样的,通过by关键字来实现,这意味着将p属性的具体实现委托给Delegate类来完成。当然这个时候,IDE会报错,提示我们创建带有getValue和setValue方法的Delegate类,根据提示去创建这两个方法即可,我们来具体实现一下:
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关键字进行声明。我们直接看setValue()方法,第一个参数就是我们的代理类,也就是声明该类(Delegate)的委托功能可以在哪个类中使用;第二个参数KProperty< * >是Kotlin的一个属性操作类,可用于获取各种属性相关的值,当前场景下用不上,而< * >表示我们不关心泛型的具体类型,类似于Java中的<?>。
委托属性的工作流程就是当我们去给PropertyProxy类的p属性赋值的时候,就会调用Delegate类的setValue方法,而当我们获取p属性的值时,就会去调用Delegate类的getValue方法。就是这么简单,真正的难点在于如何灵活应用。
by lazy
既然谈到委托属性,那总是绕不开by lazy的。相信大家也很熟悉,平常几乎都用得飞起。by lazy其实是属于延迟属性,我们经常用的lateinit也是(具体来说是通过它们来修饰),而延迟属性就是委托属性的一种。
说回by lazy,其中lazy并不是关键字,而是一个高阶函数,可以接收一个lambda表达式作为参数,我们来看下面的例子:
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属性,然后我们调用它,把结果打印出来:
enter lazy()
Value
------
Value
我们会发现,调用了两次str属性,而"enter lazy()"只打印了一次。这是因为lazy函数只有在第一次调用时执行整个Lambda表达式,而后调用则会直接返回第一次调用返回的结果。也就是说lazy()方法会把最后一行作为返回值,当我们第一次调用str完成初始化之后,后面再调用的话就直接以最后一行作为返回值返回,这其实也是延迟属性(懒加载)的奥义所在。看下它的源码:
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
这里的actual是Kotlin的关键字,表示多平台项目中的一个平台相关实现。lazy()方法接收了一个lambda参数,然后会返回一个Lazy对象,这里返回了SynchronizedLazyImpl实例。很明显SynchronizedLazyImpl类是Lazy的子类。
先看下Lazy,它是一个接口:
/**
* 表示具有延迟初始化的值.
*
* To create an instance of [Lazy] use the [lazy] function.
*/
public interface Lazy<out T> {
/**
* 获取当前 Lazy 实例的延迟初始化值.
* 初始化该值后,在此延迟实例(Lazy instance)的剩余生命周期内不得更改.
*/
public val value: T
/**
* 如果此延迟实例的值已初始化,则返回“true”,否则返回“false”.
* 一旦此函数返回“true”,它将在此 Lazy 实例的剩余生命周期内保持“true”.
*/
public fun isInitialized(): Boolean
}
很简单,持有一个value字段,然后一个isInitialized()方法,官方注释也写得很清楚。这里的<out T>是表示Lazy在泛型T上是协变的,具体可以看Kotlin的协变和逆变。提一嘴,Google爸爸的注释写得都非常详细,一定要去看。接着详细来看下SynchronizedLazyImpl类的实现:
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类实现了Lazy和Serializable接口,接收两个参数initializer和lock,initializer就是我们的初始化逻辑函数,lock则是锁,用于获取值时的同步操作,默认为空。重载了Lazy接口的value(和isInitialized()方法),Lazy接口的value属性用于获取当前Lazy实例的延迟初始化值,一旦初始化之后,就不能在改实例的剩余生命周期内更改。所以重载的value属性只有get()方法,并没有set()方法。
然后自己还持有了一个私有的_value值,并设置了初始值:
@Volatile private var _value: Any? = UNINITIALIZED_VALUE
_value使用了@Volatile注解,相当于Java的volatile关键字,具有可见性和有序性,因此一旦_value值修改了,其他线程就可以看到其最新的值。
value属性在get()时,会先判断当前的_value还是不是初始值(UNINITIALIZED_VALUE),如果不是就说明已经初始化过了,直接返回即可;否则就走初始化逻辑来初始化_value值,我们看到使用了synchronized方法(类似Java的synchronized关键字)来保证线程安全,然后又判断了一次_v2 !== UNINITIALIZED_VALUE,进行双重检验。最后执行initializer()函数进行初始化,更新_value,再进行返回。整个逻辑其实很清晰。
我们上面分析的这个lazy()方法其实是最常用的一个,它还有另外两个实现:
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参数提到lazy()方法里来,本质上和我们前面提到的没区别,我们直接看第二个lazy()方法,多出来了一个LazyThreadSafetyMode类型的参数,然后再根据不同模式去创建不同的实例。
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”,大意就是:假如我们这时发现initializer是空的,那么意味着value值已经在别的线程被初始化了。为什么这么说呢,我们接下来看:
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,完成初始化。这也就是为什么说在判断的时候发现initializer为空,那么就意味着value值已经在别的线程被初始化了的原因。那valueUpdater是啥?
companion object {
private val valueUpdater = java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater(
SafePublicationLazyImpl::class.java,
Any::class.java,
"_value"
)
}
是Java并发包的AtomicReferenceFieldUpdater类,newUpdater是它的一个静态方法,返回自身。那么其实上面的操作也就是通过AtomicReferenceFieldUpdater的CAS逻辑来保证了_value属性的原子操作,因为@Volatile是不具备原子性的。
这也就意味着SafePublicationLazyImpl类是支持多个线程同时调用,并且可以在全部或部分线程上同时进行初始化。但是,如果某个值已经由另外一个线程初始化,则将返回该值,不再进行初始化。
UnsafeLazyImpl类因为是非线程安全的,所以就是对value直接操作判断,这里就不再分析了。总结下三种模式的区别:
SYNCHRONIZED => lazy()方法的默认模式,初始化操作仅仅在首先调用的第一个线程上执行,其他线程将引用缓存后的值。
PUBLICATION => 支持同时多个线程调用,并且可以在全部或者部分线程上同时进行初始化。如果某个值已由另一个线程初始化,则将返回该值,而不执行初始化。
NONE => 不是线程安全的。
结语
本篇文章主要围绕委托来简单介绍了Java的静态代理和动态代理,再到Kotlin的类委托和委托属性,最后分析了by lazy延迟属性。其实整体分析下来,我们会发现并没有太多的难点,Kotlin精妙的设计让我们在处理事务上更加简洁明了。