Search code examples
scalareflectionproxyproxy-pattern

Scala Invocation Handler causes ClassCastException


I'm trying to implement a proxy pattern so that I can swap out an underlying instance dynamically under the covers when necessary along with an extension method to trigger the swap. I've implemented this in Java before, but I'm having trouble with it in Scala.

This is my scenario:

class Client { ...library code... }

trait DynamicClient extends Client {
   def swap: Client
}

class Provider extends Provider[DynamicClient] {
  def get():DynamicClient {
    java.lang.reflect.Proxy.newProxyInstance(
      classOf[DynamicClient].getClassLoader,
      Array(classOf[DynamicClient]),
      handler)
    .asInstanceOf[DynamicClient]
  }
}

class DynamicClientHandler extends java.lang.reflect.InvocationHandler {

  var client:Client = createNewClient()
  def swap(): {
    client = createNewClient()
    client
  }
  def createNewClient():Client: { ... }


  def invoke(proxy: AnyRef, method: java.lang.reflect.Method, args: Array[AnyRef]): AnyRef = {
      method.getDeclaringClass match {
        case dyn if dyn == classOf[DynamicClient] => swap()
        case _ => method.invoke(client, args: _*)
      }
  }
}

Now the problem: When I invoke methods from the DynamicClient or Object on the Proxy object, they work just fine.

val dynamicClient = injector.instanceOf[DynamicClient]
val initial = dynamicClient.client
val client = dynamicClient.swap()
val dynamicClient.toString // "Client@1234" (Object impl of toString via client instance)
assert(client != initial) //passes just fine, the underlying client is re-initialized

Any call to a method belonging to the Client class fails before it ever gets to the Invocation Handler.

//Both of the following scenarios fail independently of the other
//ERROR:
dynamicClient.asInstanceOf[Client]
//ERROR:
dynamicClient.doSomeClientMethod()

With this exception trace:

java.lang.ClassCastException: com.sun.proxy.$Proxy22 cannot be cast to Client

Why do I get this cast exception? Is there a better way to handle proxy invocation handling in Scala vs. the Java way?


Solution

  • Ok. I've tried to make your example really reproducible, here what it have become:

    import java.lang.reflect.{Method, Proxy}
    
    class Client
    
    trait DynamicClient extends Client {
      def swap: Client
    }
    
    def mkClient =
      Proxy.newProxyInstance(
        classOf[Client].getClassLoader,
        Array(classOf[DynamicClient]),
        new DynamicClientHandler
      ).asInstanceOf[DynamicClient]
    
    
    class DynamicClientHandler extends java.lang.reflect.InvocationHandler {
      val client = new Client{}
    
      def invoke(proxy: AnyRef, method: Method, args: Array[AnyRef]): AnyRef =
        if (method.getDeclaringClass == classOf[DynamicClient])
          swap
        else method.invoke(client, args: _*)
    
    
      def swap = createNewClient
    
      def createNewClient = mkClient
    }
    
    mkClient.swap
    

    This example will work as soon you change class to trait in definition of Client.

    Why? Because it's clear from the answer you linked in your comment that trait extending class is really a restriction, that working only in scala compiler. So from java perspective interface DynamicClient still has nothing common with class Client as reflection error says.

    So you can't really create Proxy of a class and should think of some workaround.