最近在公司开发了一个新项目。这个项目要求人工去编写配置文件,然后根据配置文件配置的函数去处理数据。学到的隐式转换终于排上用场了。假设这些函数就是对比两个Set内容,然后得到这俩Set的相关得分。如下,然后分别实现这些函数即可。

object ScoreFunction{
  implicit class FunctionGetterFaster(val functionConf: String) extends AnyVal {
    def getFunc: (Set[String], Set[String]) => Double = {
      functionConf match {
        case "numberCmp" => numberCmp
        case "dateCmp" => dateCmp
        case "setCmp" => setCmp
        case "sameChar" => sameChar
        case "editDistance" => editDistance
        case _ => (x: Set[String], y: Set[String]) => 0.0
      }
    }
  }
}

注:implicit实现了一个隐式转换,将字符串转换为FunctionGetterFaster对象。extends AnyVal又是为了不发生实际的转换,这么折腾半天其实就是为了实现"dateCmp".getFunc。又不想花费构造对象的开销。

   但是经过思考,觉得这样做法总觉得不优雅。首先人工编写了配置文件了,已经指明了函数名,再匹配字符串,再获得函数,总觉得多了一步。并且最大的问题是如果我要拓展一个函数,总是要再在match中编写一个。自然而然就想到了反射机制:
object ScoreFunction{
  import reflect.runtime.universe._
  implicit class FunctionReflector(val functionConf: String) extends AnyVal {
    def getFunc: (Set[String], Set[String]) => Double = {
      try{
        val mirror = runtimeMirror(getClass.getClassLoader)
        val instanceMirror = mirror.reflect(ScoreFunction)
        val methodSymbol = instanceMirror.symbol.toType.decl(TermName(functionConf)).asMethod
        val methodMirror = instanceMirror.reflectMethod(methodSymbol)
        def func(v1: Set[String], v2: Set[String]): Double = methodMirror(v1,v2).asInstanceOf[Double]
        func
      }catch {
        case _:Throwable => (x: Set[String], y: Set[String]) => 0.0
      }
    }
    
  }
}
   这里根据函数名取到并调用,返回结果是Any类型,所以要把它转成最后返回值格式。中间可能出现错误,找不到函数、找到的函数返回值类型不对,这里用了一个大大的try catch来解决。

   将对应的函数实现。并且编写了一个conf类里面存了一个map来存所有对应函数的引用地址。满心欢喜得意洋洋的运行了,结果傻眼了。Spark提示函数不可序列化:
object not serializable (class: scala.reflect.runtime.JavaMirrors$JavaMirror$JavaVanillaMethodMirror2, value: method mirror for def dateCmp...
   病急乱投医,尝试让ScoreFunction继承Serializable接口,还不行。尝试给这个隐式转换类、隐式转换类的方法、里面涉及的值添加@transient注解来不序列化它,还不行。将其恢复到第一个版本,不用反射就可以了。但是我怎么能满足于此呢,辛辛苦苦写的反射,就这么放弃么?最后尝试了半天,发现给反射的几个变量加上lazy居然就可以了!
object ScoreFunction{
  import reflect.runtime.universe._
  implicit class FunctionReflector(val functionConf: String) extends AnyVal {
    def getFunc: (Set[String], Set[String]) => Double = {
      try{
        lazy val mirror = runtimeMirror(getClass.getClassLoader)
        lazy val instanceMirror = mirror.reflect(ScoreFunction)
        lazy val methodSymbol = instanceMirror.symbol.toType.decl(TermName(functionConf)).asMethod
        lazy val methodMirror = instanceMirror.reflectMethod(methodSymbol)
        def func(v1: Set[String], v2: Set[String]): Double = methodMirror(v1,v2).asInstanceOf[Double]
        func
      }catch {
        case _:Throwable => (x: Set[String], y: Set[String]) => 0.0
      }
    }
    
  }
}
   通过仔细推敲,之所以提示不可序列化是因为我的conf的函数配置分发到节点的时候需要进行序列化,而序列化的内容就是我最后这个func,这个func包含了一个MethodMirror,罪魁祸首就是它不可序列化。StackOverflow上面还有人提了相关问题。还有人说他提交给scala了一些代码希望它能可序列化(当然没有采用,所以我现在还不可序列化)。而加上了lazy,相当于不对这个值进行序列化了,而是把这个隐式转换对象。整个打包发送到worker上,然后用的时候才去加载,就不涉及对MethodMirror进行序列化了。理论上讲这种方式应该可以解决绝大多数的spark报的值、函数不可序列化问题。也许是自己孤陋寡闻,第一次发现lazy居然可以这样用,发出来分享,有大牛的话多多交流赐教。