Java动态代理

April 6, 2019

在之前空对象模式一文中,讨论约束空对象方法调用时,提到可以使用 动态代理 实现。复用前文的数据模型,现在就来实现这个方式。而动态代理的具体运行逻辑详情,将在以后文章单独进行源码剖析。

和静态代理一样,动态代理也需要把所有行为抽象化,于是把以前写在 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

使用动态代理的好处是,再增加实现方法时无需修改已有实现逻辑,就能阻止代码运行时调用空对象方法。