Scala反射更新案例类val

时间:2014-01-29 12:33:33

标签: scala reflection

我在这里使用scala和slick,并且我有一个baserepository,负责执行我的类的基本操作。 对于设计决策,我们确实有updateTime和createdTime列全部由应用程序处理,而不是由数据库中的触发器处理。这两个字段都是joda DataTime实例。 这些字段定义为两个特征,分别称为HasUpdatedAt和HasCreatedAt,用于表

trait HasCreatedAt {
    val createdAt: Option[DateTime]
}

case class User(name:String,createdAt:Option[DateTime] = None) extends HasCreatedAt

我想知道如何使用反射来调用用户复制方法,以便在数据库插入方法期间更新createdAt值。

在@vptron和@ kevin-wright评论之后编辑

我有这样的回购

trait BaseRepo[ID, R] {

    def insert(r: R)(implicit session: Session): ID
  }

我想只实现一次插入,并且我想创建它以进行更新,这就是为什么我没有使用复制方法,否则我需要在使用createdAt列的任何地方实现它。

2 个答案:

答案 0 :(得分:5)

这个问题在这里得到了回答,以帮助其他人解决这类问题。 我最终使用此代码使用scala反射执行我的case类的复制方法。

import reflect._
import scala.reflect.runtime.universe._
import scala.reflect.runtime._

class Empty

val mirror = universe.runtimeMirror(getClass.getClassLoader)
// paramName is the parameter that I want to replacte the value
// paramValue is the new parameter value
def updateParam[R : ClassTag](r: R, paramName: String, paramValue: Any): R = {

  val instanceMirror = mirror.reflect(r)
  val decl = instanceMirror.symbol.asType.toType
  val members = decl.members.map(method => transformMethod(method, paramName, paramValue, instanceMirror)).filter {
    case _: Empty => false
    case _ => true
  }.toArray.reverse

  val copyMethod = decl.declaration(newTermName("copy")).asMethod
  val copyMethodInstance = instanceMirror.reflectMethod(copyMethod)

  copyMethodInstance(members: _*).asInstanceOf[R]
}

def transformMethod(method: Symbol, paramName: String, paramValue: Any, instanceMirror: InstanceMirror) = {
  val term = method.asTerm
  if (term.isAccessor) {
    if (term.name.toString == paramName) {
      paramValue
    } else instanceMirror.reflectField(term).get
  } else new Empty
}

有了这个,我可以执行我的案例类的复制方法,替换确定的字段值。

答案 1 :(得分:1)

正如评论所说,不要使用反射来改变val。你会用java最终变量吗?它使你的代码做出了意想不到的事情。如果需要更改val的值,请不要使用val,请使用var。

trait HasCreatedAt {
    var createdAt: Option[DateTime] = None
}

case class User(name:String) extends HasCreatedAt

尽管在案例类中使用var可能会带来一些意想不到的行为,例如:复制将无法按预期工作。这可能导致更倾向于不使用案例类。

另一种方法是使insert方法返回case类的更新副本,例如:

trait HasCreatedAt {
    val createdAt: Option[DateTime]
    def withCreatedAt(dt:DateTime):this.type
}

case class User(name:String,createdAt:Option[DateTime] = None) extends HasCreatedAt {
    def withCreatedAt(dt:DateTime) = this.copy(createdAt = Some(dt))
}

trait BaseRepo[ID, R <: HasCreatedAt] {
    def insert(r: R)(implicit session: Session): (ID, R) = {
        val id = ???//insert into db
        (id, r.withCreatedAt(??? /*now*/))
    }
}

修改

由于我没有回答你原来的问题,你可能知道你在做什么,我正在添加一种方法来做到这一点。

import scala.reflect.runtime.universe._

val user = User("aaa", None)
val m = runtimeMirror(getClass.getClassLoader)
val im = m.reflect(user)
val decl = im.symbol.asType.toType.declaration("createdAt":TermName).asTerm
val fm = im.reflectField(decl)
fm.set(??? /*now*/)

但是,请不要这样做。阅读this stackoveflow answer以了解它可能导致的结果(vals映射到最终字段)。