Skip to content

Instantly share code, notes, and snippets.

@asufana
Last active December 14, 2015 02:58
Show Gist options
  • Save asufana/9866173 to your computer and use it in GitHub Desktop.
Save asufana/9866173 to your computer and use it in GitHub Desktop.
PlayFramework1起動時処理

PlayFramework1起動時処理

だいたいで書く

クラス図

Server

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));
    }

HttpServerPipelineFactory

  • 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;
    }
}

PlayHandler

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));

Invoker

処理呼び出しクラス

  • リクエスト毎にスレッドを与えて処理する
  • スレッドプールに空きがなければキューイング
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);
    }

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;

NettyInvocation

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

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;
                }
            }
    }

ApplicationClassloader

カスタムクラスローダ

  • 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()]));
                }
        }

ApplicationClass

クラスラッパー

    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;
        }

PluginCollection

Pluginクラスのコレクション

public class PluginCollection {

    //登録されたプラグインのenhance処理を呼び出し
    public void enhance(ApplicationClasses.ApplicationClass applicationClass){
        for (PlayPlugin plugin : getEnabledPlugins()) {
                plugin.enhance(applicationClass);
        }
    }

JPAPlugin

JPAプラグイン

public class JPAPlugin extends PlayPlugin {

    @Override
    //JPAEnhancer呼び出し
    public void enhance(ApplicationClass applicationClass) throws Exception {
        new JPAEnhancer().enhanceThisClass(applicationClass);
    }

PlayPlugin

プラグイン機能の抽象基底クラス

  • PlayPlugin には、以下サブクラスが存在する
  • JPAPlugin
  • Evolutions
  • WS
  • その他
public abstract class PlayPlugin implements Comparable<PlayPlugin> {

JPAEnhancer

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

クラス拡張の抽象基底クラス

  • Enhancer には、以下サブクラスが存在する
  • JPAEnhancer(JPAユーティリティメソッドの追加)
  • ControllersEnhancer(コントローラの public static なメソッドを拡張する)
  • PropertiesEnhancer(アクセアを追加してBeans化する)
  • SigEnhancer
  • MailerEnhancer
  • LocalvariablesNamesEnhancer
  • ContinuationEnhancer
public abstract class Enhancer {

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment