获取对象中调用者类名称的最佳方法是什么?

时间:2015-11-25 06:36:41

标签: scala

我可以使用此功能:

scala> object LOGGER {
     | def warning(msg: String)(implicit className:String) = {
     |   className
     | }
     | }
defined object LOGGER

scala> class testing {
     | lazy implicit val className = this.getClass.getName
     | def test = LOGGER.warning("Testing")
     | }
defined class testing

scala> val obj = new testing()
obj: testing = testing@11fb4f69

scala> obj.test
res51: String = testing <=======

scala> class testing2 {
     | lazy implicit val className = this.getClass.getName
     | def test = LOGGER.warning("Testing")
     | }
defined class testing2

scala> val obj2 = new testing2()
obj2: testing2 = testing2@2ca3a203


scala> obj2.test
res53: String = testing2 <=====

我也尝试在object LOGGER中使用Thread.currentThread.getStackTrace,但无法在testing函数中打印调用类warning。 还有其他方法吗?

2 个答案:

答案 0 :(得分:3)

动态变量

一种方法是DymamicVariable

import scala.util.DynamicVariable
object LOGGER {
  val caller = new DynamicVariable[String]("---")
  def time = new Date().toString
  def warning(msg: String) = println(s"[${caller.value} : $time] $msg")
}

trait Logging {
  def logged[T](action: => T) = LOGGER.caller.withValue(this.getClass.getName)(action)
}

class testing extends Logging {
  def test = logged {
    //some actions
    LOGGER.warning("test something")
    //some other actions
  }
}

val t = new testing

t.test

将打印类似

的内容
  

[测试:11月25日星期三11:29:23 MSK 2015]测试一些东西

或者不是在Logging中混合就可以直接使用

class testing {
  def test = LOGGER.caller.withValue(this.getClass.getName) {
    //some actions
    LOGGER.warning("test something")
    //some other actions
  }
}

另一个更强大,但更复杂的支持方法是构建一些简单的macro

您可以在其他来源中定义,最好在其他子项目中定义

import scala.reflect.macros.blackbox.Context
import scala.language.experimental.macros
class LoggerImpl(val c: Context) {
  import c.universe._

  def getClassSymbol(s: Symbol): Symbol = if (s.isClass) s else getClassSymbol(s.owner)

  def logImpl(msg: Expr[String]): Expr[Unit] = {
    val cl = getClassSymbol(c.internal.enclosingOwner).toString
    val time = c.Expr[String](q"new java.util.Date().toString")
    val logline = c.Expr[String](q""" "[" + $cl + " : " + $time + "]" + $msg """)
    c.Expr[Unit](q"println($logline)")
  }
}

object Logger {
  def warning(msg: String): Unit = macro LoggerImpl.logImpl
}

现在您无需更改测试类:

class testing {
  def test = {
    //some actions
    Logger.warning("something happen")
    //some other actions
  }
}

看到所需的输出。

Thsi可能是运行时堆栈内省的非常有效的替代方法

答案 1 :(得分:0)

我在我的自定义类加载器项目中使用这种技术来获取堆栈中第一个类的名称,而不是在我的包中。一般的想法是从UrlClassloader复制的。

String callerClassName="";
StackTraceElement[] stackTrace=Thread.currentThread().getStackTrace();
for (int i=1; i < stackTrace.length; i++) {
  String candidateClassName=stackTrace[i].getClassName();
  if(!candidateClassName.startsWith("to.be.ignored") && 
     !candidateClassName.startsWith("java")){
    callerClassName=candidateClassName;
    break;
  }
}

这种方法有它的缺点,因为它只获取类的名称,而不是实际的类,甚至更好的对象。