摘要:是一個抽象類,繼承了接口,它的方法是這個類的核心。因為可能需要一個返回值,所以它同時繼承了接口來提供返回值。
注:本系列文章所用play版本為1.2.6
在上一篇中我們剖析了Play framework的啟動原理,很容易就能發現Play framework的啟動主入口在play.server.Server中,在本節,我們來一起看看Server類中主要發生了什么。
Server類既然是程序運行的主入口,那么必然是由main方法進入的,Server類中的main方法十分簡單。源碼如下:
public static void main(String[] args) throws Exception { File root = new File(System.getProperty("application.path")); //獲取參數中的precompiled if (System.getProperty("precompiled", "false").equals("true")) { Play.usePrecompiled = true; } //獲取參數中的writepid if (System.getProperty("writepid", "false").equals("true")) { //這個方法的作用是檢查當前目錄下是否存在server.pid文件,若存在表明當前已有程序在運行 writePID(root); } //Play類的初始化 Play.init(root, System.getProperty("play.id", "")); if (System.getProperty("precompile") == null) { //Server類初始化 new Server(args); } else { Logger.info("Done."); } }
main方法執行的操作很簡單:
獲取程序路徑
檢查是否存在precompiled參數
檢查是否存在writepid參數,若存在則檢查是否存在server.pid文件,若存在則表明已有程序在運行,不存在則將當前程序pid寫入server.pid
play類初始化
檢查是否存在precompile參數項,若存在表示是個預編譯行為,結束運行,若沒有則啟動服務
這其中最重要的便是Play類的初始化以及Server類的初始化
這里我們先來看Server類的初始化過程,現在可以先簡單的將Play類的初始化理解為Play框架中一些常量的初始化以及日志、配置文件、路由信息等配置的讀取。
這里貼一下Server類的初始化過程:
public Server(String[] args) { //設置文件編碼為UTF-8 System.setProperty("file.encoding", "utf-8"); //p為Play類初始化過程中讀取的配置文件信息 final Properties p = Play.configuration; //獲取參數中的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"))); //若沒有配置則設置默認端口為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 { //獲取配置文件中的默認http地址,若不存在則在系統參數中查找 //之前還是參數配置大于配置文件,這里不知道為什么又變成了配置文件的優先級高于參數配置,很迷 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服務器啟動類初始化,使用nio服務器,無限制線程池 //這里的線程池是netty的主線程池與工作線程池,是處理連接的線程池,而Play實際執行業務操作的線程池在另一個地方配置 ServerBootstrap bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory( Executors.newCachedThreadPool(), Executors.newCachedThreadPool()) ); try { //初始化http端口 if (httpPort != -1) { //設置管道工廠類 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端口服務器的啟動過程,和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) //輸出啟動成功,以便進行自動化測試 System.out.println("~ Server is up and running"); } }
server類的初始化沒什么好說的,重點就在于那2個管道工廠類,HttpServerPipelineFactory與SslHttpServerPipelineFactory
HttpServerPipelineFactoryHttpServerPipelineFactory類作用就是為netty服務器增加了各個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解碼器,流區塊聚合器,http response編碼器,區塊寫入器,play處理器
flash處理器和流區塊聚合器這里暫且不作詳細解析,讀者可以理解為這2者的作用分別為傳輸flash流和合并請求頭中有Transfer-Encoding:chunked的請求數據。
PlayHandler類是Play將netty接收到的request轉換為play的request并真正開始業務處理的入口類,我們先暫時放下PlayHandler,先來看看SslHttpServerPipelineFactory做了什么
SslHttpServerPipelineFactory是https使用的管道工廠類,他除了添加了一下處理器外,最重要的是根據配置創建了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; }
在設置完SSLEngine后,SslHttpServerPipelineFactory在decoder處理器前加了一個SslHandler來處理連接,這里不是使用playHandler作為處理器,而是使用SslPlayHandler,SslPlayHandler是PlayHandler的一個子類,就是重寫了連接和出錯時的處理,這里不多做闡述。
PlayHandlerPlayHandler的作用是解析http request的類型,然后根據不同類型來進行不同的處理,由于PlayHandler內處理的東西較多,這里先附上一張大體流程圖以供讀者參考
messageReceived是一個Override方法,作用就是在netty消息傳遞至該handle時進行相應操作,方法代碼如下
@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的信息,不是則不進行處理 if (msg instanceof HttpRequest) { final HttpRequest nettyRequest = (HttpRequest) msg; // 若為websocket的初次握手請求,則進行websocket握手過程 if (HttpHeaders.Values.WEBSOCKET.equalsIgnoreCase(nettyRequest.getHeader(HttpHeaders.Names.UPGRADE))) { websocketHandshake(ctx, nettyRequest, messageEvent); return; } try { //將netty的request轉為Play的request final Request request = parseRequest(ctx, nettyRequest, messageEvent); final Response response = new Response(); //Response.current為ThreadLocal類型,保證每個線程擁有多帶帶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
可以很清楚的發現PlayHandler處理請求就是依靠了各個不同的Invocation實現來完成,那我們就來看看Invocation他是如何一步步處理請求的。
InvocationInvocation是一個抽象類,繼承了Runnable接口,它的run方法是這個類的核心。
public void run() { //waitInQueue是一個監視器,監視了這個invocation在隊列中等待的時間 if (waitInQueue != null) { waitInQueue.stop(); } try { //預初始化,是對語言文件的清空 preInit(); //初始化 if (init()) { //運行執行前插件任務 before(); //處理request execute(); //運行執行后插件任務 after(); //成功執行后的處理 onSuccess(); } } catch (Suspend e) { //Suspend類讓請求暫停 suspend(e); after(); } catch (Throwable e) { onException(e); } finally { _finally(); } }
這里面的preInit()、before()、after()是沒有實現類的,用的就是Invocation類中的方法。我們先來看看Invocation類中對這些方法的實現,然后再來看看他的實現類對這些方法的修改
init
public boolean init() { //設置當前線程的classloader Thread.currentThread().setContextClassLoader(Play.classloader); //檢查是否為dev模式,是則檢查文件是否修改,以進行熱加載 Play.detectChanges(); //若Play未啟動,啟動 if (!Play.started) { if (Play.mode == Mode.PROD) { throw new UnexpectedException("Application is not started"); } Play.start(); } //InvocationContext中添加當前映射方法的annotation InvocationContext.current.set(getInvocationContext()); return true; }
Play.start可以先理解為啟動Play服務,具體展開將在之后mvc章節詳解
execute
execute在Invocation類中是一個抽象方法,需要實現類完成自己的實現
onSuccess
public void onSuccess() throws Exception { Play.pluginCollection.onInvocationSuccess(); }
執行play插件中的執行成功方法
onException
public void onException(Throwable e) { Play.pluginCollection.onInvocationException(e); if (e instanceof PlayException) { throw (PlayException) e; } throw new UnexpectedException(e); }
執行play插件中的執行異常方法,然后拋出異常
suspend
public void suspend(Suspend suspendRequest) { if (suspendRequest.task != null) { WaitForTasksCompletion.waitFor(suspendRequest.task, this); } else { Invoker.invoke(this, suspendRequest.timeout); } }
若暫停的請求有任務,那么調用WaitForTasksCompletion.waitFor進行等待,直到任務完成再繼續運行請求
若沒有任務,那就將請求通過ScheduledThreadPoolExecutor.schedule延時一段時間后執行
_finally
public void _finally() { Play.pluginCollection.invocationFinally(); InvocationContext.current.remove(); }
執行play插件中的invocationFinally方法
NettyInvocationNettyInvocation是netty請求使用的實現類,我們來依次看看他對init,execute,onSuccess等方法的實現上相比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來處理服務器錯誤,這里有一點要注意,可以發現,NettyInvocation的run有一層try catch來處理500錯誤,PlayHandle中的messageReceived也有一層try catch來處理500錯誤,我的理解是前者是用來處理業務流程中的錯誤的,后者是為了防止意外錯誤引發整個服務器掛掉所以又套了一層做保險
init
@Override public boolean init() { //設置當前線程的classloader Thread.currentThread().setContextClassLoader(Play.classloader); if (Logger.isTraceEnabled()) { Logger.trace("init: begin"); } //設置當前線程的request和response Request.current.set(request); Response.current.set(response); try { //如果是dev模式檢查路徑更新 if (Play.mode == Play.Mode.DEV) { Router.detectChanges(Play.ctxPath); } //如果是prod模式且靜態路徑緩存有當前請求信息,則從緩存中獲取 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); } //使用靜態資源 serveStatic(rs, ctx, request, response, nettyRequest, event); if (Logger.isTraceEnabled()) { Logger.trace("init: end false"); } return false; } //檢查是否為靜態資源 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) { //使用靜態資源 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的路徑,處理靜態資源以及處理路徑不存在的資源,這里也就說明了訪問路由設置中的靜態資源是不需要啟動play服務的,是否意味著可以通過play搭建一個靜態資源服務器?
execute
@Override public void execute() throws Exception { //檢查連接是否中斷 if (!ctx.getChannel().isConnected()) { try { ctx.getChannel().close(); } catch (Throwable e) { // Ignore } return; } //渲染之前檢查長度 saveExceededSizeError(nettyRequest, request, response); //運行 ActionInvoker.invoke(request, response); }
ActionInvoker.invoke是play mvc的執行入口方法,這個在之后會進行詳解
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,若是則關閉區塊流并返回response,若不是則返回正常的response
JobJob是Play任務的基類,用來處理異步任務,Job雖然繼承了Invocation,但他并不會加入至Play的主線程池執行,Job有自己多帶帶的線程池進行處理。因為Job可能需要一個返回值,所以它同時繼承了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()"); //執行任務并返回結果 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方法其實差不多,中間執行job的代碼其實就是execute方法的實現,但是有一點不同的是,job方法完成后不會調用onSuccess()方法。
這里只是提一下Job與Invocation類的關系,job任務的處理放在之后的插件篇再談
WebSocketInvocation是websocket請求的實現類,我們來具體看下他對Invocation的方法做了怎么樣的復寫
init
@Override public boolean init() { Http.Request.current.set(request); Http.Inbound.current.set(inbound); Http.Outbound.current.set(outbound); return super.init(); }
沒什么大的變動,就是在當前線程下添加了出入站的通道信息
execute
@Override public void execute() throws Exception { WebSocketInvoker.invoke(request, inbound, outbound); }
execute方法就是將request請求,出入站通道傳入WebSocketInvoker.invoke進行處理,WebSocketInvoker.invoke的代碼如下,可以看出WebSocketInvoker.invoke方法也就是調用了ActionInvoker.invoke來對請求進行處理。
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只有當連接關閉時才會觸發onSuccess方法,所以onSuccess相比Invocation也就多了一個關閉出站通道的操作
ServletInvocationServletInvocation是當使用Servlet容器時的實現類,也就是將程序用war打包后放在Servlet容器后運行的類,ServletInvocation不直接基礎于Invocation,而且繼承于DirectInvocation,DirectInvocation繼承了Invocation,DirectInvocation類添加了一個Suspend字段,用來處理線程暫停或等待操作。
因為在Servlet規范中請求線程的管理交由Servlet容器處理,所以ServletInvocation不是使用Invoker的ScheduledThreadPoolExecutor來執行的,那么在配置文件中設置play.pool數量對于使用Servlet容器的程序是無效的。
既然ServletInvocation不是使用Invoker中的線程池運行的,那他是從什么地方初始化這個類并運行的呢,這點將在本篇的ServletWrapper中再詳解
我們來看看ServletInvocation對Invocation中的方法進行了怎樣的覆寫
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相比就是靜態資源的緩存交由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結束后就將結果返回,這里不知道為什么不和NettyInvocation統一一下,都在execute階段返回結果或者都在onSuccess階段返回結果
使用netty的流程我們已經理清楚了,簡單來說就是講netty的請求處理后交由ActionInvoker.invoke來執行,ActionInvoker.invoke也是之后要研究的重點。
ServletWrapper這篇的一開始我們以Server類為入口闡述了使用java命令直接運行時的運行過程,那么如果程序用war打包后放在Servlet容器中是如何運行的呢?
我們可以打開play程序包下resources/war的web.xml來看看默認的web.xml中是怎么配置的。
play.server.ServletWrapper play play.server.ServletWrapper
可以看出監聽器和servlet入口都是ServletWrapper,那我們從程序的創建開始一點點剖析play的運行過程
contextInitializedcontextInitialized是在啟動時自動加載,主要完成一些初始化的工作,代碼如下
public void contextInitialized(ServletContextEvent e) { //standalonePlayServer表示這是否是一個獨立服務器 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(); //使用預編譯的文件 Play.usePrecompiled = true; //初始化,初始化過程中會將運行模式強制改為prod Play.init(root, playId); //檢查配置文件中模式是不是dev,是dev提示自動切換為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手動加載路徑 // 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個地方需要注意
不管配置文件中使用的是什么模式,放在Servlet容器中會統一改為prod
默認使用預編譯模式加載
serviceservice是servlet處理request的方法,我們先來看一下他的實現代碼
@Override protected void service(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException { //路由還沒初始化時初始化路由 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轉為play的request request = parseRequest(httpServletRequest); if (Logger.isTraceEnabled()) { Logger.trace("ServletWrapper>service, request: " + request); } //檢查插件中是否有實現 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) { //處理靜態資源 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 { //因為servlet的線程會復用,所以要手動刪除當前線程內的值 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其實就是一個處理方式,先轉為play的request再扔到Invoker類中進行處理,那么我們來看看Invoker.invokeInThread的具體實現過程
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就是在當前線程下運行invocation,如果遇到暫?;蛘叩却蝿胀瓿删脱h直到完成。
要注意的一點是,Play 1.2.6未完成servlet模式下的websocket實現,所以如果要用websocket請用netty模式
至此,play的2種正常運行的啟動邏輯已經分析完畢,play的啟動過程還是在與不同的服務器運行容器打交道,如何將轉化后的請求交由ActionInvoker來處理。
下一篇,我們先不看ActionInvoker的具體實現,先看看play對配置文件及路由的分析處理過程。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/68303.html
摘要:使用自建的類加載器主要是為了便于處理預編譯后的字節碼以及方便在模式下進行即時的熱更新。 注:本系列文章所用play版本為1.2.6 在上一篇中,我們分析了play的2種啟動方式,這一篇,我們來看看Play類的初始化過程 Play類 無論是Server還是ServletWrapper方式運行,在他們的入口中都會運行Play.init()來對Play類進行初始化。那在解析初始化之前,我們先...
摘要:注本系列文章所用版本為介紹是個輕量級的框架,致力于讓程序員實現快速高效開發,它具有以下幾個方面的優勢熱加載。在調試模式下,所有修改會及時生效。拋棄配置文件。約定大于配置。 注:本系列文章所用play版本為1.2.6 介紹 Play framework是個輕量級的RESTful框架,致力于讓java程序員實現快速高效開發,它具有以下幾個方面的優勢: 熱加載。在調試模式下,所有修改會及時...
摘要:本文將著重介紹使用來部署一個基于的應用程序會多么便捷,當然這個過程主要基于插件。如你所見,這是一個基于的應用程序。這個基于的應用程序將無法被訪問??偨Y可以如此簡單地給一個基于的應用程序建立,相信很多人都會像筆者一樣離不開它。 本文作者 Jacek Laskowski 擁有近20年的應用程序開發經驗,現 CodiLime 的軟件開發團隊 Leader,曾從 IBM 取得多種資格認證。在這...
摘要:前言當我們點擊屏幕按鍵時,就會聽到音,那么音是如何播放起來的呢,由于最近項目需求順便熟悉下了音的邏輯。完成的繪制過程,包括過程。向分發收到的用戶發起的事件,如按鍵,觸屏等事件??偨Y音的流程就簡單分析到這里,歡迎大家交流指正。 前言 當我們點擊屏幕按鍵時,就會聽到touch音,那么touch音是如何播放起來的呢,由于最近項目需求順便熟悉下了touch音的邏輯。 正文 談touch邏輯首先...
閱讀 795·2021-11-24 09:38
閱讀 998·2021-11-11 11:01
閱讀 3236·2021-10-19 13:22
閱讀 1523·2021-09-22 15:23
閱讀 2827·2021-09-08 09:35
閱讀 2765·2019-08-29 11:31
閱讀 2119·2019-08-26 11:47
閱讀 1563·2019-08-26 11:44