在用 Scala Macro Annotation 實現以前, 我是根據 Akka 官方文檔建議的 擴展 機制來綁定配置:html
class SettingsImpl(config: Config) extends Extension { import config._ val BrokerHost = getString("kafka_consumer.broker.host") val BrokerPort = getInt("kafka_consumer.broker.port") } object Settings extends ExtensionId[SettingsImpl] with ExtensionIdProvider { def createExtension(system: ExtendedActorSystem) = new SettingsImpl(system.settings.config) def lookup() = Settings } class KafkaConsumer extends Actor { val settings = Settings(context.system) val brokerHost = settings.BrokerHost val brokerPort = settings.BrokerPort def receive = ??? }
application.conf
除了akka
外, 加入擴展的內容:java
akka { ... } kafka_consumer.broker { host:10.0.0.1 port:9092 }
隨着配置項個數增長一個量級, 這類 getXxx(...)
寫得也是讓我 醉了, 更不要談重構的時候...[不忍直視]git
我開始尋思着能不能這樣:github
class KafkaConsumer extends Actor { @conf val brokerHost = "" @conf val brokerPort = 0 }
而後讓編譯器 智能 的幫我 擋酒 , 她酒量可比我好太多了.json
下面就是我以 sbt-example-paradise 爲基礎實現的步驟:app
修改 Test.scala 爲:ide
object Test extends App { @hello val i = 0 println(i) }
執行 sbt clean run
, 不出意料, 報錯了:scala
[error] scala.MatchError: List(val i = 0) (of class scala.collection.immutable.$colon$colon) [error] at helloMacro$.impl(Macros.scala:10) [error] @hello val i = 0 [error]
顯然 Macros.scala 中 match case
沒有考慮 @hello
在 val
上的狀況, 那不如先來看看它是啥:code
annottees.map(_.tree).toList match { case t :: Nil => println(t.getClass); t }
其實前面的錯誤信息已經 暗示了 t
的內容是 val i = 0
, 所以println(t)
已經沒有意義了, 但弄清它的類型, 有助於替換 =
右邊的部分 .htm
sbt clean run
:
class scala.reflect.internal.Trees$ValDef [info] Running Test 0
去查看 ValDef
源碼, 你會發現:
case class ValDef(mods: Modifiers, name: TermName, tpt: Tree, rhs: Tree) ...
這一步已經涉及抽象語法樹的範疇, 有興趣的請閱讀 reflection 中的
Tree
的部分
啊哈, 這也就意味着能夠這樣寫:
annottees.map(_.tree).toList match { case (t @ ValDef(mods, name, tpt, rhs)) :: Nil => println(rhs); t }
直覺告訴我 rhs
就是 0
, sbt clean run
:
0 [info] Running Test 0
如今, 只要弄清楚怎麼構造我想要的 rhs
就能夠達到目的了. 怎麼作呢, 看看 Macros.scala 的示範, 不難想到:
annottees.map(_.tree).toList match { case ValDef(mods, name, tpt, rhs) :: Nil => ValDef(mods, name, tpt, q"10") }
sbt clean run
:
[info] Running Test 10
q"..." 是一種叫 quasiquotes 的特性, 它使得構造語法樹過程的變得異常的簡單
請不要天真的覺得將 q"0"
改爲 q"""config.getInt("test.i")"""
就大功告成, 後面還有不少問題:
config
對象引用從哪裏來?@conf
修飾的值類型怎麼判斷?case q"..." =>
來替代 case ValDef(...) =>
?2.10
到 2.11
版本之間的差別?這些問題的留個你們一塊兒思考, 也能夠關注個人開源項目 config-annotation 與我一塊兒探討.
更爲複雜的案例請見json-annotation.