亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

Play framework源碼解析 Part1:Play framework 介紹、項目構(gòu)成及啟動

Riddler / 1297人閱讀

摘要:注本系列文章所用版本為介紹是個輕量級的框架,致力于讓程序員實現(xiàn)快速高效開發(fā),它具有以下幾個方面的優(yōu)勢熱加載。在調(diào)試模式下,所有修改會及時生效。拋棄配置文件。約定大于配置。

注:本系列文章所用play版本為1.2.6
介紹

Play framework是個輕量級的RESTful框架,致力于讓java程序員實現(xiàn)快速高效開發(fā),它具有以下幾個方面的優(yōu)勢:

熱加載。在調(diào)試模式下,所有修改會及時生效。

拋棄xml配置文件。約定大于配置。

支持異步編程

無狀態(tài)mvc框架,拓展性良好

簡單的路由設(shè)置

這里附上Play framework的文檔地址,官方有更為詳盡的功能敘述。Play framework文檔

項目構(gòu)成

play framework的初始化非常簡單,只要下載了play的軟件包后,在命令行中運行play new xxx即可初始化一個項目。
自動生成的項目結(jié)構(gòu)如下:

運行play程序也非常簡單,在項目目錄下使用play run即可運行。

啟動腳本解析 play framework軟件包目錄

為了更好的了解play framework的運作原理,我們來從play framework的啟動腳本開始分析,分析啟動腳本有助于我們了解play framework的運行過程。
play framework1.2.6軟件包解壓后的文件如下:

play的啟動腳本是使用python編寫的,腳本的入口為play軟件包根目錄下的play文件,下面我們將從play這個腳本的主入口開始分析。

play腳本解析

play腳本在開頭引入了3個類,分別為play.cmdloader,play.application,play.utils,從添加的系統(tǒng)參數(shù)中可以看出play啟動腳本的存放路徑為 framework/pym
cmdloader.py的主要作用為加載framework/pym/commands下的各個腳本文件,用于之后對命令參數(shù)的解釋運行
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主程序根目錄下創(chuàng)建一個名為id的文件,即可設(shè)置默認的框架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ù)中,有一個ignoreMissing,這個參數(shù)的全稱其實是ignoreMissingModules,作用就是在當(dāng)配置文件中配置有模塊但是在play目錄下并沒有找到時是否忽略,如需忽略那么在啟動時需要加入--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"])來進行PlayApplication類和CommandLoader類的初始化。
在PlayApplication類的初始化過程中,它創(chuàng)建了PlayConfParser類用來解析配置文件,這也就是說play的配置文件解析是在腳本啟動階段進行的,這也是為什么修改配置文件無法實時生效需要重啟的原因
在PlayConfParser中有一個常量DEFAULTS儲存了默認的http端口和jpda調(diào)試端口,分別為9000和8000,需要添加默認值可以修改DEFAULTS,DEFAULTS內(nèi)的值只有當(dāng)配置文件中找不到時才會生效。

    DEFAULTS = {
        "http.port": "9000",
        "jpda.port": "8000"
    }

值得一提的是,配置文件中的http.port的優(yōu)先級是小于命令參數(shù)中的http.port的

    #env為命令參數(shù)
    if env.has_key("http.port"):
        self.entries["http.port"] = env["http.port"]

CommandLoader類的功能很簡單,就是遍歷framework/pym/commands下的.py文件,依次加載然后讀取他的全局變量COMMANDS和MODULE儲存用于之后的命令處理和模塊加載。

回到play腳本中,在PlayApplication類和CommandLoader類初始化完成之后,play進行"--deps"參數(shù)的檢查,如果存在--deps,則調(diào)用play.deps.DependenciesManager類來進行依賴的檢查、更新。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便正式進行啟動過程,play按照以下的順序進行加載:

加載配置文件中記錄的模塊的命令信息

加載參數(shù)中指定的模塊的命令信息

運行各模塊中的before函數(shù)

執(zhí)行play_command,play_command即為run,test,war等play需要的執(zhí)行的命令

運行各模塊中的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常用命令的運行過程...

Play常用運行命令解析

在本節(jié)的一開始,我決定先把play所有的可用命令先列舉一下,以便讀者可以選擇性閱讀。

命令名稱 命令所在文件 作用
antify ant.py 初始化ant構(gòu)建工具的build.xml文件
run base.py 運行程序
new base.py 新建play應(yīng)用
clean base.py 刪除臨時文件,即清空tmp文件夾
test base.py 運行測試程序
autotest、auto-test base.py 自動運行所有測試項目
id base.py 設(shè)置項目id
new,run base.py 新建play應(yīng)用并啟動
clean,run base.py 刪除臨時文件并運行
modules base.py 顯示項目用到的模塊,注:這里顯示的模塊只是在項目配置文件中引用的模塊,命令參數(shù)中添加的模塊不會顯示
check check.py 檢查play更新
classpath、cp classpath.py 顯示應(yīng)用的classpath
start daemon.py 在后臺運行play程序
stop daemon.py 停止正在運行的程序
restart daemon.py 重啟正在運行的程序
pid daemon.py 顯示運行中的程序的pid
out daemon.py 顯示輸出
dependencies、deps deps.py 運行DependenciesManager更新依賴
eclipsify、ec eclipse.py 創(chuàng)建eclipse配置文件
evolutions evolutions.py 運行play.db.Evolutions進行數(shù)據(jù)庫演變檢查
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 將模塊添加至項目
install modulesrepo.py 安裝模塊
netbeansify netbeans.py 生成netbeans配置文件
precompile precompile.py 預(yù)編譯
secret secret.py 生成secret key
status status.py 顯示運行中項目的狀態(tài)
version version.py 顯示play framework的版本號
war war.py 將項目打包為war文件
run與start

run應(yīng)該是我們平時用的最多的命令了,run命令的作用其實很簡單,就是根據(jù)命令參數(shù)拼接java參數(shù),然后調(diào)用java來運行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)的實現(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
    #這里其實有個問題,這里假定的是JAVA_OPTS變量里只存了jvm內(nèi)存設(shè)置,如果JAVA_OPTS還存了其他選項,那對運行可能有影響
    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")
    #讀取配置文件中的運行模式
    application_mode = self.readConf("application.mode").lower()
    #如果模式是prod,則用server模式編譯
    if application_mode == "prod":
        java_args.append("-server")
    # JDK 7 compat
    # 使用新class校驗器 (不知道作用)
    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、項目根目錄下server.pid,查找是否存在指定pid

若第一步找到pid,查找當(dāng)前進程列表中是否存在此pid進程,存在則試圖關(guān)閉進程。(如果此pid不是play的進程呢。。。)

在配置文件中找application.log.system.out看是否關(guān)閉了系統(tǒng)輸出

啟動程序,這里與run命令唯一的區(qū)別就是他指定了stdout位置,這樣就變成了后臺程序

將啟動后的程序的pid寫入server.pid

stop命令即關(guān)閉當(dāng)前進程,這里要提一下,play有個注解叫OnApplicationStop,即會在程序停止時觸發(fā),而OnApplicationStop的實現(xiàn)主要是調(diào)用了Runtime.getRuntime().addShutdownHook();來完成

test與autotest

使用play test命令可以讓程序進入測試模式,test命令和run命令其實差別不大,唯一的區(qū)別就在于使用test命令時,腳本會將play id自動替換為test,當(dāng)play id為test時會自動引入testrunner模塊,testrunner模塊主要功能為添加@tests路由并實現(xiàn)了test測試頁面,他的具體實現(xiàn)過程后續(xù)再談。
autotest命令的作用是自動測試所有的測試用例,他的執(zhí)行順序是這樣的:

檢查是否存在tmp文件夾,存在即刪除

檢查是否有程序正在運行,如存在則關(guān)閉程序

檢查程序是否配置了ssl但是沒有指定證書

檢查是否存在test-result文件夾,存在即刪除

使用test作為id重啟程序

調(diào)用play.modules.testrunner.FirePhoque來進行自動化測試

關(guān)閉程序

autotest的腳本的步驟1和2我覺得是有問題的,應(yīng)該換下順序,不然如果程序正在向tmp文件夾插入臨時文件,那么tmp文件夾就刪除失敗了。
關(guān)閉程序的代碼調(diào)用了http://localhost:%http_port/@kill進行關(guān)閉,@kill的實現(xiàn)方法在play.CorePlugin下,注意,@kill在prod模式下無效(這是廢話)
由于使用了python作為啟動腳本,無法通過java內(nèi)部變量值判斷程序是否開啟,只能查看控制臺的輸出日志,所以在使用test作為id重啟后,腳本使用下面的代碼判斷程序是否完全啟動:

try:
    #這里啟動程序
    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"則正常啟動
            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類的實現(xiàn)過程我們之后再詳解。

new與clean

我們使用new來創(chuàng)建一個新項目,play new的使用方法為play new project-name [--with] [--name]。
with參數(shù)為項目所使用的模塊。
name為項目名,這里要注意一點,projectname和--name的參數(shù)可以設(shè)置為不同值,projectname是項目建立的文件夾名,--name的值為項目配置文件中的application.name。
如果不加--name,腳本會提示你是否使用projectname作為名字,在確認之后,腳本會將resources/application-skel中的所有文件拷貝到projectname文件夾下,然后用輸入的name替換項目配置文件下的application.name,并生成一個64位的secretKey替換配置文件中的secretKey。
接著,腳本會查找使用的模塊中是否存在dependencies.yml,并將dependencies.yml中的內(nèi)容加入項目的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命令非常簡單,就是刪除整個tmp文件夾

war與precompile

很多時候,我們需要使用tomcat等服務(wù)器容器作為服務(wù)載體,這時候就需要將play應(yīng)用打包為war
war的使用參數(shù)是play war project-name [-o/--output][filename] [--zip] [--exclude][exclude-directories]
使用-o或--output來指定輸出文件夾,使用--zip壓縮為war格式,使用--exclude來包含另外需要打包的文件夾
要注意的是,必須在項目目錄外進行操作,不然會失敗
在參數(shù)處理完畢后,腳本正式開始打包過程,分為2個步驟:1.預(yù)編譯。2:打包
預(yù)編譯即用到了precompile命令,precompile命令與run命令幾乎一樣,只是在java參數(shù)中加入了precompile=yes,這里要注意下,這里加入的precompile值是yes,不是true,所以Play類中的usePrecompiled是false這里搞錯了,Play類中的usePrecompiled檢查的參數(shù)是precompiled,而不是precompile
讓我們來看一下加入了這個java參數(shù)對程序的影響。
與預(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方法較長,就不貼了,下面一段代碼在getAllClasses方法中進入
            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);
            }
        }

        //主要是這一段,他將增強處理后的字節(jié)碼寫入了文件,增強處理在之后會深入展開
        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é)束后,腳本正式開始打包過程,打包過程比較簡單,就是講預(yù)編譯后的字節(jié)碼文件、模板文件、配置文件、使用的類庫、使用的模塊類庫等移動至WEB-INF文件夾中,如果使用了--zip,那么腳本會將生成的文件夾用zip格式打包。

secret和status

secret命令能生成一個新的secret key
status命令是用于實時顯示程序的運行狀態(tài),腳本的運作十分簡單,步驟如下:

檢查是否有--url參數(shù),有則在他之后添加@status

檢查是否存在--secret

如果沒有--url,則使用http://localhost:%http_port/@status;如果沒有 --secret,則從配置文件中讀取secret key

將secret_key、"@status"使用sha加密,并加入Authorization請求頭

發(fā)送請求

@status的實現(xiàn)和@kill一樣在CorePlugin類中,這在之后再進行詳解。

總結(jié)

Play的啟動腳本分析至此就結(jié)束了,從腳本的分析過程中我們可以稍微探究下Play在腳本啟動階段有何行為,這對我們進行腳本改造或者啟動優(yōu)化還是非常有幫助的。
下一篇,我們來看看Play的啟動類是如何運作的。。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/68304.html

相關(guān)文章

  • Play framework源碼解析 Part3:Play的初始化與啟動

    摘要:使用自建的類加載器主要是為了便于處理預(yù)編譯后的字節(jié)碼以及方便在模式下進行即時的熱更新。 注:本系列文章所用play版本為1.2.6 在上一篇中,我們分析了play的2種啟動方式,這一篇,我們來看看Play類的初始化過程 Play類 無論是Server還是ServletWrapper方式運行,在他們的入口中都會運行Play.init()來對Play類進行初始化。那在解析初始化之前,我們先...

    xuxueli 評論0 收藏0
  • Play framework源碼解析 Part2:Server與ServletWrapper

    摘要:是一個抽象類,繼承了接口,它的方法是這個類的核心。因為可能需要一個返回值,所以它同時繼承了接口來提供返回值。 注:本系列文章所用play版本為1.2.6 在上一篇中我們剖析了Play framework的啟動原理,很容易就能發(fā)現(xiàn)Play framework的啟動主入口在play.server.Server中,在本節(jié),我們來一起看看Server類中主要發(fā)生了什么。 Server類 既然是...

    JiaXinYi 評論0 收藏0
  • 如何使用 Docker 部署一個基于 Play Framework 的 Scala Web 應(yīng)用?

    摘要:本文將著重介紹使用來部署一個基于的應(yīng)用程序會多么便捷,當(dāng)然這個過程主要基于插件。如你所見,這是一個基于的應(yīng)用程序。這個基于的應(yīng)用程序?qū)o法被訪問。總結(jié)可以如此簡單地給一個基于的應(yīng)用程序建立,相信很多人都會像筆者一樣離不開它。 本文作者 Jacek Laskowski 擁有近20年的應(yīng)用程序開發(fā)經(jīng)驗,現(xiàn) CodiLime 的軟件開發(fā)團隊 Leader,曾從 IBM 取得多種資格認證。在這...

    2501207950 評論0 收藏0
  • Play Framework升級到2.6.x的填坑記錄

    摘要:為了使用最新的,升級到配置修改根據(jù)官網(wǎng)的升級指南,修改文件,更改插件版本號文件中,把和單獨加入。此文件為首頁的模板。推測可能是版本和版本的首頁模板不同,于是到官網(wǎng)下載版本的,找到并覆蓋項目的相應(yīng)文件。添加插件的語句至此,升級成功完成。 為了使用最新的Play WS Api,升級到play 2.6.21 1.配置修改 根據(jù)官網(wǎng)的升級指南,修改plugins.sbt文件,更改插件版本號:a...

    voidking 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<