摘要:是一個(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
HttpServerPipelineFactoryHttpServerPipelineFactory類作用就是為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是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í)的處理,這里不多做闡述。
PlayHandlerPlayHandler的作用是解析http request的類型,然后根據(jù)不同類型來進(jìn)行不同的處理,由于PlayHandler內(nèi)處理的東西較多,這里先附上一張大體流程圖以供讀者參考
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
可以很清楚的發(fā)現(xiàn)PlayHandler處理請求就是依靠了各個(gè)不同的Invocation實(shí)現(xiàn)來完成,那我們就來看看Invocation他是如何一步步處理請求的。
InvocationInvocation是一個(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方法
NettyInvocationNettyInvocation是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
JobJob是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是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)閉出站通道的操作
ServletInvocationServletInvocation是當(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)行過程
contextInitializedcontextInitialized是在啟動(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ù)編譯模式加載
serviceservice是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模式
至此,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
摘要:使用自建的類加載器主要是為了便于處理預(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)行初始化。那在解析初始化之前,我們先...
摘要:注本系列文章所用版本為介紹是個(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í)...
摘要:本文將著重介紹使用來部署一個(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)證。在這...
摘要:前言當(dāng)我們點(diǎn)擊屏幕按鍵時(shí),就會(huì)聽到音,那么音是如何播放起來的呢,由于最近項(xiàng)目需求順便熟悉下了音的邏輯。完成的繪制過程,包括過程。向分發(fā)收到的用戶發(fā)起的事件,如按鍵,觸屏等事件??偨Y(jié)音的流程就簡單分析到這里,歡迎大家交流指正。 前言 當(dāng)我們點(diǎn)擊屏幕按鍵時(shí),就會(huì)聽到touch音,那么touch音是如何播放起來的呢,由于最近項(xiàng)目需求順便熟悉下了touch音的邏輯。 正文 談touch邏輯首先...
閱讀 808·2021-11-24 09:38
閱讀 1011·2021-11-11 11:01
閱讀 3255·2021-10-19 13:22
閱讀 1542·2021-09-22 15:23
閱讀 2845·2021-09-08 09:35
閱讀 2780·2019-08-29 11:31
閱讀 2134·2019-08-26 11:47
閱讀 1579·2019-08-26 11:44