在之前空对象模式一文中,讨论约束空对象方法调用时,提到可以使用 动态代理 实现。复用前文的数据模型,现在就来实现这个方式。而动态代理的具体运行逻辑详情,将在以后文章单独进行源码剖析。
和静态代理一样,动态代理也需要把所有行为抽象化,于是把以前写在 User 的行为全部抽象到接口 IUser。
1
2
3
4
5
6
7
interface IUser{
fun getUserId(): String
fun getRoomId(): String
fun getRemark(): String
}
然后 User 模型实现该抽象接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class User(private val userId: String,
private val roomId: String,
private val nickname: String) : IUser {
override fun getUserId(): String {
return userId
}
override fun getRoomId(): String {
return roomId
}
override fun getRemark(): String {
return nickname
}
}
实现动态代理的关键是实现 InvocationHandler。使用动态代理,是为了把所有方法代理到同一实例中,所以实现 InvocationHandler 时还需要提供有参构造方法,让外部传入接收委托的实例。
不过,这次并不需要委托任何行为,而只是通过动态代理这个中转,以抛出异常的方式阻止实例里所有方法的调用。由以下代码可见,该中介类无需保存被委托类实例,只是在实现方法里抛异常阻止调用。
1
2
3
4
5
6
7
class Handler : InvocationHandler {
// 实现唯一的抽象方法,SAM,Single abstract mathod.
override fun invoke(proxy: Any, method: Method, args: Array<out Any>): Any {
// 目的就是抛出异常
throw IllegalAccessException("Access to this method is denied.")
}
}
实现中介类 InvocationHandler 之后就可以创建实例。按照惯例,空对象单例对象一般和其模型放在一起。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class User(private val userId: String,
private val roomId: String,
private val nickname: String) : IUser {
.....
companion object {
val defUser by lazy {
// 创建空对象实例,这个实例里所有方法已被代理
Proxy.newProxyInstance(
IUser::class.java.classLoader,
arrayOf(IUser::class.java),
Handler()) as IUser
}
}
}
这个被代理的实例使用方式和普通对象无异
1
2
3
4
5
object ProxyRunner {
@JvmStatic fun main(args: Array<String>) {
User.defUser.getRemark()
}
}
只在运行时走代理逻辑,然后主动抛出预定实现的异常:
1
2
3
4
5
6
Exception in thread "main" java.lang.reflect.UndeclaredThrowableException
at com.sun.proxy.$Proxy0.getRemark(Unknown Source)
at proxy.ProxyRunner.main(ProxyRunner.java:6)
Caused by: java.lang.IllegalAccessException: Access to this method is denied.
at proxy.Handler.invoke(Handler.kt:8)
... 2 more
使用动态代理的好处是,再增加实现方法时无需修改已有实现逻辑,就能阻止代码运行时调用空对象方法。