摘要:注本系列文章所用版本為介紹是個(gè)輕量級(jí)的框架,致力于讓程序員實(shí)現(xiàn)快速高效開發(fā),它具有以下幾個(gè)方面的優(yōu)勢(shì)熱加載。在調(diào)試模式下,所有修改會(huì)及時(shí)生效。拋棄配置文件。約定大于配置。
注:本系列文章所用play版本為1.2.6介紹
Play framework是個(gè)輕量級(jí)的RESTful框架,致力于讓java程序員實(shí)現(xiàn)快速高效開發(fā),它具有以下幾個(gè)方面的優(yōu)勢(shì):
熱加載。在調(diào)試模式下,所有修改會(huì)及時(shí)生效。
拋棄xml配置文件。約定大于配置。
支持異步編程
無狀態(tài)mvc框架,拓展性良好
簡(jiǎn)單的路由設(shè)置
這里附上Play framework的文檔地址,官方有更為詳盡的功能敘述。Play framework文檔
項(xiàng)目構(gòu)成play framework的初始化非常簡(jiǎn)單,只要下載了play的軟件包后,在命令行中運(yùn)行play new xxx即可初始化一個(gè)項(xiàng)目。
自動(dòng)生成的項(xiàng)目結(jié)構(gòu)如下:
運(yùn)行play程序也非常簡(jiǎn)單,在項(xiàng)目目錄下使用play run即可運(yùn)行。
啟動(dòng)腳本解析 play framework軟件包目錄為了更好的了解play framework的運(yùn)作原理,我們來從play framework的啟動(dòng)腳本開始分析,分析啟動(dòng)腳本有助于我們了解play framework的運(yùn)行過程。
play framework1.2.6軟件包解壓后的文件如下:
play的啟動(dòng)腳本是使用python編寫的,腳本的入口為play軟件包根目錄下的play文件,下面我們將從play這個(gè)腳本的主入口開始分析。
play腳本在開頭引入了3個(gè)類,分別為play.cmdloader,play.application,play.utils,從添加的系統(tǒng)參數(shù)中可以看出play啟動(dòng)腳本的存放路徑為 framework/pym
cmdloader.py的主要作用為加載framework/pym/commands下的各個(gè)腳本文件,用于之后對(duì)命令參數(shù)的解釋運(yùn)行
application.py的主要作用為解析項(xiàng)目路徑下conf/中的配置文件、加載模塊、拼接最后運(yùn)行的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主程序根目錄下創(chuàng)建一個(gè)名為id的文件,即可設(shè)置默認(rèn)的框架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"] = ""
命令參數(shù)的分隔由以下代碼完成,例如使用了play run --%test,那么參數(shù)列表就是["xxxxplay","run","--%test"],這段代碼也說明了,play的命令格式為 play cmd [app_path] [--options]
application_path = None remaining_args = [] if len(sys.argv) == 1: application_path = os.getcwd() if len(sys.argv) == 2: application_path = os.getcwd() remaining_args = sys.argv[2:] if len(sys.argv) > 2: if sys.argv[2].startswith("-"): application_path = os.getcwd() remaining_args = sys.argv[2:] else: application_path = os.path.normpath(os.path.abspath(sys.argv[2])) remaining_args = sys.argv[3:]
在play參數(shù)中,有一個(gè)ignoreMissing,這個(gè)參數(shù)的全稱其實(shí)是ignoreMissingModules,作用就是在當(dāng)配置文件中配置有模塊但是在play目錄下并沒有找到時(shí)是否忽略,如需忽略那么在啟動(dòng)時(shí)需要加入--force
ignoreMissing = False if remaining_args.count("--force") == 1: remaining_args.remove("--force") ignoreMissing = True
腳本通過play_app = PlayApplication(application_path, play_env, ignoreMissing)和cmdloader = CommandLoader(play_env["basedir"])來進(jìn)行PlayApplication類和CommandLoader類的初始化。
在PlayApplication類的初始化過程中,它創(chuàng)建了PlayConfParser類用來解析配置文件,這也就是說play的配置文件解析是在腳本啟動(dòng)階段進(jìn)行的,這也是為什么修改配置文件無法實(shí)時(shí)生效需要重啟的原因
在PlayConfParser中有一個(gè)常量DEFAULTS儲(chǔ)存了默認(rèn)的http端口和jpda調(diào)試端口,分別為9000和8000,需要添加默認(rèn)值可以修改DEFAULTS,DEFAULTS內(nèi)的值只有當(dāng)配置文件中找不到時(shí)才會(huì)生效。
DEFAULTS = { "http.port": "9000", "jpda.port": "8000" }
值得一提的是,配置文件中的http.port的優(yōu)先級(jí)是小于命令參數(shù)中的http.port的
#env為命令參數(shù) if env.has_key("http.port"): self.entries["http.port"] = env["http.port"]
CommandLoader類的功能很簡(jiǎn)單,就是遍歷framework/pym/commands下的.py文件,依次加載然后讀取他的全局變量COMMANDS和MODULE儲(chǔ)存用于之后的命令處理和模塊加載。
回到play腳本中,在PlayApplication類和CommandLoader類初始化完成之后,play進(jìn)行"--deps"參數(shù)的檢查,如果存在--deps,則調(diào)用play.deps.DependenciesManager類來進(jìn)行依賴的檢查、更新。DependenciesManager的解析將放到后話詳解。
if remaining_args.count("--deps") == 1: cmdloader.commands["dependencies"].execute(command="dependencies", app=play_app, args=["--sync"], env=play_env, cmdloader=cmdloader) remaining_args.remove("--deps")
接下來,play便正式進(jìn)行啟動(dòng)過程,play按照以下的順序進(jìn)行加載:
加載配置文件中記錄的模塊的命令信息
加載參數(shù)中指定的模塊的命令信息
運(yùn)行各模塊中的before函數(shù)
執(zhí)行play_command,play_command即為run,test,war等play需要的執(zhí)行的命令
運(yùn)行各模塊中的after函數(shù)
結(jié)束腳本
if play_command in cmdloader.commands: for name in cmdloader.modules: module = cmdloader.modules[name] if "before" in dir(module): module.before(command=play_command, app=play_app, args=remaining_args, env=play_env) status = cmdloader.commands[play_command].execute(command=play_command, app=play_app, args=remaining_args, env=play_env, cmdloader=cmdloader) for name in cmdloader.modules: module = cmdloader.modules[name] if "after" in dir(module): module.after(command=play_command, app=play_app, args=remaining_args, env=play_env) sys.exit(status)
下面,我們來看看play常用命令的運(yùn)行過程...
Play常用運(yùn)行命令解析在本節(jié)的一開始,我決定先把play所有的可用命令先列舉一下,以便讀者可以選擇性閱讀。
命令名稱 | 命令所在文件 | 作用 |
---|---|---|
antify | ant.py | 初始化ant構(gòu)建工具的build.xml文件 |
run | base.py | 運(yùn)行程序 |
new | base.py | 新建play應(yīng)用 |
clean | base.py | 刪除臨時(shí)文件,即清空tmp文件夾 |
test | base.py | 運(yùn)行測(cè)試程序 |
autotest、auto-test | base.py | 自動(dòng)運(yùn)行所有測(cè)試項(xiàng)目 |
id | base.py | 設(shè)置項(xiàng)目id |
new,run | base.py | 新建play應(yīng)用并啟動(dòng) |
clean,run | base.py | 刪除臨時(shí)文件并運(yùn)行 |
modules | base.py | 顯示項(xiàng)目用到的模塊,注:這里顯示的模塊只是在項(xiàng)目配置文件中引用的模塊,命令參數(shù)中添加的模塊不會(huì)顯示 |
check | check.py | 檢查play更新 |
classpath、cp | classpath.py | 顯示應(yīng)用的classpath |
start | daemon.py | 在后臺(tái)運(yùn)行play程序 |
stop | daemon.py | 停止正在運(yùn)行的程序 |
restart | daemon.py | 重啟正在運(yùn)行的程序 |
pid | daemon.py | 顯示運(yùn)行中的程序的pid |
out | daemon.py | 顯示輸出 |
dependencies、deps | deps.py | 運(yùn)行DependenciesManager更新依賴 |
eclipsify、ec | eclipse.py | 創(chuàng)建eclipse配置文件 |
evolutions | evolutions.py | 運(yùn)行play.db.Evolutions進(jìn)行數(shù)據(jù)庫(kù)演變檢查 |
help | help.py | 輸出所有play的可用命令 |
idealize、idea | intellij.py | 生成idea配置文件 |
javadoc | javadoc.py | 生成javadoc |
new-module、nm | modulesrepo.py | 創(chuàng)建新模塊 |
list-modules、lm | modulesrepo.py | 顯示play社區(qū)中的模塊 |
build-module、bm | modulesrepo.py | 打包模塊 |
add | modulesrepo.py | 將模塊添加至項(xiàng)目 |
install | modulesrepo.py | 安裝模塊 |
netbeansify | netbeans.py | 生成netbeans配置文件 |
precompile | precompile.py | 預(yù)編譯 |
secret | secret.py | 生成secret key |
status | status.py | 顯示運(yùn)行中項(xiàng)目的狀態(tài) |
version | version.py | 顯示play framework的版本號(hào) |
war | war.py | 將項(xiàng)目打包為war文件 |
run應(yīng)該是我們平時(shí)用的最多的命令了,run命令的作用其實(shí)很簡(jiǎn)單,就是根據(jù)命令參數(shù)拼接java參數(shù),然后調(diào)用java來運(yùn)行play.server.Server,run函數(shù)的代碼如下:
def run(app, args): #app即為play腳本中創(chuàng)建的PlayApplication類 global process #這里檢查是否存在conf/routes和conf/application.conf app.check() print "~ Ctrl+C to stop" print "~ " java_cmd = app.java_cmd(args) try: process = subprocess.Popen (java_cmd, env=os.environ) signal.signal(signal.SIGTERM, handle_sigterm) return_code = process.wait() signal.signal(signal.SIGINT, handle_sigint) if 0 != return_code: sys.exit(return_code) except OSError: print "Could not execute the java executable, please make sure the JAVA_HOME environment variable is set properly (the java executable should reside at JAVA_HOME/bin/java). " sys.exit(-1) print
app.java_cmd(args)的實(shí)現(xiàn)代碼如下:
def java_cmd(self, java_args, cp_args=None, className="play.server.Server", args = None): if args is None: args = [""] memory_in_args=False #檢查java參數(shù)中是否有jvm內(nèi)存設(shè)置 for arg in java_args: if arg.startswith("-Xm"): memory_in_args=True #如果參數(shù)中無jvm內(nèi)存設(shè)置,那么在配置文件中找是否有jvm內(nèi)存設(shè)置,若還是沒有則在環(huán)境變量中找是否有JAVA_OPTS #這里其實(shí)有個(gè)問題,這里假定的是JAVA_OPTS變量里只存了jvm內(nèi)存設(shè)置,如果JAVA_OPTS還存了其他選項(xiàng),那對(duì)運(yùn)行可能有影響 if not memory_in_args: memory = self.readConf("jvm.memory") if memory: java_args = java_args + memory.split(" ") elif "JAVA_OPTS" in os.environ: java_args = java_args + os.environ["JAVA_OPTS"].split(" ") #獲取程序的classpath if cp_args is None: cp_args = self.cp_args() #讀取配置文件中的jpda端口 self.jpda_port = self.readConf("jpda.port") #讀取配置文件中的運(yùn)行模式 application_mode = self.readConf("application.mode").lower() #如果模式是prod,則用server模式編譯 if application_mode == "prod": java_args.append("-server") # JDK 7 compat # 使用新class校驗(yàn)器 (不知道作用) java_args.append("-XX:-UseSplitVerifier") #查找配置文件中是否有java安全配置,如果有則加入java參數(shù)中 java_policy = self.readConf("java.policy") if java_policy != "": policyFile = os.path.join(self.path, "conf", java_policy) if os.path.exists(policyFile): print "~ using policy file "%s"" % policyFile java_args.append("-Djava.security.manager") java_args.append("-Djava.security.policy==%s" % policyFile) #加入http端口設(shè)置 if self.play_env.has_key("http.port"): args += ["--http.port=%s" % self.play_env["http.port"]] #加入https端口設(shè)置 if self.play_env.has_key("https.port"): args += ["--https.port=%s" % self.play_env["https.port"]] #設(shè)置文件編碼 java_args.append("-Dfile.encoding=utf-8") #設(shè)置編譯命令 (這邊使用了jregex/Pretokenizer類的next方法,不知道有什么用) java_args.append("-XX:CompileCommand=exclude,jregex/Pretokenizer,next") #如果程序模式在dev,則添加jpda調(diào)試器參數(shù) if self.readConf("application.mode").lower() == "dev": if not self.play_env["disable_check_jpda"]: self.check_jpda() java_args.append("-Xdebug") java_args.append("-Xrunjdwp:transport=dt_socket,address=%s,server=y,suspend=n" % self.jpda_port) java_args.append("-Dplay.debug=yes") #拼接java參數(shù) java_cmd = [self.java_path(), "-javaagent:%s" % self.agent_path()] + java_args + ["-classpath", cp_args, "-Dapplication.path=%s" % self.path, "-Dplay.id=%s" % self.play_env["id"], className] + args return java_cmd
start命令與run命令很類似,執(zhí)行步驟為:
依次查找play變量pid_file、系統(tǒng)環(huán)境變量PLAY_PID_PATH、項(xiàng)目根目錄下server.pid,查找是否存在指定pid
若第一步找到pid,查找當(dāng)前進(jìn)程列表中是否存在此pid進(jìn)程,存在則試圖關(guān)閉進(jìn)程。(如果此pid不是play的進(jìn)程呢。。。)
在配置文件中找application.log.system.out看是否關(guān)閉了系統(tǒng)輸出
啟動(dòng)程序,這里與run命令唯一的區(qū)別就是他指定了stdout位置,這樣就變成了后臺(tái)程序
將啟動(dòng)后的程序的pid寫入server.pid
stop命令即關(guān)閉當(dāng)前進(jìn)程,這里要提一下,play有個(gè)注解叫OnApplicationStop,即會(huì)在程序停止時(shí)觸發(fā),而OnApplicationStop的實(shí)現(xiàn)主要是調(diào)用了Runtime.getRuntime().addShutdownHook();來完成
test與autotest使用play test命令可以讓程序進(jìn)入測(cè)試模式,test命令和run命令其實(shí)差別不大,唯一的區(qū)別就在于使用test命令時(shí),腳本會(huì)將play id自動(dòng)替換為test,當(dāng)play id為test時(shí)會(huì)自動(dòng)引入testrunner模塊,testrunner模塊主要功能為添加@tests路由并實(shí)現(xiàn)了test測(cè)試頁(yè)面,他的具體實(shí)現(xiàn)過程后續(xù)再談。
autotest命令的作用是自動(dòng)測(cè)試所有的測(cè)試用例,他的執(zhí)行順序是這樣的:
檢查是否存在tmp文件夾,存在即刪除
檢查是否有程序正在運(yùn)行,如存在則關(guān)閉程序
檢查程序是否配置了ssl但是沒有指定證書
檢查是否存在test-result文件夾,存在即刪除
使用test作為id重啟程序
調(diào)用play.modules.testrunner.FirePhoque來進(jìn)行自動(dòng)化測(cè)試
關(guān)閉程序
autotest的腳本的步驟1和2我覺得是有問題的,應(yīng)該換下順序,不然如果程序正在向tmp文件夾插入臨時(shí)文件,那么tmp文件夾就刪除失敗了。
關(guān)閉程序的代碼調(diào)用了http://localhost:%http_port/@kill進(jìn)行關(guān)閉,@kill的實(shí)現(xiàn)方法在play.CorePlugin下,注意,@kill在prod模式下無效(這是廢話)
由于使用了python作為啟動(dòng)腳本,無法通過java內(nèi)部變量值判斷程序是否開啟,只能查看控制臺(tái)的輸出日志,所以在使用test作為id重啟后,腳本使用下面的代碼判斷程序是否完全啟動(dòng):
try: #這里啟動(dòng)程序 play_process = subprocess.Popen(java_cmd, env=os.environ, stdout=sout) except OSError: print "Could not execute the java executable, please make sure the JAVA_HOME environment variable is set properly (the java executable should reside at JAVA_HOME/bin/java). " sys.exit(-1) #打開日志輸出文件 soutint = open(os.path.join(app.log_path(), "system.out"), "r") while True: if play_process.poll(): print "~" print "~ Oops, application has not started?" print "~" sys.exit(-1) line = soutint.readline().strip() if line: print line #若出現(xiàn)"Server is up and running"則正常啟動(dòng) if line.find("Server is up and running") > -1: # This line is written out by Server.java to system.out and is not log file dependent soutint.close() break
firephoque類的實(shí)現(xiàn)過程我們之后再詳解。
new與clean我們使用new來創(chuàng)建一個(gè)新項(xiàng)目,play new的使用方法為play new project-name [--with] [--name]。
with參數(shù)為項(xiàng)目所使用的模塊。
name為項(xiàng)目名,這里要注意一點(diǎn),projectname和--name的參數(shù)可以設(shè)置為不同值,projectname是項(xiàng)目建立的文件夾名,--name的值為項(xiàng)目配置文件中的application.name。
如果不加--name,腳本會(huì)提示你是否使用projectname作為名字,在確認(rèn)之后,腳本會(huì)將resources/application-skel中的所有文件拷貝到projectname文件夾下,然后用輸入的name替換項(xiàng)目配置文件下的application.name,并生成一個(gè)64位的secretKey替換配置文件中的secretKey。
接著,腳本會(huì)查找使用的模塊中是否存在dependencies.yml,并將dependencies.yml中的內(nèi)容加入項(xiàng)目的dependencies.yml中,并調(diào)用DependenciesManager檢查依賴狀態(tài)。
new函數(shù)的主要代碼如下:
print "~ The new application will be created in %s" % os.path.normpath(app.path) if application_name is None: application_name = raw_input("~ What is the application name? [%s] " % os.path.basename(app.path)) if application_name == "": application_name = os.path.basename(app.path) copy_directory(os.path.join(env["basedir"], "resources/application-skel"), app.path) os.mkdir(os.path.join(app.path, "app/models")) os.mkdir(os.path.join(app.path, "lib")) app.check() replaceAll(os.path.join(app.path, "conf/application.conf"), r"%APPLICATION_NAME%", application_name) replaceAll(os.path.join(app.path, "conf/application.conf"), r"%SECRET_KEY%", secretKey()) print "~"
clean命令非常簡(jiǎn)單,就是刪除整個(gè)tmp文件夾
war與precompile很多時(shí)候,我們需要使用tomcat等服務(wù)器容器作為服務(wù)載體,這時(shí)候就需要將play應(yīng)用打包為war
war的使用參數(shù)是play war project-name [-o/--output][filename] [--zip] [--exclude][exclude-directories]
使用-o或--output來指定輸出文件夾,使用--zip壓縮為war格式,使用--exclude來包含另外需要打包的文件夾
要注意的是,必須在項(xiàng)目目錄外進(jìn)行操作,不然會(huì)失敗
在參數(shù)處理完畢后,腳本正式開始打包過程,分為2個(gè)步驟:1.預(yù)編譯。2:打包
預(yù)編譯即用到了precompile命令,precompile命令與run命令幾乎一樣,只是在java參數(shù)中加入了precompile=yes,這里要注意下,這里加入的precompile值是yes,不是true,所以Play類中的usePrecompiled是false這里搞錯(cuò)了,Play類中的usePrecompiled檢查的參數(shù)是precompiled,而不是precompile
讓我們來看一下加入了這個(gè)java參數(shù)對(duì)程序的影響。
與預(yù)編譯有關(guān)的代碼主要是下面2段:
static boolean preCompile() { if (usePrecompiled) { if (Play.getFile("precompiled").exists()) { classloader.getAllClasses(); Logger.info("Application is precompiled"); return true; } Logger.error("Precompiled classes are missing!!"); fatalServerErrorOccurred(); return false; } //這里開始預(yù)編譯 try { Logger.info("Precompiling ..."); Thread.currentThread().setContextClassLoader(Play.classloader); long start = System.currentTimeMillis(); //getAllClasses方法較長(zhǎng),就不貼了,下面一段代碼在getAllClasses方法中進(jìn)入 classloader.getAllClasses(); if (Logger.isTraceEnabled()) { Logger.trace("%sms to precompile the Java stuff", System.currentTimeMillis() - start); } if (!lazyLoadTemplates) { start = System.currentTimeMillis(); //編譯模板 TemplateLoader.getAllTemplate(); if (Logger.isTraceEnabled()) { Logger.trace("%sms to precompile the templates", System.currentTimeMillis() - start); } } return true; } catch (Throwable e) { Logger.error(e, "Cannot start in PROD mode with errors"); fatalServerErrorOccurred(); return false; } }
public byte[] enhance() { this.enhancedByteCode = this.javaByteCode; if (isClass()) { // before we can start enhancing this class we must make sure it is not a PlayPlugin. // PlayPlugins can be included as regular java files in a Play-application. // If a PlayPlugin is present in the application, it is loaded when other plugins are loaded. // All plugins must be loaded before we can start enhancing. // This is a problem when loading PlayPlugins bundled as regular app-class since it uses the same classloader // as the other (soon to be) enhanched play-app-classes. boolean shouldEnhance = true; try { CtClass ctClass = enhanceChecker_classPool.makeClass(new ByteArrayInputStream(this.enhancedByteCode)); if (ctClass.subclassOf(ctPlayPluginClass)) { shouldEnhance = false; } } catch( Exception e) { // nop } if (shouldEnhance) { Play.pluginCollection.enhance(this); } } //主要是這一段,他將增強(qiáng)處理后的字節(jié)碼寫入了文件,增強(qiáng)處理在之后會(huì)深入展開 if (System.getProperty("precompile") != null) { try { // emit bytecode to standard class layout as well File f = Play.getFile("precompiled/java/" + (name.replace(".", "/")) + ".class"); f.getParentFile().mkdirs(); FileOutputStream fos = new FileOutputStream(f); fos.write(this.enhancedByteCode); fos.close(); } catch (Exception e) { e.printStackTrace(); } } return this.enhancedByteCode; }
預(yù)編譯過程結(jié)束后,腳本正式開始打包過程,打包過程比較簡(jiǎn)單,就是講預(yù)編譯后的字節(jié)碼文件、模板文件、配置文件、使用的類庫(kù)、使用的模塊類庫(kù)等移動(dòng)至WEB-INF文件夾中,如果使用了--zip,那么腳本會(huì)將生成的文件夾用zip格式打包。
secret和statussecret命令能生成一個(gè)新的secret key
status命令是用于實(shí)時(shí)顯示程序的運(yùn)行狀態(tài),腳本的運(yùn)作十分簡(jiǎn)單,步驟如下:
檢查是否有--url參數(shù),有則在他之后添加@status
檢查是否存在--secret
如果沒有--url,則使用http://localhost:%http_port/@status;如果沒有 --secret,則從配置文件中讀取secret key
將secret_key、"@status"使用sha加密,并加入Authorization請(qǐng)求頭
發(fā)送請(qǐng)求
@status的實(shí)現(xiàn)和@kill一樣在CorePlugin類中,這在之后再進(jìn)行詳解。
總結(jié)Play的啟動(dòng)腳本分析至此就結(jié)束了,從腳本的分析過程中我們可以稍微探究下Play在腳本啟動(dòng)階段有何行為,這對(duì)我們進(jìn)行腳本改造或者啟動(dòng)優(yōu)化還是非常有幫助的。
下一篇,我們來看看Play的啟動(dòng)類是如何運(yùn)作的。。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/68304.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()來對(duì)Play類進(jìn)行初始化。那在解析初始化之前,我們先...
摘要:是一個(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類 既然是...
摘要:本文將著重介紹使用來部署一個(gè)基于的應(yīng)用程序會(huì)多么便捷,當(dāng)然這個(gè)過程主要基于插件。如你所見,這是一個(gè)基于的應(yīng)用程序。這個(gè)基于的應(yīng)用程序?qū)o法被訪問??偨Y(jié)可以如此簡(jiǎn)單地給一個(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)證。在這...
摘要:為了使用最新的,升級(jí)到配置修改根據(jù)官網(wǎng)的升級(jí)指南,修改文件,更改插件版本號(hào)文件中,把和單獨(dú)加入。此文件為首頁(yè)的模板。推測(cè)可能是版本和版本的首頁(yè)模板不同,于是到官網(wǎng)下載版本的,找到并覆蓋項(xiàng)目的相應(yīng)文件。添加插件的語(yǔ)句至此,升級(jí)成功完成。 為了使用最新的Play WS Api,升級(jí)到play 2.6.21 1.配置修改 根據(jù)官網(wǎng)的升級(jí)指南,修改plugins.sbt文件,更改插件版本號(hào):a...
閱讀 2485·2021-11-17 09:33
閱讀 772·2021-11-04 16:13
閱讀 1345·2021-10-14 09:50
閱讀 707·2019-08-30 15:53
閱讀 3675·2019-08-30 14:18
閱讀 3278·2019-08-30 14:14
閱讀 2112·2019-08-30 12:46
閱讀 3192·2019-08-26 14:05