最近用spray做点东西,刚开始入门,把doc大概过了一遍,最灵活的要数它的routing-DSL了。当然由于scala强大的特性,很多不同于Java的东西,源码看起来还是相当费劲的,尤其是对于我这种scala使用没多久的新手。
今天碰到的问题是如何把参数转化成想要的类型,比如?age=25&birth=1988-01-23,能接收为Int和Date参数。很快在文档里找到了方法,如下写法:
case class Color(red: Int, green: Int, blue: Int) val route = path("color") { parameters(‘red.as[Int], ‘green.as[Int], ‘blue.as[Int]) { (red, green, blue) => val color = Color(red, green, blue) doSomethingWith(color) // route working with the Color instance } }
但是我在自己写的时候,发现这样写Date会导致编译错误,
val route: Route = path("stat" / Rest) { path => parameters(‘age.as[Int], ‘birth.as[java.util.Date]) { (age, birth) => get { complete { path + age.toString + birth } } } }
[info] Compiling 1 Scala source to D:\study\spray-template\target\scala-2.11\classes... [error] D:\study\spray-template\src\main\scala\com\example\MyService.scala:60: too many arguments for method parameters: (pdm: spr ay.routing.directives.ParamDefMagnet)pdm.Out [error] parameters(‘age.as[Int], ‘birth.as[java.util.Date]) { (age, birth) => [error] ^
错误完全不知道在说什么,经典的方法,把错误信息google一下也没有找到满意的答案。然后我开始自己思考,spray或者scala没理由强大到能你随便写一个类型A,as[A]就能把string转化好的啊,一定有什么magic的事情在背后默默地发生了。
于是首先ctrl+b(idea的快捷键,用了idea后我再也不想换回eclipse了,这不是广告)点进去之后发现是一个case class NameReceptacle,不是我想象的直接返回类型A。继续看parameters,点进去之后是:
def parameters(pdm: ParamDefMagnet): pdm.Out = pdm()
然后我就傻了,然后就是一堆implicit的东西,完全不知道在讲什么!然后去
doc里找,看到了
The Magnet Pattern
然后似乎看到了希望,点开,好长一篇E文啊,心生胆怯!不过后来还是坚持看完了,简单来说就是scala下一种为了解决传统的method overloading由于类型擦除等造成的一些不足的设计模式吧。然后由于‘age.as[Int]返回的是NameReceptacle[Int]类型,就找到了“重载”的方法
implicit def forNR[T](implicit fsod: FSOD[T]) = extractParameter[NameReceptacle[T], T] { nr ? filter(nr.name, fsod) }
可以看到它需要一个implicit的FSOD?是这个东西
import spray.httpx.unmarshalling.{ FromStringOptionDeserializer ? FSOD, _ }
type FromStringOptionDeserializer[T] = Deserializer[Option[String], T]
事情比较清楚了,spray确实没理由强大到随便给个类型A它都能把string转化为A。原来是需要一个Deserializer来做这件事情,因为是implicit,所以你不需要显示写出来。看样子‘age.as[Int]应该是spray默认有定义一个Deserializer[Option[String], Int]即把String转化为Int的implicit函数。那Date不行报错,应该就是没有Deserializer[Option[String], Date],那自己定义看看吧。
implicit def string2Date = new Deserializer[Option[String], Date] { override def apply(v1: Option[String]): Deserialized[Date] = v1 match { case None => Left(ContentExpected) case Some(str) => { val sdf = new SimpleDateFormat("yyyy-MM-dd") try { val date = sdf.parse(str) Right(date) } catch { case _: Throwable => Left(MalformedContent("format yyyy-MM-dd")) } } } }
再编译,没问题,运行也ok!
后来发现object Deserializer里有一个方法,可以把普通函数f:A=> B“提升”为Deserializer
implicit def fromFunction2Converter[A, B](implicit f: A ? B) = new Deserializer[A, B] { def apply(a: A) = { try Right(f(a)) catch { case NonFatal(ex) ? Left(MalformedContent(ex.toString, ex)) } } }
于是可以简化为
implicit def str2Date(str: String) = { val sdf = new SimpleDateFormat("yyyy-MM-dd") sdf.parse(str) }
看上去清爽许多了!
最后说一个小技巧,一开始没明白‘age.as[Int]是什么类型,一种方法是ctrl+b点进去看看,还有一种,你可以定义一个
val x = ‘age.as[Int]
然后把鼠标移动到x,alt+enter,选择"add type annotation to value definition"就变成
val x: NameReceptacle[Int] = ‘age.as[Int]
这在某些情况下还是很方便的。
scala确实很强大灵活,学习曲线有高,继续努力。。。。。。。。