成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Play framework源碼解析 Part2:Server與ServletWrapper

JiaXinYi / 3144人閱讀

摘要:是一個(gè)抽象類,繼承了接口,它的方法是這個(gè)類的核心。因?yàn)榭赡苄枰粋€(gè)返回值,所以它同時(shí)繼承了接口來提供返回值。

注:本系列文章所用play版本為1.2.6

在上一篇中我們剖析了Play framework的啟動(dòng)原理,很容易就能發(fā)現(xiàn)Play framework的啟動(dòng)主入口在play.server.Server中,在本節(jié),我們來一起看看Server類中主要發(fā)生了什么。

Server類

既然是程序運(yùn)行的主入口,那么必然是由main方法進(jìn)入的,Server類中的main方法十分簡單。源碼如下:

    public static void main(String[] args) throws Exception {
        File root = new File(System.getProperty("application.path"));
        //獲取參數(shù)中的precompiled
        if (System.getProperty("precompiled", "false").equals("true")) {
            Play.usePrecompiled = true;
        }
        //獲取參數(shù)中的writepid
        if (System.getProperty("writepid", "false").equals("true")) {
            //這個(gè)方法的作用是檢查當(dāng)前目錄下是否存在server.pid文件,若存在表明當(dāng)前已有程序在運(yùn)行
            writePID(root);
        }
        //Play類的初始化
        Play.init(root, System.getProperty("play.id", ""));
        if (System.getProperty("precompile") == null) {
            //Server類初始化
            new Server(args);
        } else {
            Logger.info("Done.");
        }
    }

main方法執(zhí)行的操作很簡單:

獲取程序路徑

檢查是否存在precompiled參數(shù)

檢查是否存在writepid參數(shù),若存在則檢查是否存在server.pid文件,若存在則表明已有程序在運(yùn)行,不存在則將當(dāng)前程序pid寫入server.pid

play類初始化

檢查是否存在precompile參數(shù)項(xiàng),若存在表示是個(gè)預(yù)編譯行為,結(jié)束運(yùn)行,若沒有則啟動(dòng)服務(wù)

這其中最重要的便是Play類的初始化以及Server類的初始化
這里我們先來看Server類的初始化過程,現(xiàn)在可以先簡單的將Play類的初始化理解為Play框架中一些常量的初始化以及日志、配置文件、路由信息等配置的讀取。
這里貼一下Server類的初始化過程:

    public Server(String[] args) {
        //設(shè)置文件編碼為UTF-8
        System.setProperty("file.encoding", "utf-8");
        //p為Play類初始化過程中讀取的配置文件信息
        final Properties p = Play.configuration;
        //獲取參數(shù)中的http與https端口信息,若不存在則用配置文件中的http與https端口信息
        httpPort = Integer.parseInt(getOpt(args, "http.port", p.getProperty("http.port", "-1")));
        httpsPort = Integer.parseInt(getOpt(args, "https.port", p.getProperty("https.port", "-1")));
        //若沒有配置則設(shè)置默認(rèn)端口為9000
        if (httpPort == -1 && httpsPort == -1) {
            httpPort = 9000;
        }
        //http與https端口不能相同
        if (httpPort == httpsPort) {
            Logger.error("Could not bind on https and http on the same port " + httpPort);
            Play.fatalServerErrorOccurred();
        }

        InetAddress address = null;
        InetAddress secureAddress = null;
        try {
            //獲取配置文件中的默認(rèn)http地址,若不存在則在系統(tǒng)參數(shù)中查找
            //之前還是參數(shù)配置大于配置文件,這里不知道為什么又變成了配置文件的優(yōu)先級(jí)高于參數(shù)配置,很迷
            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"));
            }

        } catch (Exception e) {
            Logger.error(e, "Could not understand http.address");
            Play.fatalServerErrorOccurred();
        }
        try {
            //同上,獲取https地址
            if (p.getProperty("https.address") != null) {
                secureAddress = InetAddress.getByName(p.getProperty("https.address"));
            } else if (System.getProperties().containsKey("https.address")) {
                secureAddress = InetAddress.getByName(System.getProperty("https.address"));
            }
        } catch (Exception e) {
            Logger.error(e, "Could not understand https.address");
            Play.fatalServerErrorOccurred();
        }
        //netty服務(wù)器啟動(dòng)類初始化,使用nio服務(wù)器,無限制線程池
        //這里的線程池是netty的主線程池與工作線程池,是處理連接的線程池,而Play實(shí)際執(zhí)行業(yè)務(wù)操作的線程池在另一個(gè)地方配置
        ServerBootstrap bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(
                Executors.newCachedThreadPool(), Executors.newCachedThreadPool())
        );
        try {
            //初始化http端口
            if (httpPort != -1) {
                //設(shè)置管道工廠類
                bootstrap.setPipelineFactory(new HttpServerPipelineFactory());
                //綁定端口
                bootstrap.bind(new InetSocketAddress(address, httpPort));
                bootstrap.setOption("child.tcpNoDelay", true);

                if (Play.mode == Mode.DEV) {
                    if (address == null) {
                        Logger.info("Listening for HTTP on port %s (Waiting a first request to start) ...", httpPort);
                    } else {
                        Logger.info("Listening for HTTP at %2$s:%1$s (Waiting a first request to start) ...", httpPort, address);
                    }
                } else {
                    if (address == null) {
                        Logger.info("Listening for HTTP on port %s ...", httpPort);
                    } else {
                        Logger.info("Listening for HTTP at %2$s:%1$s  ...", httpPort, address);
                    }
                }

            }

        } catch (ChannelException e) {
            Logger.error("Could not bind on port " + httpPort, e);
            Play.fatalServerErrorOccurred();
        }
        //下面是https端口服務(wù)器的啟動(dòng)過程,和http一致
        bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(
                Executors.newCachedThreadPool(), Executors.newCachedThreadPool())
        );

        try {
            if (httpsPort != -1) {
                //這里的管道工廠類變成了SslHttpServerPipelineFactory
                bootstrap.setPipelineFactory(new SslHttpServerPipelineFactory());
                bootstrap.bind(new InetSocketAddress(secureAddress, httpsPort));
                bootstrap.setOption("child.tcpNoDelay", true);

                if (Play.mode == Mode.DEV) {
                    if (secureAddress == null) {
                        Logger.info("Listening for HTTPS on port %s (Waiting a first request to start) ...", httpsPort);
                    } else {
                        Logger.info("Listening for HTTPS at %2$s:%1$s (Waiting a first request to start) ...", httpsPort, secureAddress);
                    }
                } else {
                    if (secureAddress == null) {
                        Logger.info("Listening for HTTPS on port %s ...", httpsPort);
                    } else {
                        Logger.info("Listening for HTTPS at %2$s:%1$s  ...", httpsPort, secureAddress);
                    }
                }

            }

        } catch (ChannelException e) {
            Logger.error("Could not bind on port " + httpsPort, e);
            Play.fatalServerErrorOccurred();
        }
        if (Play.mode == Mode.DEV) {
           // print this line to STDOUT - not using logger, so auto test runner will not block if logger is misconfigured (see #1222)
           //輸出啟動(dòng)成功,以便進(jìn)行自動(dòng)化測試
           System.out.println("~ Server is up and running");
        }
    }

server類的初始化沒什么好說的,重點(diǎn)就在于那2個(gè)管道工廠類,HttpServerPipelineFactory與SslHttpServerPipelineFactory

HttpServerPipelineFactory

HttpServerPipelineFactory類作用就是為netty服務(wù)器增加了各個(gè)ChannelHandler。

public class HttpServerPipelineFactory implements ChannelPipelineFactory {

    public ChannelPipeline getPipeline() throws Exception {

        Integer max = Integer.valueOf(Play.configuration.getProperty("play.netty.maxContentLength", "-1"));

        ChannelPipeline pipeline = pipeline();
        PlayHandler playHandler = new PlayHandler();

        pipeline.addLast("flashPolicy", new FlashPolicyHandler()); 
        pipeline.addLast("decoder", new HttpRequestDecoder());
        pipeline.addLast("aggregator", new StreamChunkAggregator(max));
        pipeline.addLast("encoder", new HttpResponseEncoder());
        pipeline.addLast("chunkedWriter", playHandler.chunkedWriteHandler);
        pipeline.addLast("handler", playHandler);

        return pipeline;
    }
}

pipeline依次添加了flash處理器,http request解碼器,流區(qū)塊聚合器,http response編碼器,區(qū)塊寫入器,play處理器
flash處理器和流區(qū)塊聚合器這里暫且不作詳細(xì)解析,讀者可以理解為這2者的作用分別為傳輸flash流和合并請求頭中有Transfer-Encoding:chunked的請求數(shù)據(jù)。
PlayHandler類是Play將netty接收到的request轉(zhuǎn)換為play的request并真正開始業(yè)務(wù)處理的入口類,我們先暫時(shí)放下PlayHandler,先來看看SslHttpServerPipelineFactory做了什么

SslHttpServerPipelineFactory

SslHttpServerPipelineFactory是https使用的管道工廠類,他除了添加了一下處理器外,最重要的是根據(jù)配置創(chuàng)建了https的連接方式。

    public ChannelPipeline getPipeline() throws Exception {

        Integer max = Integer.valueOf(Play.configuration.getProperty("play.netty.maxContentLength", "-1"));
        String mode = Play.configuration.getProperty("play.netty.clientAuth", "none");

        ChannelPipeline pipeline = pipeline();

        // Add SSL handler first to encrypt and decrypt everything.
        SSLEngine engine = SslHttpServerContextFactory.getServerContext().createSSLEngine();
        engine.setUseClientMode(false);

        if ("want".equalsIgnoreCase(mode)) {
            engine.setWantClientAuth(true);
        } else if ("need".equalsIgnoreCase(mode)) {
            engine.setNeedClientAuth(true);
        }

        engine.setEnableSessionCreation(true);

        pipeline.addLast("flashPolicy", new FlashPolicyHandler());
        pipeline.addLast("ssl", new SslHandler(engine));
        pipeline.addLast("decoder", new HttpRequestDecoder());
        pipeline.addLast("aggregator", new StreamChunkAggregator(max));
        pipeline.addLast("encoder", new HttpResponseEncoder());
        pipeline.addLast("chunkedWriter", new ChunkedWriteHandler());

        pipeline.addLast("handler", new SslPlayHandler());

        return pipeline;
    }

在設(shè)置完SSLEngine后,SslHttpServerPipelineFactory在decoder處理器前加了一個(gè)SslHandler來處理連接,這里不是使用playHandler作為處理器,而是使用SslPlayHandler,SslPlayHandler是PlayHandler的一個(gè)子類,就是重寫了連接和出錯(cuò)時(shí)的處理,這里不多做闡述。

PlayHandler

PlayHandler的作用是解析http request的類型,然后根據(jù)不同類型來進(jìn)行不同的處理,由于PlayHandler內(nèi)處理的東西較多,這里先附上一張大體流程圖以供讀者參考

messageReceived

messageReceived是一個(gè)Override方法,作用就是在netty消息傳遞至該handle時(shí)進(jìn)行相應(yīng)操作,方法代碼如下

    @Override
    public void messageReceived(final ChannelHandlerContext ctx, final MessageEvent messageEvent) throws Exception {
        if (Logger.isTraceEnabled()) {
            Logger.trace("messageReceived: begin");
        }

        final Object msg = messageEvent.getMessage();

        //判斷是否為HttpRequest的信息,不是則不進(jìn)行處理
        if (msg instanceof HttpRequest) {

            final HttpRequest nettyRequest = (HttpRequest) msg;

            // 若為websocket的初次握手請求,則進(jìn)行websocket握手過程
            if (HttpHeaders.Values.WEBSOCKET.equalsIgnoreCase(nettyRequest.getHeader(HttpHeaders.Names.UPGRADE))) {
                websocketHandshake(ctx, nettyRequest, messageEvent);
                return;
            }

            try {
                //將netty的request轉(zhuǎn)為Play的request
                final Request request = parseRequest(ctx, nettyRequest, messageEvent);

                final Response response = new Response();
                //Response.current為ThreadLocal類型,保證每個(gè)線程擁有多帶帶Response
                Http.Response.current.set(response);

                // Buffered in memory output
                response.out = new ByteArrayOutputStream();

                // Direct output (will be set later)
                response.direct = null;

                // Streamed output (using response.writeChunk)
                response.onWriteChunk(new Action() {

                    public void invoke(Object result) {
                        writeChunk(request, response, ctx, nettyRequest, result);
                    }
                });

                /*
                    查找Play插件(即繼承了PlayPlugin抽象類的類)中是否有該request的實(shí)現(xiàn)方法,若存在,則用Play插件的實(shí)現(xiàn)
                    Play自帶的插件實(shí)現(xiàn)有:
                        CorePlugin實(shí)現(xiàn)了/@kill和/@status路徑的訪問流程
                        DBPlugin實(shí)現(xiàn)了/@db路徑的訪問流程
                        Evolutions實(shí)現(xiàn)了/@evolutions路徑的訪問流程
                    這里將插件的訪問與其他訪問分開最主要原因應(yīng)該是插件訪問不需要啟動(dòng)Play,而其他命令必須先調(diào)用Play.start來啟動(dòng)
                */
                boolean raw = Play.pluginCollection.rawInvocation(request, response);
                if (raw) {
                    copyResponse(ctx, request, response, nettyRequest);
                } else {
                    /*
                        Invoker.invoke就是向Invoker類中的ScheduledThreadPoolExecutor提交任務(wù)并執(zhí)行
                        使用ScheduledThreadPoolExecutor是因?yàn)榭梢远〞r(shí),便于執(zhí)行異步任務(wù)
                        Invoker類可以理解為是各個(gè)不同容器任務(wù)轉(zhuǎn)向Play任務(wù)的一個(gè)轉(zhuǎn)換類,他下面有2個(gè)靜態(tài)內(nèi)部類,分別為InvocationContext和Invocation
                        InvocationContext內(nèi)存放了當(dāng)前request映射的controller類的annotation
                        Invocation則為Play任務(wù)的基類,他的實(shí)現(xiàn)類有:
                            Job:Play的異步任務(wù)
                            NettyInvocation:netty請求的實(shí)現(xiàn)類
                            ServletInvocation:Servlet容器中的實(shí)現(xiàn)類
                            WebSocketInvocation:websocket的實(shí)現(xiàn)類
                    */
                    Invoker.invoke(new NettyInvocation(request, response, ctx, nettyRequest, messageEvent));
                }

            } catch (Exception ex) {
                //處理服務(wù)器錯(cuò)誤
                serve500(ex, ctx, nettyRequest);
            }
        }

        // 如果msg是websocket,則進(jìn)行websocket接收
        if (msg instanceof WebSocketFrame) {
            WebSocketFrame frame = (WebSocketFrame) msg;
            websocketFrameReceived(ctx, frame);
        }

        if (Logger.isTraceEnabled()) {
            Logger.trace("messageReceived: end");
        }
    }

可以很清楚的發(fā)現(xiàn)PlayHandler處理請求就是依靠了各個(gè)不同的Invocation實(shí)現(xiàn)來完成,那我們就來看看Invocation他是如何一步步處理請求的。

Invocation

Invocation是一個(gè)抽象類,繼承了Runnable接口,它的run方法是這個(gè)類的核心。

    public void run() {
        //waitInQueue是一個(gè)監(jiān)視器,監(jiān)視了這個(gè)invocation在隊(duì)列中等待的時(shí)間
        if (waitInQueue != null) {
            waitInQueue.stop();
        }
        try {
            //預(yù)初始化,是對語言文件的清空
            preInit();
            //初始化
            if (init()) {
                //運(yùn)行執(zhí)行前插件任務(wù)
                before();
                //處理request
                execute();
                //運(yùn)行執(zhí)行后插件任務(wù)
                after();
                //成功執(zhí)行后的處理
                onSuccess();
            }
        } catch (Suspend e) {
            //Suspend類讓請求暫停
            suspend(e);
            after();
        } catch (Throwable e) {
            onException(e);
        } finally {
            _finally();
        }
    }

這里面的preInit()、before()、after()是沒有實(shí)現(xiàn)類的,用的就是Invocation類中的方法。我們先來看看Invocation類中對這些方法的實(shí)現(xiàn),然后再來看看他的實(shí)現(xiàn)類對這些方法的修改

init

    public boolean init() {
        //設(shè)置當(dāng)前線程的classloader
        Thread.currentThread().setContextClassLoader(Play.classloader);
        //檢查是否為dev模式,是則檢查文件是否修改,以進(jìn)行熱加載
        Play.detectChanges();
        //若Play未啟動(dòng),啟動(dòng)
        if (!Play.started) {
            if (Play.mode == Mode.PROD) {
                throw new UnexpectedException("Application is not started");
            }
            Play.start();
        }
        //InvocationContext中添加當(dāng)前映射方法的annotation
        InvocationContext.current.set(getInvocationContext());
        return true;
    }

Play.start可以先理解為啟動(dòng)Play服務(wù),具體展開將在之后mvc章節(jié)詳解

execute

execute在Invocation類中是一個(gè)抽象方法,需要實(shí)現(xiàn)類完成自己的實(shí)現(xiàn)

onSuccess

    public void onSuccess() throws Exception {
        Play.pluginCollection.onInvocationSuccess();
    }

執(zhí)行play插件中的執(zhí)行成功方法

onException

    public void onException(Throwable e) {
        Play.pluginCollection.onInvocationException(e);
        if (e instanceof PlayException) {
            throw (PlayException) e;
        }
        throw new UnexpectedException(e);
    }

執(zhí)行play插件中的執(zhí)行異常方法,然后拋出異常

suspend

    public void suspend(Suspend suspendRequest) {
        if (suspendRequest.task != null) {
            WaitForTasksCompletion.waitFor(suspendRequest.task, this);
        } else {
            Invoker.invoke(this, suspendRequest.timeout);
        }
    }

若暫停的請求有任務(wù),那么調(diào)用WaitForTasksCompletion.waitFor進(jìn)行等待,直到任務(wù)完成再繼續(xù)運(yùn)行請求
若沒有任務(wù),那就將請求通過ScheduledThreadPoolExecutor.schedule延時(shí)一段時(shí)間后執(zhí)行

_finally

    public void _finally() {
        Play.pluginCollection.invocationFinally();
        InvocationContext.current.remove();
    }

執(zhí)行play插件中的invocationFinally方法

NettyInvocation

NettyInvocation是netty請求使用的實(shí)現(xiàn)類,我們來依次看看他對init,execute,onSuccess等方法的實(shí)現(xiàn)上相比Invocation改變了什么

run

    @Override
    public void run() {
        try {
            if (Logger.isTraceEnabled()) {
                Logger.trace("run: begin");
            }
            super.run();
        } catch (Exception e) {
            serve500(e, ctx, nettyRequest);
        }
        if (Logger.isTraceEnabled()) {
            Logger.trace("run: end");
        }
    }

NettyInvocation的run方法和基類唯一的差別就是包了一層try catch來處理服務(wù)器錯(cuò)誤,這里有一點(diǎn)要注意,可以發(fā)現(xiàn),NettyInvocation的run有一層try catch來處理500錯(cuò)誤,PlayHandle中的messageReceived也有一層try catch來處理500錯(cuò)誤,我的理解是前者是用來處理業(yè)務(wù)流程中的錯(cuò)誤的,后者是為了防止意外錯(cuò)誤引發(fā)整個(gè)服務(wù)器掛掉所以又套了一層做保險(xiǎn)

init

    @Override
    public boolean init() {
        //設(shè)置當(dāng)前線程的classloader
        Thread.currentThread().setContextClassLoader(Play.classloader);
        if (Logger.isTraceEnabled()) {
            Logger.trace("init: begin");
        }
        //設(shè)置當(dāng)前線程的request和response
        Request.current.set(request);
        Response.current.set(response);
        try {
            //如果是dev模式檢查路徑更新
            if (Play.mode == Play.Mode.DEV) {
                Router.detectChanges(Play.ctxPath);
            }
            //如果是prod模式且靜態(tài)路徑緩存有當(dāng)前請求信息,則從緩存中獲取
            if (Play.mode == Play.Mode.PROD && staticPathsCache.containsKey(request.domain + " " + request.method + " " + request.path)) {
                RenderStatic rs = null;
                synchronized (staticPathsCache) {
                    rs = staticPathsCache.get(request.domain + " " + request.method + " " + request.path);
                }
                //使用靜態(tài)資源
                serveStatic(rs, ctx, request, response, nettyRequest, event);
                if (Logger.isTraceEnabled()) {
                    Logger.trace("init: end false");
                }
                return false;
            }
            //檢查是否為靜態(tài)資源
            Router.routeOnlyStatic(request);
            super.init();
        } catch (NotFound nf) {
            //返回404
            serve404(nf, ctx, request, nettyRequest);
            if (Logger.isTraceEnabled()) {
                Logger.trace("init: end false");
            }
            return false;
        } catch (RenderStatic rs) {
            //使用靜態(tài)資源
            if (Play.mode == Play.Mode.PROD) {
                synchronized (staticPathsCache) {
                    staticPathsCache.put(request.domain + " " + request.method + " " + request.path, rs);
                }
            }
            serveStatic(rs, ctx, request, response, nettyRequest, this.event);
            if (Logger.isTraceEnabled()) {
                Logger.trace("init: end false");
            }
            return false;
        }

        if (Logger.isTraceEnabled()) {
            Logger.trace("init: end true");
        }
        return true;
    }

可以看出,NettyInvocation的初始化就是在基類初始化之前判斷request的路徑,處理靜態(tài)資源以及處理路徑不存在的資源,這里也就說明了訪問路由設(shè)置中的靜態(tài)資源是不需要啟動(dòng)play服務(wù)的,是否意味著可以通過play搭建一個(gè)靜態(tài)資源服務(wù)器?

execute

    @Override
    public void execute() throws Exception {
        //檢查連接是否中斷
        if (!ctx.getChannel().isConnected()) {
            try {
                ctx.getChannel().close();
            } catch (Throwable e) {
                // Ignore
            }
            return;
        }
        //渲染之前檢查長度
        saveExceededSizeError(nettyRequest, request, response);
        //運(yùn)行
        ActionInvoker.invoke(request, response);
    }

ActionInvoker.invoke是play mvc的執(zhí)行入口方法,這個(gè)在之后會(huì)進(jìn)行詳解

success

    @Override
    public void onSuccess() throws Exception {
        super.onSuccess();
        if (response.chunked) {
            closeChunked(request, response, ctx, nettyRequest);
        } else {
            copyResponse(ctx, request, response, nettyRequest);
        }
        if (Logger.isTraceEnabled()) {
            Logger.trace("execute: end");
        }
    }

成功之后就判斷是否是chunked類型的request,若是則關(guān)閉區(qū)塊流并返回response,若不是則返回正常的response

Job

Job是Play任務(wù)的基類,用來處理異步任務(wù),Job雖然繼承了Invocation,但他并不會(huì)加入至Play的主線程池執(zhí)行,Job有自己多帶帶的線程池進(jìn)行處理。因?yàn)镴ob可能需要一個(gè)返回值,所以它同時(shí)繼承了Callable接口來提供返回值。
既然job能提供返回值,那真正起作用的方法就是call()而不是run(),我們來看一下他的call方法

    public V call() {
        Monitor monitor = null;
        try {
            if (init()) {
                before();
                V result = null;

                try {
                    lastException = null;
                    lastRun = System.currentTimeMillis();
                    monitor = MonitorFactory.start(getClass().getName()+".doJob()");
                    //執(zhí)行任務(wù)并返回結(jié)果
                    result = doJobWithResult();
                    monitor.stop();
                    monitor = null;
                    wasError = false;
                } catch (PlayException e) {
                    throw e;
                } catch (Exception e) {
                    StackTraceElement element = PlayException.getInterestingStrackTraceElement(e);
                    if (element != null) {
                        throw new JavaExecutionException(Play.classes.getApplicationClass(element.getClassName()), element.getLineNumber(), e);
                    }
                    throw e;
                }
                after();
                return result;
            }
        } catch (Throwable e) {
            onException(e);
        } finally {
            if(monitor != null) {
                monitor.stop();
            }
            _finally();
        }
        return null;
    }

可以看出,job的call方法和Invocation的run方法其實(shí)差不多,中間執(zhí)行job的代碼其實(shí)就是execute方法的實(shí)現(xiàn),但是有一點(diǎn)不同的是,job方法完成后不會(huì)調(diào)用onSuccess()方法。
這里只是提一下Job與Invocation類的關(guān)系,job任務(wù)的處理放在之后的插件篇再談

WebSocketInvocation

WebSocketInvocation是websocket請求的實(shí)現(xiàn)類,我們來具體看下他對Invocation的方法做了怎么樣的復(fù)寫

init

    @Override
    public boolean init() {
        Http.Request.current.set(request);
        Http.Inbound.current.set(inbound);
        Http.Outbound.current.set(outbound);
        return super.init();
    }

沒什么大的變動(dòng),就是在當(dāng)前線程下添加了出入站的通道信息

execute

    @Override
    public void execute() throws Exception {
        WebSocketInvoker.invoke(request, inbound, outbound);
    }

execute方法就是將request請求,出入站通道傳入WebSocketInvoker.invoke進(jìn)行處理,WebSocketInvoker.invoke的代碼如下,可以看出WebSocketInvoker.invoke方法也就是調(diào)用了ActionInvoker.invoke來對請求進(jìn)行處理。

    public static void invoke(Http.Request request, Http.Inbound inbound, Http.Outbound outbound) {
        try {
            if (Play.mode == Play.Mode.DEV) {
                WebSocketController.class.getDeclaredField("inbound").set(null, Http.Inbound.current());
                WebSocketController.class.getDeclaredField("outbound").set(null, Http.Outbound.current());
                WebSocketController.class.getDeclaredField("params").set(null, Scope.Params.current());
                WebSocketController.class.getDeclaredField("request").set(null, Http.Request.current());
                WebSocketController.class.getDeclaredField("session").set(null, Scope.Session.current());
                WebSocketController.class.getDeclaredField("validation").set(null, Validation.current());
            }
            //websocket是沒有response的
            ActionInvoker.invoke(request, null);
        }catch (PlayException e) {
            throw e;
        } catch (Exception e) {
            throw new UnexpectedException(e);
        }
    }

onSuccess

    @Override
    public void onSuccess() throws Exception {
        outbound.close();
        super.onSuccess();
    }

websocket只有當(dāng)連接關(guān)閉時(shí)才會(huì)觸發(fā)onSuccess方法,所以onSuccess相比Invocation也就多了一個(gè)關(guān)閉出站通道的操作

ServletInvocation

ServletInvocation是當(dāng)使用Servlet容器時(shí)的實(shí)現(xiàn)類,也就是將程序用war打包后放在Servlet容器后運(yùn)行的類,ServletInvocation不直接基礎(chǔ)于Invocation,而且繼承于DirectInvocation,DirectInvocation繼承了Invocation,DirectInvocation類添加了一個(gè)Suspend字段,用來處理線程暫停或等待操作。
因?yàn)樵赟ervlet規(guī)范中請求線程的管理交由Servlet容器處理,所以ServletInvocation不是使用Invoker的ScheduledThreadPoolExecutor來執(zhí)行的,那么在配置文件中設(shè)置play.pool數(shù)量對于使用Servlet容器的程序是無效的。
既然ServletInvocation不是使用Invoker中的線程池運(yùn)行的,那他是從什么地方初始化這個(gè)類并運(yùn)行的呢,這點(diǎn)將在本篇的ServletWrapper中再詳解

我們來看看ServletInvocation對Invocation中的方法進(jìn)行了怎樣的覆寫

init

    @Override
    public boolean init() {
        try {
            return super.init();
        } catch (NotFound e) {
            serve404(httpServletRequest, httpServletResponse, e);
            return false;
        } catch (RenderStatic r) {
            try {
                serveStatic(httpServletResponse, httpServletRequest, r);
            } catch (IOException e) {
                throw new UnexpectedException(e);
            }
            return false;
        }
    }

很簡單的初始化,和NettyInvocation相比就是靜態(tài)資源的緩存交由Servlet容器處理

run

    @Override
    public void run() {
        try {
            super.run();
        } catch (Exception e) {
            serve500(e, httpServletRequest, httpServletResponse);
            return;
        }
    }

與NettyInvocation一樣

execute

    @Override
    public void execute() throws Exception {
        ActionInvoker.invoke(request, response);
        copyResponse(request, response, httpServletRequest, httpServletResponse);
    }

ServletInvocation在execute結(jié)束后就將結(jié)果返回,這里不知道為什么不和NettyInvocation統(tǒng)一一下,都在execute階段返回結(jié)果或者都在onSuccess階段返回結(jié)果

使用netty的流程我們已經(jīng)理清楚了,簡單來說就是講netty的請求處理后交由ActionInvoker.invoke來執(zhí)行,ActionInvoker.invoke也是之后要研究的重點(diǎn)。

ServletWrapper

這篇的一開始我們以Server類為入口闡述了使用java命令直接運(yùn)行時(shí)的運(yùn)行過程,那么如果程序用war打包后放在Servlet容器中是如何運(yùn)行的呢?
我們可以打開play程序包下resources/war的web.xml來看看默認(rèn)的web.xml中是怎么配置的。

  
      play.server.ServletWrapper
  
  
  
    play
    play.server.ServletWrapper    
  

可以看出監(jiān)聽器和servlet入口都是ServletWrapper,那我們從程序的創(chuàng)建開始一點(diǎn)點(diǎn)剖析play的運(yùn)行過程

contextInitialized

contextInitialized是在啟動(dòng)時(shí)自動(dòng)加載,主要完成一些初始化的工作,代碼如下

    public void contextInitialized(ServletContextEvent e) {
        //standalonePlayServer表示這是否是一個(gè)獨(dú)立服務(wù)器
        Play.standalonePlayServer = false;

        ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
        //這是程序根目錄
        String appDir = e.getServletContext().getRealPath("/WEB-INF/application");
        File root = new File(appDir);

        //play id在web.xml中配置
        final String playId = System.getProperty("play.id", e.getServletContext().getInitParameter("play.id"));
        if (StringUtils.isEmpty(playId)) {
            throw new UnexpectedException("Please define a play.id parameter in your web.xml file. Without that parameter, play! cannot start your application. Please add a context-param into the WEB-INF/web.xml file.");
        }

        Play.frameworkPath = root.getParentFile();
        //使用預(yù)編譯的文件
        Play.usePrecompiled = true;
        //初始化,初始化過程中會(huì)將運(yùn)行模式強(qiáng)制改為prod
        Play.init(root, playId);
        //檢查配置文件中模式是不是dev,是dev提示自動(dòng)切換為prod
        Play.Mode mode = Play.Mode.valueOf(Play.configuration.getProperty("application.mode", "DEV").toUpperCase());
        if (mode.isDev()) {
            Logger.info("Forcing PROD mode because deploying as a war file.");
        }

        // Servlet 2.4手動(dòng)加載路徑
        // Servlet 2.4 does not allow you to get the context path from the servletcontext...
        if (isGreaterThan(e.getServletContext(), 2, 4)) {
            loadRouter(e.getServletContext().getContextPath());
        }

        Thread.currentThread().setContextClassLoader(oldClassLoader);
    }

初始化過程有2個(gè)地方需要注意

不管配置文件中使用的是什么模式,放在Servlet容器中會(huì)統(tǒng)一改為prod

默認(rèn)使用預(yù)編譯模式加載

service

service是servlet處理request的方法,我們先來看一下他的實(shí)現(xiàn)代碼

    @Override
    protected void service(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
        //路由還沒初始化時(shí)初始化路由
        if (!routerInitializedWithContext) {
            loadRouter(httpServletRequest.getContextPath());
        }

        if (Logger.isTraceEnabled()) {
            Logger.trace("ServletWrapper>service " + httpServletRequest.getRequestURI());
        }

        Request request = null;
        try {
            Response response = new Response();
            response.out = new ByteArrayOutputStream();
            Response.current.set(response);
            //httpServletRequest轉(zhuǎn)為play的request
            request = parseRequest(httpServletRequest);

            if (Logger.isTraceEnabled()) {
                Logger.trace("ServletWrapper>service, request: " + request);
            }
            //檢查插件中是否有實(shí)現(xiàn)
            boolean raw = Play.pluginCollection.rawInvocation(request, response);
            if (raw) {
                copyResponse(Request.current(), Response.current(), httpServletRequest, httpServletResponse);
            } else {
                //這里不是Invoker.invoke()方法,是invokeInThread
                Invoker.invokeInThread(new ServletInvocation(request, response, httpServletRequest, httpServletResponse));
            }
        } catch (NotFound e) {
            //處理404
            if (Logger.isTraceEnabled()) {
                Logger.trace("ServletWrapper>service, NotFound: " + e);
            }
            serve404(httpServletRequest, httpServletResponse, e);
            return;
        } catch (RenderStatic e) {
            //處理靜態(tài)資源
            if (Logger.isTraceEnabled()) {
                Logger.trace("ServletWrapper>service, RenderStatic: " + e);
            }
            serveStatic(httpServletResponse, httpServletRequest, e);
            return;
        } catch(URISyntaxException e) {
            //處理404
            serve404(httpServletRequest, httpServletResponse, new NotFound(e.toString()));
            return;
        } catch (Throwable e) {
            throw new ServletException(e);
        } finally {
            //因?yàn)閟ervlet的線程會(huì)復(fù)用,所以要手動(dòng)刪除當(dāng)前線程內(nèi)的值
            Request.current.remove();
            Response.current.remove();
            Scope.Session.current.remove();
            Scope.Params.current.remove();
            Scope.Flash.current.remove();
            Scope.RenderArgs.current.remove();
            Scope.RouteArgs.current.remove();
            CachedBoundActionMethodArgs.clear();
        }
    }

看的出和PlayHandle其實(shí)就是一個(gè)處理方式,先轉(zhuǎn)為play的request再扔到Invoker類中進(jìn)行處理,那么我們來看看Invoker.invokeInThread的具體實(shí)現(xiàn)過程

    public static void invokeInThread(DirectInvocation invocation) {
        boolean retry = true;
        while (retry) {
            invocation.run();
            if (invocation.retry == null) {
                retry = false;
            } else {
                try {
                    if (invocation.retry.task != null) {
                        invocation.retry.task.get();
                    } else {
                        Thread.sleep(invocation.retry.timeout);
                    }
                } catch (Exception e) {
                    throw new UnexpectedException(e);
                }
                retry = true;
            }
        }
    }

invokeInThread就是在當(dāng)前線程下運(yùn)行invocation,如果遇到暫?;蛘叩却蝿?wù)完成就循環(huán)直到完成。
要注意的一點(diǎn)是,Play 1.2.6未完成servlet模式下的websocket實(shí)現(xiàn),所以如果要用websocket請用netty模式

總結(jié)

至此,play的2種正常運(yùn)行的啟動(dòng)邏輯已經(jīng)分析完畢,play的啟動(dòng)過程還是在與不同的服務(wù)器運(yùn)行容器打交道,如何將轉(zhuǎn)化后的請求交由ActionInvoker來處理。
下一篇,我們先不看ActionInvoker的具體實(shí)現(xiàn),先看看play對配置文件及路由的分析處理過程。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/68303.html

相關(guān)文章

  • Play framework源碼解析 Part3:Play的初始化啟動(dòng)

    摘要:使用自建的類加載器主要是為了便于處理預(yù)編譯后的字節(jié)碼以及方便在模式下進(jìn)行即時(shí)的熱更新。 注:本系列文章所用play版本為1.2.6 在上一篇中,我們分析了play的2種啟動(dòng)方式,這一篇,我們來看看Play類的初始化過程 Play類 無論是Server還是ServletWrapper方式運(yùn)行,在他們的入口中都會(huì)運(yùn)行Play.init()來對Play類進(jìn)行初始化。那在解析初始化之前,我們先...

    xuxueli 評(píng)論0 收藏0
  • Play framework源碼解析 Part1:Play framework 介紹、項(xiàng)目構(gòu)成及啟動(dòng)

    摘要:注本系列文章所用版本為介紹是個(gè)輕量級(jí)的框架,致力于讓程序員實(shí)現(xiàn)快速高效開發(fā),它具有以下幾個(gè)方面的優(yōu)勢熱加載。在調(diào)試模式下,所有修改會(huì)及時(shí)生效。拋棄配置文件。約定大于配置。 注:本系列文章所用play版本為1.2.6 介紹 Play framework是個(gè)輕量級(jí)的RESTful框架,致力于讓java程序員實(shí)現(xiàn)快速高效開發(fā),它具有以下幾個(gè)方面的優(yōu)勢: 熱加載。在調(diào)試模式下,所有修改會(huì)及時(shí)...

    Riddler 評(píng)論0 收藏0
  • 如何使用 Docker 部署一個(gè)基于 Play Framework 的 Scala Web 應(yīng)用?

    摘要:本文將著重介紹使用來部署一個(gè)基于的應(yīng)用程序會(huì)多么便捷,當(dāng)然這個(gè)過程主要基于插件。如你所見,這是一個(gè)基于的應(yīng)用程序。這個(gè)基于的應(yīng)用程序?qū)o法被訪問??偨Y(jié)可以如此簡單地給一個(gè)基于的應(yīng)用程序建立,相信很多人都會(huì)像筆者一樣離不開它。 本文作者 Jacek Laskowski 擁有近20年的應(yīng)用程序開發(fā)經(jīng)驗(yàn),現(xiàn) CodiLime 的軟件開發(fā)團(tuán)隊(duì) Leader,曾從 IBM 取得多種資格認(rèn)證。在這...

    2501207950 評(píng)論0 收藏0
  • 淺談Android O Touch聲音播放流程

    摘要:前言當(dāng)我們點(diǎn)擊屏幕按鍵時(shí),就會(huì)聽到音,那么音是如何播放起來的呢,由于最近項(xiàng)目需求順便熟悉下了音的邏輯。完成的繪制過程,包括過程。向分發(fā)收到的用戶發(fā)起的事件,如按鍵,觸屏等事件??偨Y(jié)音的流程就簡單分析到這里,歡迎大家交流指正。 前言 當(dāng)我們點(diǎn)擊屏幕按鍵時(shí),就會(huì)聽到touch音,那么touch音是如何播放起來的呢,由于最近項(xiàng)目需求順便熟悉下了touch音的邏輯。 正文 談touch邏輯首先...

    XiNGRZ 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<