理解 asInstanceOf 时的 ClassCastException

ClassCastException when asInstanceOf in for comprehension

在 PlayFramework 2.4 中,我尝试将所有控制器方法转换为 JavaScript 路由。

val jsRoutesClass = classOf[routes.javascript]
val controllers = jsRoutesClass.getFields.map(_.get(null))

for (
  controller <- controllers;
  method <- controller.getClass.getDeclaredMethods
) yield method.invoke(controller).asInstanceOf[JavaScriptReverseRoute]

但是出现如下错误:

Error injecting constructor, java.lang.ClassCastException: java.lang.String cannot be cast to play.api.routing.JavaScriptReverseRoute 
at controllers.Application.<init>(Application.scala:21)
while locating controllers.Application
   for parameter 1 at router.Routes.<init>(Routes.scala:35)
while locating router.Routes
while locating play.api.inject.RoutesProvider
while locating play.api.routing.Router

我添加了一些代码,但我认为那是不必要的代码。之后就没有异常了。

for (
  controller <- controllers;
  method <- controller.getClass.getDeclaredMethods;
  action <- method.invoke(controller).toString
) yield method.invoke(controller).asInstanceOf[JavaScriptReverseRoute]

为什么错误出现在第一个代码示例中而不是第二个?

让我们一步一步地看代码,看看每一行产生什么。 我将您的代码转换为完整的示例代码,但我希望我抓住了您代码的本质。

package controllers

import play.api._
import play.api.mvc._

class Sample extends Controller {
  def hello(name: String) = Action {
    implicit req =>
    import routes.javascript._

    val jsRoutesClass = classOf[routes.javascript]
    val controllers = jsRoutesClass.getFields.map(_.get(null))
    val met = for (
        controller <- controllers;
        method <- controller.getClass.getDeclaredMethods
      ) yield method
    Ok(met.mkString(", "))
  }
}

执行此请求时,您会看到类似

的内容
public play.api.routing.JavaScriptReverseRoute controllers.javascript.ReverseSample.hello(), public java.lang.String controllers.javascript.ReverseSample._defaultPrefix()

您应该从您的路线中找到所有方法,但请注意还有 _defaultPrefix() 方法 return 类型字符串。

这就是您的第一个代码示例不起作用的原因。其中一种方法没有 return JavaScriptReverseRoute,因此抛出异常。

这仍然不能解释为什么您的第二个代码示例不起作用。因此,让我们向示例控制器添加一些代码:

package controllers

import play.api._
import play.api.mvc._

class Sample extends Controller {
  def hello(name: String) = Action {
    implicit req =>
    import routes.javascript._

    val jsRoutesClass = classOf[routes.javascript]
    val controllers = jsRoutesClass.getFields.map(_.get(null))
    val met = for (
        controller <- controllers;
        method <- controller.getClass.getDeclaredMethods
      ) yield method.invoke(controller)
    Ok(met.mkString(", "))
  }
}

请注意,我们还没有强制转换方法调用的结果,请求产生如下内容:

JavaScriptReverseRoute(controllers.Sample.hello,
    function(name) {
      return _wA({method:"GET", url:"/" + (function(k,v) {return v})("name", encodeURIComponent(name))})
    }
  ), 

仔细看,你会发现末尾有一个流氓 ,,这意味着我们的临时 val met 的值在位置 0 a JavaScriptReverseRoute 和位置 1 和空字符串。

因此,查看您的解决方法,操作 action <- method.invoke(controller).toString 是一个带有 javascript 的字符串,另一次是空字符串。由于我们在理解中,字符串会自动转换为字符数组,如果该数组为空,则不会执行 yield 块。

您的解决方法的问题是,如果 _.defaultPrefix() 曾经产生一个字符串,它将再次抛出一个 class 转换异常。

更好的解决方案是过滤每个没有您的例外结果类型的方法,如下所示:

package controllers

import play.api._
import play.api.mvc._

class Sample extends Controller {
  def hello(name: String) = Action {
    implicit req =>
      import routes.javascript._

      val jsRoutesClass = classOf[routes.javascript]
      val controllers = jsRoutesClass.getFields.map(_.get(null))
      val met = for (
          controller <- controllers;
          method <- controller.getClass.getDeclaredMethods if method.getReturnType() == classOf[play.api.routing.JavaScriptReverseRoute]
         ) yield  method.invoke(controller).asInstanceOf[play.api.routing.JavaScriptReverseRoute]
       Ok(met.mkString(", "))
  }
}

这应该会导致预期的行为。