だいたいで書く
PlayFrameworkエントリポイント
- JBoss Nettyサーバ起動してポートをリッスンする
- Nettyイベント時の振る舞い(ハンドラ)の組み合わせ(パイプライン)を設定しておく
public class Server {
//エントリポイント
public static void main(String[] args) throws Exception {
File root = new File(System.getProperty("application.path"));
Play.init(root, System.getProperty("play.id", ""));
//サーバ起動
new Server(args);
}
//Netty起動クラス
public Server(String[] args) {
System.setProperty("file.encoding", "utf-8");
final Properties p = Play.configuration;
//公開ポート番号の設定
httpPort = Integer.parseInt(getOpt(args, "http.port", p.getProperty("http.port", "-1")));
httpsPort = Integer.parseInt(getOpt(args, "https.port", p.getProperty("https.port", "-1")));
if (httpPort == -1 && httpsPort == -1) {
httpPort = 9000;
}
//リッスン制御アドレスの設定
InetAddress address = null;
InetAddress secureAddress = null;
if (p.getProperty("http.address") != null) {
address = InetAddress.getByName(p.getProperty("http.address"));
} else if (System.getProperties().containsKey("http.address")) {
address = InetAddress.getByName(System.getProperty("http.address"));
}
//nettyサーバの起動ヘルパを生成
ServerBootstrap bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(
Executors.newCachedThreadPool(), Executors.newCachedThreadPool()));
//nettyイベント時の振る舞いを設定
bootstrap.setPipelineFactory(new HttpServerPipelineFactory());
//リッスンポートを開く
bootstrap.bind(new InetSocketAddress(address, httpPort));
}
- Nettyパイプラインを設定
- ハンドラの組み合わせがパイプライン
- リクエスト毎にこのパイプラインを通って処理される
参考:HandlerとChannelPipeline
public class HttpServerPipelineFactory implements ChannelPipelineFactory {
//NettyChannelPipeline設定
public ChannelPipeline getPipeline() throws Exception {
//HTTP サーバがレスポンスとして返すコンテンツの最大長
Integer max = Integer.valueOf(Play.configuration.getProperty("play.netty.maxContentLength", "-1"));
ChannelPipeline pipeline = pipeline();
//Playのflashスコープのリクエストを処理する
pipeline.addLast("flashPolicy", new FlashPolicyHandler());
//httpリクエストの処理ハンドラ(Netty標準)
pipeline.addLast("decoder", new HttpRequestDecoder());
//複数チャンクレスポンスのマージ
pipeline.addLast("aggregator", new StreamChunkAggregator(max));
//httpレスポンスの処理ハンドラ(Netty標準)
pipeline.addLast("encoder", new HttpResponseEncoder());
//httpレスポンスのチャンク出力ハンドラ(Netty標準)
pipeline.addLast("chunkedWriter", playHandler.chunkedWriteHandler);
//Playハンドラを登録
pipeline.addLast("handler", new PlayHandler());
return pipeline;
}
}
Nettyイベントで呼び出されるPlay振る舞いハンドラ
- Netty のイベントハンドラ SimpleChannelUpstreamHandler を継承
- NettyInvocationを生成し、ここからPlayのフレームワーク処理に入って行く
public class PlayHandler extends SimpleChannelUpstreamHandler {
@Override
//HTTPリクエスト受信時のイベント実装
public void messageReceived(final ChannelHandlerContext ctx,
final MessageEvent messageEvent) throws Exception {
//フレームワーク処理呼び出し
Invoker.invoke(new NettyInvocation(request,
response,
ctx,
nettyRequest,
messageEvent));
処理呼び出しクラス
- リクエスト毎にスレッドを与えて処理する
- スレッドプールに空きがなければキューイング
public class Invoker {
//スレッドを与えてInvocationクラスを実行する
public static Future<?> invoke(final Invocation invocation) {
Monitor monitor = MonitorFactory.getMonitor("Invoker queue size", "elmts.");
monitor.add(executor.getQueue().size());
invocation.waitInQueue = MonitorFactory.start("Waiting for execution");
//Runnableクラスを実行する
return executor.submit(invocation);
}
抽象基底な処理クラス
- Future呼び出しのためRunnableインターフェースを持つ
- Future/Promiseによる非同期・並列処理を可能にする
- Invocationのサブクラスは後述
public static abstract class Invocation implements Runnable {
@Override
//executor.submit()から呼び出される
//PlayFrameworkのフレームワーク処理
public void run() {
preInit();
if (init()) {
before();
execute();
after();
onSuccess();
}
}
//初期化処理
public boolean init() {
//ソース変更有無を確認してHOTデプロイする
Play.detectChanges();
if (!Play.started) {
//DEVモードはここでPlay開始
Play.start();
}
}
//詳細処理はサブクラスで実装
public abstract void execute() throws Exception;
Invoker.Invocationのサブクラス
- Invocation には、起動種別毎に以下のサブクラスが存在する
- NettyInvocation(通常リクエストからの呼び出し時)
- DirectInvocation(同一スレッドから呼び出される)
- WebSocketInvocation(WebSocket呼び出し時時)
- Job(非同期呼び出し時)
public class NettyInvocation extends Invoker.Invocation {
//コンストラクタ
public NettyInvocation(Request request, Response response, ChannelHandlerContext ctx, HttpRequest nettyRequest, MessageEvent e) {
this.ctx = ctx;
this.request = request;
this.response = response;
this.nettyRequest = nettyRequest;
this.event = e;
}
@Override
//初期化処理
public boolean init() {
Thread.currentThread().setContextClassLoader(Play.classloader);
Request.current.set(request);
Response.current.set(response);
try {
if (Play.mode == Play.Mode.DEV) {
//routeファイルが更新されていたら再読み込みする
Router.detectChanges(Play.ctxPath);
}
//リクエストされたrouteの存在確認
Router.routeOnlyStatic(request);
//初期処理
super.init();
}
}
@Override
//処理
public void execute() throws Exception {
// Check the exceeded size before re rendering so we can render the error if the size is exceeded
saveExceededSizeError(nettyRequest, request, response);
ActionInvoker.invoke(request, response);
}
Playフレームワークのメインクラス
public class Play {
//各種ファイルの変更有無確認
public static synchronized void detectChanges() {
try {
//ソースファイルが更新されていたら再コンパイルする
classloader.detectChanges();
//routeファイルが更新されていたら再読み込みする
Router.detectChanges(ctxPath);
for(VirtualFile conf : confs) {
if (conf.lastModified() > startedAt) {
//confファイルに変更があったらPlayを再起動する
start();
return;
}
}
}
カスタムクラスローダ
- java.lang.ClassLoaderを継承
- ClassラッパであるApplicationClassをハンドルする
public class ApplicationClassloader extends ClassLoader {
//ソースファイルが更新されていたら再コンパイルする
public void detectChanges() {
//変更されたクラスの洗い出し
List<ApplicationClass> modifieds = new ArrayList<ApplicationClass>();
for (ApplicationClass applicationClass : Play.classes.all()) {
//最終変更時刻で判断
if (applicationClass.timestamp < applicationClass.javaFile.lastModified()) {
applicationClass.refresh();
modifieds.add(applicationClass);
}
}
//Setでユニークを取る
Set<ApplicationClass> modifiedWithDependencies = new HashSet<ApplicationClass>();
modifiedWithDependencies.addAll(modifieds);
List<ClassDefinition> newDefinitions = new ArrayList<ClassDefinition>();
for (ApplicationClass applicationClass : modifiedWithDependencies) {
//クラス拡張処理とコンパイルを実行
applicationClass.enhance();
//処理遅延しないようにコンパイル結果をキャッシュしておく
BytecodeCache.cacheBytecode(applicationClass.enhancedByteCode, applicationClass.name, applicationClass.javaSource);
newDefinitions.add(new ClassDefinition(applicationClass.javaClass, applicationClass.enhancedByteCode));
}
//クラス再ロード
if (newDefinitions.size() > 0) {
Cache.clear();
try {
//変更クラスのみ再ロード
HotswapAgent.reload(newDefinitions.toArray(new ClassDefinition[newDefinitions.size()]));
}
}
クラスラッパー
public static class ApplicationClass {
//プラグインによるクラス拡張(JavassistによるByteCode操作)
public byte[] enhance() {
//拡張してbyteコードを保持する
Play.pluginCollection.enhance(this);
//プリコンパイル設定時にはコンパイルして配置する
if (System.getProperty("precompile") != null) {
try {
//byteコードをファイルに変換(.classファイル)
File f = Play.getFile("precompiled/java/" + (name.replace(".", "/")) + ".class");
f.getParentFile().mkdirs();
FileOutputStream fos = new FileOutputStream(f);
fos.write(this.enhancedByteCode);
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return this.enhancedByteCode;
}
Pluginクラスのコレクション
public class PluginCollection {
//登録されたプラグインのenhance処理を呼び出し
public void enhance(ApplicationClasses.ApplicationClass applicationClass){
for (PlayPlugin plugin : getEnabledPlugins()) {
plugin.enhance(applicationClass);
}
}
JPAプラグイン
public class JPAPlugin extends PlayPlugin {
@Override
//JPAEnhancer呼び出し
public void enhance(ApplicationClass applicationClass) throws Exception {
new JPAEnhancer().enhanceThisClass(applicationClass);
}
プラグイン機能の抽象基底クラス
- PlayPlugin には、以下サブクラスが存在する
- JPAPlugin
- Evolutions
- WS
- その他
public abstract class PlayPlugin implements Comparable<PlayPlugin> {
JPAクラス拡張
- Enhancerクラスを継承
- Javassist CtClass で既存クラスをバイトコード拡張する
- 各種JPAユーティリティメソッドの追加
public class JPAEnhancer extends Enhancer {
public void enhanceThisClass(ApplicationClass applicationClass) throws Exception {
//Javassistオブジェクト取得
CtClass ctClass = makeClass(applicationClass);
//JPABaseを継承していること
if (!ctClass.subtypeOf(classPool.get("play.db.jpa.JPABase"))) {
return;
}
//@Entityアノテーションが付加されていること
if (!hasAnnotation(ctClass, "javax.persistence.Entity")) {
return;
}
String entityName = ctClass.getName();
//countメソッド追加
CtMethod count = CtMethod.make("public static long count() { return play.db.jpa.JPQL.instance.count(\"" + entityName + "\"); }", ctClass);
ctClass.addMethod(count);
//findAllメソッド追加
CtMethod findAll = CtMethod.make("public static java.util.List findAll() { return play.db.jpa.JPQL.instance.findAll(\"" + entityName + "\"); }", ctClass);
ctClass.addMethod(findAll);
//Byteコードを保持
applicationClass.enhancedByteCode = ctClass.toBytecode();
ctClass.defrost();
}
クラス拡張の抽象基底クラス
- Enhancer には、以下サブクラスが存在する
- JPAEnhancer(JPAユーティリティメソッドの追加)
- ControllersEnhancer(コントローラの public static なメソッドを拡張する)
- PropertiesEnhancer(アクセアを追加してBeans化する)
- SigEnhancer
- MailerEnhancer
- LocalvariablesNamesEnhancer
- ContinuationEnhancer
public abstract class Enhancer {