Play framework源码解析 Part3:Play的初始化与启动

在上一篇中,我们分析了play的2种启动方式,这一篇,我们来看看Play类的初始化过程 Play类 无论是Server还是ServletWrapper方式运行,在他们的入口中都会运行Play.init()来对Play类进行初始化。那在解析初始化之前,我们先来看看Play类是做什么的,它里面有什么重要的方法。 首先要明确的一点是,Play类是整个Play framework框架的管理、配置中心,它存放了大部分框架需要的成员变量,例如id,配置信息,所有加载的class,使用的插件管理器等等。下图就是Play类中的方法列表。 这其中加注释的几个方法是比较重要的,我们下面便来从init开始一点点剖析Play类中的各个方法。 Play的初始化 public static void init(File root, String id) { // Simple things Play.id = id; Play.started = false; Play.applicationPath = root; // 加载所有 play.static 中的记录的类 initStaticStuff(); //猜测play framework的路径 guessFrameworkPath(); // 读取配置文件 readConfiguration(); Play.classes = new ApplicationClasses(); // 初始化日志 Logger.init(); String logLevel = configuration.getProperty("application.log", "INFO"); //only override log-level if Logger was not configured manually if( !Logger.configuredManually) { Logger.setUp(logLevel); } Logger.recordCaller = Boolean.parseBoolean(configuration.getProperty("application.log.recordCaller", "false")); Logger.info("Starting %s", root.getAbsolutePath()); //设置临时文件夹 if (configuration.getProperty("play.tmp", "tmp").equals("none")) { tmpDir = null; Logger.debug("No tmp folder will be used (play.tmp is set to none)"); } else { tmpDir = new File(configuration.getProperty("play.tmp", "tmp")); if (!tmpDir.isAbsolute()) { tmpDir = new File(applicationPath, tmpDir.getPath()); } if (Logger.isTraceEnabled()) { Logger.trace("Using %s as tmp dir", Play.tmpDir); } if (!tmpDir.exists()) { try { if (readOnlyTmp) { throw new Exception("ReadOnly tmp"); } tmpDir.mkdirs(); } catch (Throwable e) { tmpDir = null; Logger.warn("No tmp folder will be used (cannot create the tmp dir)"); } } } // 设置运行模式 try { mode = Mode.valueOf(configuration.getProperty("application.mode", "DEV").toUpperCase()); } catch (IllegalArgumentException e) { Logger.error("Illegal mode '%s', use either prod or dev", configuration.getProperty("application.mode")); fatalServerErrorOccurred(); } if (usePrecompiled || forceProd) { mode = Mode.PROD; } // 获取http使用路径 ctxPath = configuration.getProperty("http.path", ctxPath); // 设置文件路径 VirtualFile appRoot = VirtualFile.open(applicationPath); roots.add(appRoot); javaPath = new CopyOnWriteArrayList<VirtualFile>(); javaPath.add(appRoot.child("app")); javaPath.add(appRoot.child("conf")); // 设置模板路径 if (appRoot.child("app/views").exists()) { templatesPath = new ArrayList<VirtualFile>(2); templatesPath.add(appRoot.child("app/views")); } else { templatesPath = new ArrayList<VirtualFile>(1); } // 设置路由文件 routes = appRoot.child("conf/routes"); // 设置模块路径 modulesRoutes = new HashMap<String, VirtualFile>(16); // 加载模块 loadModules(); // 模板路径中加入框架自带的模板文件 templatesPath.add(VirtualFile.open(new File(frameworkPath, "framework/templates"))); // 初始化classloader classloader = new ApplicationClassloader(); // Fix ctxPath if ("/".equals(Play.ctxPath)) { Play.ctxPath = ""; } // 设置cookie域名 Http.Cookie.defaultDomain = configuration.getProperty("application.defaultCookieDomain", null); if (Http.Cookie.defaultDomain!=null) { Logger.info("Using default cookie domain: " + Http.Cookie.defaultDomain); } // 加载插件 pluginCollection.loadPlugins(); // 如果是prod直接启动 if (mode == Mode.PROD || System.getProperty("precompile") != null) { mode = Mode.PROD; //预编译 if (preCompile() && System.getProperty("precompile") == null) { start(); } else { return; } } else { Logger.warn("You're running Play! in DEV mode"); } pluginCollection.onApplicationReady(); Play.initialized = true; } 如上面的代码所示,初始化过程主要的顺序为: ...

January 20, 2018 · 8 min · 1547 words · luyanliang

Play framework源码解析 Part2:Server与ServletWrapper

在上一节中我们剖析了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 ...

January 11, 2018 · 9 min · 1803 words · luyanliang

Play framework源码解析 Part1: Play framework 介绍、项目构成及启动脚本解析

注:本系列文章所用play版本为1.2.6 介绍 Play framework是个轻量级的RESTful框架,致力于让java程序员实现快速高效开发,它具有以下几个方面的优势: 热加载。在调试模式下,所有修改会及时生效。 抛弃xml配置文件。约定大于配置。 支持异步编程 无状态mvc框架,拓展性良好 简单的路由设置 这里附上Play framework的文档地址,官方有更为详尽的功能叙述。Play framework文档 项目构成 play framework的初始化非常简单,只要下载了play的软件包后,在命令行中运行play new xxx即可初始化一个项目。 自动生成的项目结构如下: 运行play程序也非常简单,在项目目录下使用 play run 即可运行。 启动脚本解析 play framework软件包目录 为了更好的了解play framework的运作原理,我们来从play framework的启动脚本开始分析,分析启动脚本有助于我们了解play framework的运行过程。 play的启动脚本是使用python编写的,脚本的入口为play软件包根目录下的play文件,下面我们将从play这个脚本的主入口开始分析。 play脚本解析 play脚本在开头引入了3个类,分别为play.cmdloader,play.application,play.utils,从添加的系统参数中可以看出play启动脚本的存放路径为 framework/pym cmdloader.py的主要作用为加载framework/pym/commands下的各个脚本文件,用于之后对命令参数的解释运行 application.py的主要作用为解析项目路径下conf/中的配置文件、加载模块、拼接最后运行的java命令 sys.path.append(os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), 'framework', 'pym')) from play.cmdloader import CommandLoader from play.application import PlayApplication from play.utils import * 在脚本的开头,有这么一段代码,意味着只要在play主程序根目录下创建一个名为id的文件,即可设置默认的框架id play_env["id_file"] = os.path.join(play_env['basedir'], 'id') if os.path.exists(play_env["id_file"]): play_env["id"] = open(play_env["id_file"]).readline().strip() else: play_env["id"] = '' 命令参数的分隔由以下代码完成,例如使用了play run –%test,那么参数列表就是 [“xxxx\play”,“run”,"–%test"] 这段代码也说明了,play的命令格式为 ...

January 3, 2018 · 5 min · 1041 words · luyanliang

Play framework源码解析目录

这里对Play framework源码解析做一个大致规划目录,要写的篇章如下 目录 Play framework 介绍、项目构成及启动脚本解析–已完成 Server与ServletWrapper–已完成 Play的初始化与启动–已完成 ActionInvoker与mvc 模板渲染 Play插件 classloader与字节码增强 测试 数据库拓展 辅助工具类 对Play的一些思考及优化方案

January 1, 2018 · 1 min · 16 words · luyanliang