首页 > 解决方案 > 是否有一种简单而通用的方法可以将 scala 对象的代码与要在对象主体代码之前和之后执行的代码一起包装?

问题描述

我正在尝试创建一些通用功能,允许我以通用方式添加要在 Scala 对象(使用 App 特征扩展)的主体之前和之后执行的代码 - 类似于 Scalatest 的 BeforeAndAfterAll,但用于通用代码执行。

我很想通过一个特征找到一种方法来做到这一点,但我做不到——比如

trait MyBeforeAndAfter {
    def before = println("I am executed before!")
    def after = println("I am executed after!")
}


object MyApp extends App with MyBeforeAndAfter {
    println("I am generic code in the body that's wrapped") 
}

运行应用程序时的目标输出将是(关键特征是排序):

I am executed before
I am generic code in the body that's wrapped
I am executed after

我考虑过但不知道该怎么做的两种方法是:

  1. 以某种方式将对象主体中的代码传递给一个负责包装的函数 - 但我不知道如何访问代码块作为我可以传递给函数的东西。我查看了 App trait 本身和 Scalatest 的 BeforeAndAfterAll 以了解他们的方法。前者似乎是在编译器中控制的。后者,我处于启动的第五级,我还不清楚它是如何工作的。

  2. 将之前和之后拆分为单独的特征并将它们混合在一起,虽然特征排序非常简单,但我看不到任何方法可以注释特征应该在基/目标对象/类之后执行。

堆栈溢出之神...帮助?

根据 Luis 的问题进行编辑 - 基本上你为什么要这样做?我最近正在使用 akka 进行大量构建,并且使用 akka 有很多 ActorSystem 设置/拆卸样板 - 从配置中获取东西,站立系统等,我正在考虑清除一些样板的方法来制作主应用程序的独特部分更加明显。我在其他一些情况下也遇到了一些类似的愿望,我继续思考是否有一种通用的方法可以以“干净”的方式清除样板——这意味着类似于 App trait(不需要覆盖,只需 mixin 和go),还可以灵活地在块之前和之后执行代码。

标签: scalatraits

解决方案


标准方法是不在对象主体中运行代码,而是在某些方法中运行代码

trait MyBeforeAndAfter {
  def before() = println("I am executed before!")
  def after() = println("I am executed after!")
  def run(): Unit

  def main(args: Array[String]): Unit = {
    before()
    run()
    after()
  }
}


object MyApp extends MyBeforeAndAfter {
  override def run(): Unit = {
    println("I am generic code in the body that's wrapped")
  }
}

然后如果你运行MyApp它会打印

I am executed before!
I am generic code in the body that's wrapped
I am executed after!

如果你真的想在对象的主体中运行代码,你可以定义宏注解

import scala.annotation.StaticAnnotation
import scala.language.experimental.macros
import scala.reflect.macros.blackbox

class beforeAndAfter extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro BeforeAndAfterMacro.impl
}

object BeforeAndAfterMacro {
  def impl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
    import c.universe._
    annottees match {
      case q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$body }" :: Nil =>
        val parents1 = parents :+ tq"MyBeforeAndAfter"
        q"""$mods object $tname extends { ..$earlydefns } with ..$parents1 { $self =>
          def run(): Unit = {
            ..$body
          }
        }"""
      case _ =>
        c.abort(c.enclosingPosition, "annottee must be an object")
    }
  }
}

@beforeAndAfter
object MyApp {
  println("I am generic code in the body that's wrapped")
}

//Warning:scalac: object MyApp extends scala.AnyRef with MyBeforeAndAfter {
//  def <init>() = {
//    super.<init>();
//    ()
//  };
//  def run(): Unit = println("I am generic code in the body that\'s wrapped")
//}

推荐阅读