注:本系列文章所用play版本为1.2.6
在上一篇中咱们剖析了Play framework的启动原理,很容易就能发现Play framework的启动主入口在play.server.Server中,在本节,咱们来一块儿看看Server类中主要发生了什么。java
既然是程序运行的主入口,那么必然是由main方法进入的,Server类中的main方法十分简单。源码以下:web
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方法执行的操做很简单:bootstrap
这其中最重要的即是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服务器
HttpServerPipelineFactory类做用就是为netty服务器增长了各个ChannelHandler。websocket
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作了什么session
SslHttpServerPipelineFactory是https使用的管道工厂类,他除了添加了一下处理器外,最重要的是根据配置建立了https的链接方式。mvc
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的一个子类,就是重写了链接和出错时的处理,这里很少作阐述。app
PlayHandler的做用是解析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>类型,保证每一个线程拥有单独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<Object>() { public void invoke(Object result) { writeChunk(request, response, ctx, nettyRequest, result); } }); /* 查找Play插件(即继承了PlayPlugin抽象类的类)中是否有该request的实现方法,若存在,则用Play插件的实现 Play自带的插件实现有: CorePlugin实现了/@kill和/@status路径的访问流程 DBPlugin实现了/@db路径的访问流程 Evolutions实现了/@evolutions路径的访问流程 这里将插件的访问与其余访问分开最主要缘由应该是插件访问不须要启动Play,而其余命令必须先调用Play.start来启动 */ boolean raw = Play.pluginCollection.rawInvocation(request, response); if (raw) { copyResponse(ctx, request, response, nettyRequest); } else { /* Invoker.invoke就是向Invoker类中的ScheduledThreadPoolExecutor提交任务并执行 使用ScheduledThreadPoolExecutor是由于能够定时,便于执行异步任务 Invoker类能够理解为是各个不一样容器任务转向Play任务的一个转换类,他下面有2个静态内部类,分别为InvocationContext和Invocation InvocationContext内存放了当前request映射的controller类的annotation Invocation则为Play任务的基类,他的实现类有: Job:Play的异步任务 NettyInvocation:netty请求的实现类 ServletInvocation:Servlet容器中的实现类 WebSocketInvocation:websocket的实现类 */ Invoker.invoke(new NettyInvocation(request, response, ctx, nettyRequest, messageEvent)); } } catch (Exception ex) { //处理服务器错误 serve500(ex, ctx, nettyRequest); } } // 若是msg是websocket,则进行websocket接收 if (msg instanceof WebSocketFrame) { WebSocketFrame frame = (WebSocketFrame) msg; websocketFrameReceived(ctx, frame); } if (Logger.isTraceEnabled()) { Logger.trace("messageReceived: end"); } }
能够很清楚的发现PlayHandler处理请求就是依靠了各个不一样的Invocation实现来完成,那咱们就来看看Invocation他是如何一步步处理请求的。
Invocation是一个抽象类,继承了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类中对这些方法的实现,而后再来看看他的实现类对这些方法的修改
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在Invocation类中是一个抽象方法,须要实现类完成本身的实现
public void onSuccess() throws Exception { Play.pluginCollection.onInvocationSuccess(); }
执行play插件中的执行成功方法
public void onException(Throwable e) { Play.pluginCollection.onInvocationException(e); if (e instanceof PlayException) { throw (PlayException) e; } throw new UnexpectedException(e); }
执行play插件中的执行异常方法,而后抛出异常
public void suspend(Suspend suspendRequest) { if (suspendRequest.task != null) { WaitForTasksCompletion.waitFor(suspendRequest.task, this); } else { Invoker.invoke(this, suspendRequest.timeout); } }
若暂停的请求有任务,那么调用WaitForTasksCompletion.waitFor进行等待,直到任务完成再继续运行请求
若没有任务,那就将请求经过ScheduledThreadPoolExecutor.schedule延时一段时间后执行
public void _finally() { Play.pluginCollection.invocationFinally(); InvocationContext.current.remove(); }
执行play插件中的invocationFinally方法
NettyInvocation是netty请求使用的实现类,咱们来依次看看他对init,execute,onSuccess等方法的实现上相比Invocation改变了什么
@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错误,个人理解是前者是用来处理业务流程中的错误的,后者是为了防止意外错误引起整个服务器挂掉因此又套了一层作保险
@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搭建一个静态资源服务器?
@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的执行入口方法,这个在以后会进行详解
@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
Job是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的方法作了怎么样的复写
@Override public boolean init() { Http.Request.current.set(request); Http.Inbound.current.set(inbound); Http.Outbound.current.set(outbound); return super.init(); }
没什么大的变更,就是在当前线程下添加了出入站的通道信息
@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); } }
@Override public void onSuccess() throws Exception { outbound.close(); super.onSuccess(); }
websocket只有当链接关闭时才会触发onSuccess方法,因此onSuccess相比Invocation也就多了一个关闭出站通道的操做
ServletInvocation是当使用Servlet容器时的实现类,也就是将程序用war打包后放在Servlet容器后运行的类,ServletInvocation不直接基础于Invocation,并且继承于DirectInvocation,DirectInvocation继承了Invocation,DirectInvocation类添加了一个Suspend字段,用来处理线程暂停或等待操做。
由于在Servlet规范中请求线程的管理交由Servlet容器处理,因此ServletInvocation不是使用Invoker的ScheduledThreadPoolExecutor来执行的,那么在配置文件中设置play.pool数量对于使用Servlet容器的程序是无效的。
既然ServletInvocation不是使用Invoker中的线程池运行的,那他是从什么地方初始化这个类并运行的呢,这点将在本篇的ServletWrapper中再详解
咱们来看看ServletInvocation对Invocation中的方法进行了怎样的覆写
@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容器处理
@Override public void run() { try { super.run(); } catch (Exception e) { serve500(e, httpServletRequest, httpServletResponse); return; } }
与NettyInvocation同样
@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也是以后要研究的重点。
这篇的一开始咱们以Server类为入口阐述了使用java命令直接运行时的运行过程,那么若是程序用war打包后放在Servlet容器中是如何运行的呢?
咱们能够打开play程序包下resources/war的web.xml来看看默认的web.xml中是怎么配置的。
<listener> <listener-class>play.server.ServletWrapper</listener-class> </listener> <servlet> <servlet-name>play</servlet-name> <servlet-class>play.server.ServletWrapper</servlet-class> </servlet>
能够看出监听器和servlet入口都是ServletWrapper,那咱们从程序的建立开始一点点剖析play的运行过程
contextInitialized是在启动时自动加载,主要完成一些初始化的工做,代码以下
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个地方须要注意
service是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,若是遇到暂停或者等待任务完成就循环直到完成。
要注意的一点是,Play 1.2.6未完成servlet模式下的websocket实现,因此若是要用websocket请用netty模式
至此,play的2种正常运行的启动逻辑已经分析完毕,play的启动过程仍是在与不一样的服务器运行容器打交道,如何将转化后的请求交由ActionInvoker来处理。下一篇,咱们先不看ActionInvoker的具体实现,先看看play对配置文件及路由的分析处理过程。