前振り
Redstone.dart のinstallationのソースを見ると、@app.Route("/")
というコードが出てきます。
import 'package:redstone/server.dart' as app;
@app.Route("/")
helloWorld() => "Hello, World!";
main() {
app.setupConsoleLog();
app.start();
}
このコードを起動して localhost:8080 をブラウザで開くとHelloWorldになりますし、
@app.Route("/")
の行だけをコメントアウトするとNotFoundになります。
実際にRedstoneのRouteのソースを読んでも、特に特殊なメタメタした処理などない、ただのconstコンストラクタを持った普通のクラスになっています。
以下の引用部分は、Redstone.RouteクラスのDocの翻訳です。(ちなみにRedstone.dartはMITライセンスで提供されています)
ターゲットを定義するためのアノテーション
[urlTemplate] はターゲットのURLで、:
から始まるプレフィックすをつける事で引数を設定する事もできます。
[methods] はこのターゲットにおいて許可されるHTTPメソッドで、デフォルトではGETが指定されています。
[responseType] は、レスポンスのContent-Typeです。もし指定が無ければメソッドの返り値を元にフレームワークが決定します。
[statusCode] は、レスポンスのステータスのデフォルト値で、もし氏知恵がない場合は200が使われます。
[allowMultipartRequest] がtrueの場合は、Routeクラスは(ファイルアップロードなどの)multipartリクエストを受け付けられるようになります。
使用例:
@app.Route('/user/:username', methods: const[app.GET, app.POST])
helloUser(String username) => "Hello, $username";
このように、たかがアノテーションごときでかなりいろんな設定を持たせる事ができるようで、推測するに定数的な動作をさせるにあたってかなりパワフルに、かつ、コードを適切に分割できる強いツール(構文)なのかと思われますが、いまいち理解できてないので公式ドキュメントを読んでみます。
なお、公式ドキュメントに出てくる「Type Annotation」とは型注釈の事で、var hoge = something()
ではなく String hoge = something()
のように型指定する事を指すようです。
@hoge
に関しては、metadata
とよばれます。
ここから下が翻訳です。
メタデータ
原文: https://www.dartlang.org/docs/dart-up-and-running/ch02.html#metadata (2014/12/14 時点のもの)
なお、公式ドキュメントはCreative Commons 3.0とBSDライセンスで提供されており、翻訳に際して著者に連絡を取ったりはしていませんが、問題があればご指摘ください。
コードに付加情報を与えるには、メタデータを使います。メタデータアノテーションは@
から始まり、コンパイル時定数(例えばdeprecated)を参照するか、constantコンストラクタを呼び出します。
全てのDartコードで、次の3つのアノテーションを利用する事ができます。@deprecated
、@override
、@proxy
の3つです。@override
と@proxy
の使用例は Extending a class を参照してください。ここでは、@deprecated
アノテーションの使い方を見てみましょう。
class Television {
/// _Deprecated: 代わりに [turnOn] を使ってください_
@deprecated // Metadata; activate()を使おうとすると Dart Editorが警告を出します
void activate() {
turnOn();
}
/// テレビの電源をオンにします
void turnOn() {
print('on!');
}
}
好きなメタデータアノテーションを定義する事もできます。ここでは、2つの引数を受け取る @todo
アノテーションを定義してみましょう。
library todo;
class todo {
final String who;
final String what;
const todo(this.who, this.what);
}
そして、次にこの @todo
アノテーションを利用してみましょう。
import 'todo.dart';
@todo('seth', 'make this do something')
void doSomething() {
print('do something');
}
メタデータは、library
、class
、typedef
、型パラメータ、コンストラクタ、factory
、関数、フィールド、パラメータ、変数などの宣言や、インポートやエクスポート命令より前に現れます。リフレクションを利用する事で、実行時にメタデータを取得する事ができます。
翻訳ここまで。
つまりReflectionしろってこと?
なるほどわからん。が、基本的にはIDEが警告を出してくれる程度のものでしかなくて(これは想像どおり)、これを有意に利用しようとするにはReflectionが必要との事なのだろうか?
という事は、Redstone.dart でもどこかでリフレクションしているはずなのでコードを見てみました。
実際は複雑な処理がいろいろ行われてるんですが、簡単にまとめると以下のような事をやっているようです。
import 'dart:mirrors';
class Foo {
final String _something;
const Foo(this._something);
}
@Foo("bar")
helloWorld() => "Hello, World!";
main() {
currentMirrorSystem()
.isolate
.rootLibrary
.declarations
.forEach((Symbol symbol, DeclarationMirror declaration) {
print("${symbol} のメタデータは ${declaration.metadata} です");
});
}
これの出力は
Symbol("Foo") のメタデータは [] です
Symbol("main") のメタデータは [] です
Symbol("helloWorld") のメタデータは [InstanceMirror on Instance of 'Foo'] です
のようになり、Symbol helloWorld
のメタデータとして [InstanceMirror on Instance of 'Foo']
、つまりFooのconstインスタンスを取得できる事がわかります。
実際の処理はRedstone.dartのsetup_impl.dartの_scanHandlers()関数あたりを追いかけると知る事ができます。
感想
基本的には @hoge
アノテーションは、メタデータ、つまり付加情報でしかなく、IDE等においての開発の手助けになる、が主な利用目的である事がわかります。
しかしながらReflectionを利用する事で、メタデータを取得する事ができ、そのメタデータにはコンパイル時定数かconstインスタンスが自由に設定する事ができるので、今回のRedstone.dartのルーティング設定を行うというのは非常にGoodなアイディアである事が分かります。
Reflection周りの操作は基本的には重く、可能な限り避けるべきだと思っていますが、アプリケーション起動時に1度だけReflectionを行って、以後その設定を使いまわすというのであれば、Reflectionのコストを気にする必要もありませんし、コード上もすっきりして見やすく、Reflection周りの処理をライブラリ側(今回でいえばRedstone.dart側)が隠蔽してくれるという前提で考えると、使い道も広がってくるなーと思いました。
とはいえ、@deprecated
、@override
、@proxy
についてはDartが元々定義しているアノテーションなので、よくよく出てきますので、意味や使いドコロについては知っておくと良いと思います。