国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

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

xuxueli / 2423人閱讀

摘要:使用自建的類加載器主要是為了便于處理預編譯后的字節碼以及方便在模式下進行即時的熱更新。

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

在上一篇中,我們分析了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();
        javaPath.add(appRoot.child("app"));
        javaPath.add(appRoot.child("conf"));

        // 設置模板路徑
        if (appRoot.child("app/views").exists()) {
            templatesPath = new ArrayList(2);
            templatesPath.add(appRoot.child("app/views"));
        } else {
            templatesPath = new ArrayList(1);
        }

        // 設置路由文件
        routes = appRoot.child("conf/routes");

        // 設置模塊路徑
        modulesRoutes = new HashMap(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;
    }

如上面的代碼所示,初始化過程主要的順序為:

加載所有play.static中記錄的類

獲取框架路徑

讀取配置文件

初始化日志

獲取java文件、模板文件路徑

加載模塊

加載插件

若為prod模式進行預編譯

我們來依次看看Play在這些過程中做了什么事情。

加載play.static

Play在初始化過程中會調用initStaticStuff()方法來檢查代碼目錄下是否存在play.static文件,如果存在,那么就逐行讀取文件中記錄的類,并通過反射加載類中的靜態初始化代碼段。至于作用嗎,不知道有什么用,這段代碼的優先級太高了,早于初始化過程運行,若在初始化過程結束后運行還可以用來覆寫Play類中的配置信息。或者自己寫插件然后在play.static中初始化插件依賴?或者綁定新的數據源?

    public static void initStaticStuff() {
        // Play! plugings
        Enumeration urls = null;
        try {
            urls = Play.class.getClassLoader().getResources("play.static");
        } catch (Exception e) {
        }
        while (urls != null && urls.hasMoreElements()) {
            URL url = urls.nextElement();
            try {
                BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
                String line = null;
                while ((line = reader.readLine()) != null) {
                    try {
                        Class.forName(line);
                    } catch (Exception e) {
                        Logger.warn("! Cannot init static: " + line);
                    }
                }
            } catch (Exception ex) {
                Logger.error(ex, "Cannot load %s", url);
            }
        }
    }
獲取框架路徑

獲取框架路徑很簡單,就是判斷是用jar模式運行還是直接用文件運行,然后讀取對應的路徑

    public static void guessFrameworkPath() {
        // Guess the framework path
        try {
            URL versionUrl = Play.class.getResource("/play/version");
            // Read the content of the file
            Play.version = new LineNumberReader(new InputStreamReader(versionUrl.openStream())).readLine();

            // This is used only by the embedded server (Mina, Netty, Jetty etc)
            URI uri = new URI(versionUrl.toString().replace(" ", "%20"));
            if (frameworkPath == null || !frameworkPath.exists()) {
                if (uri.getScheme().equals("jar")) {
                    String jarPath = uri.getSchemeSpecificPart().substring(5, uri.getSchemeSpecificPart().lastIndexOf("!"));
                    frameworkPath = new File(jarPath).getParentFile().getParentFile().getAbsoluteFile();
                } else if (uri.getScheme().equals("file")) {
                    frameworkPath = new File(uri).getParentFile().getParentFile().getParentFile().getParentFile();
                } else {
                    throw new UnexpectedException("Cannot find the Play! framework - trying with uri: " + uri + " scheme " + uri.getScheme());
                }
            }
        } catch (Exception e) {
            throw new UnexpectedException("Where is the framework ?", e);
        }
    }
讀取配置文件

首先要說明一下,我們這里討論的是在Play類初始化過程中的讀取配置文件過程,為什么要指出這一點呢,因為配置文件讀取后會調用插件的onConfigurationRead方法,而在初始化過程中,配置文件是優先于插件加載的,所以在初始化過程中插件的方法并不會生效。等到Play調用start方法啟動服務器時,會重新讀取配置文件,那時候插件列表已經更新完畢,會執行onConfigurationRead方法。
配置文件的讀取和啟動腳本中的解析方式基本一樣,步驟就是下面幾步:

讀取application.conf,將其中的項全部加入Properties

將所有配置項用正則過濾找出真實配置名,并找出所用id的配置項替換

將配置項中的${..}替換為對應值

讀取@include引用的配置

我們來看下play對${..}替換過程

    Pattern pattern = Pattern.compile("${([^}]+)}");
    for (Object key : propsFromFile.keySet()) {
        String value = propsFromFile.getProperty(key.toString());
        Matcher matcher = pattern.matcher(value);
        StringBuffer newValue = new StringBuffer(100);
        while (matcher.find()) {
            String jp = matcher.group(1);
            String r;
            //重點是下面這個判斷
            if (jp.equals("application.path")) {
                r = Play.applicationPath.getAbsolutePath();
            } else if (jp.equals("play.path")) {
                r = Play.frameworkPath.getAbsolutePath();
            } else {
                r = System.getProperty(jp);
                if (r == null) {
                    r = System.getenv(jp);
                }
                if (r == null) {
                    Logger.warn("Cannot replace %s in configuration (%s=%s)", jp, key, value);
                    continue;
                }
            }
            matcher.appendReplacement(newValue, r.replaceAll("", ""));
        }
        matcher.appendTail(newValue);
        propsFromFile.setProperty(key.toString(), newValue.toString());
    }

可以看出除了${application.path}和${play.path},我們還可以通過使用參數和修改環境變量來添加可替換的值

初始化日志

Play的日志處理放在Logger類中,默認使用log4j作為日志記錄工具,初始化過程的順序如下

查找配置文件中是否有日志配置文件路徑,存在跳至4

查找是否有log4j.xml,存在跳至4

查找是否有log4j.properties,存在跳至4

根據文件后綴使用對應的日志配置解析器

如果是測試模式則加上寫入測試結果文件的Appender

    public static void init() {
        //查找日志配置文件路徑,沒有則用log4j.xml
        String log4jPath = Play.configuration.getProperty("application.log.path", "/log4j.xml");
        URL log4jConf = Logger.class.getResource(log4jPath);
        boolean isXMLConfig = log4jPath.endsWith(".xml");
        //日志配置不存在,查找log4j.properties
        if (log4jConf == null) { // try again with the .properties
            isXMLConfig = false;
            log4jPath = Play.configuration.getProperty("application.log.path", "/log4j.properties");
            log4jConf = Logger.class.getResource(log4jPath);
        }
        //找不到配置文件就關閉日志
        if (log4jConf == null) {
            Properties shutUp = new Properties();
            shutUp.setProperty("log4j.rootLogger", "OFF");
            PropertyConfigurator.configure(shutUp);
        } else if (Logger.log4j == null) {
            //判斷日志配置文件是否在應用目錄下,加這條是因為play軟件包目錄下有默認的日志配置文件
            if (log4jConf.getFile().indexOf(Play.applicationPath.getAbsolutePath()) == 0) {
                // The log4j configuration file is located somewhere in the application folder,
                // so it"s probably a custom configuration file
                configuredManually = true;
            }
            //根據不同的類型解析
            if (isXMLConfig) {
                DOMConfigurator.configure(log4jConf);
            } else {
                PropertyConfigurator.configure(log4jConf);
            }
            Logger.log4j = org.apache.log4j.Logger.getLogger("play");
            // 測試模式下,將日志追加到test-result/application.log
            if (Play.runingInTestMode()) {
                org.apache.log4j.Logger rootLogger = org.apache.log4j.Logger.getRootLogger();
                try {
                    if (!Play.getFile("test-result").exists()) {
                        Play.getFile("test-result").mkdir();
                    }
                    Appender testLog = new FileAppender(new PatternLayout("%d{DATE} %-5p ~ %m%n"), Play.getFile("test-result/application.log").getAbsolutePath(), false);
                    rootLogger.addAppender(testLog);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

初始化的代碼可以看出log4j.xml的優先級高于log4j.properties,這里可以發現一個問題,Play的日志初始化完全是針對log4j來進行的,但是翻看Logger類的代碼可以看到,所有的日志輸出方法都會先判斷是否使用java.util.logging.Logger來輸出日志,在Logger類中也有一個forceJuli字段來判斷是否使用,但是這個字段一直沒有使用過,也就是說只要不手動改,那forceJuli一直是false。
如果沒有做任何配置,那么直接使用Logger來輸出日志,那么顯示的類名就只會是Play,需要在配置文件中加入application.log.recordCaller=true來將日志輸出時顯示的類變為調用類名

查找文件路徑

Play類中記錄的路徑有5種,分別為根目錄路徑,java文件路徑,模板文件路徑,主路由路徑,模塊路由路徑。文件路徑的添加很簡單,就是將文件路徑記錄在對應的變量中,這里要提的一點是,conf文件夾是被加入到java文件路徑中的,所以寫在conf文件夾中的java源碼也是可以使用的。當然,在conf文件夾里寫源碼大概會被罵死

加載模塊

Play通過調用loadModules()來加載所有模塊,使用的模塊在3個地方查找,1是系統環境變量,環境變量MODULES記錄的模塊將加載,2是配置文件中記錄的模塊,配置信息為module.開頭的模塊被加載,3是應用目錄下的modules文件夾下的模塊被加載。在查找完畢后,會判斷是否使用測試模式,是則加入_testrunner模塊,并判斷是否為dev模式,是則會加入_docviewer模塊。
這里我們來看下Play將模塊文件加入應用是如何做的

    public static void addModule(String name, File path) {
        VirtualFile root = VirtualFile.open(path);
        modules.put(name, root);
        if (root.child("app").exists()) {
            javaPath.add(root.child("app"));
        }
        if (root.child("app/views").exists()) {
            templatesPath.add(root.child("app/views"));
        }
        if (root.child("conf/routes").exists()) {
            modulesRoutes.put(name, root.child("conf/routes"));
        }
        roots.add(root);
        if (!name.startsWith("_")) {
            Logger.info("Module %s is available (%s)", name, path.getAbsolutePath());
        }
    }

可以看出引入模塊其實就是在各個路徑下加入模塊路徑

加載插件

插件(plugins)是Play framework框架非常重要的組成部分,插件作用于Play運行時的方方面面,包括請求前后的處理,更新字節碼等等,具體的插件使用說明我們放在之后的插件篇再說,這里就說說加載插件的過程。
插件的加載過程如下:

查找java文件路徑下存在的play.plugins文件

讀取每一個play.plugins,play.plugins中的記錄由2部分組成,一個是插件優先級,一個是類名,逐行讀取后根據優先級和類目組成LoadingPluginInfo,并加入List

根據優先級對List排序

根據排序完后的列表依次將插件加入至所有插件列表

初始化插件

更新插件列表

我們先來看看將插件加入所有插件列表的過程

    protected boolean addPlugin( PlayPlugin plugin ){
        synchronized( lock ){
            //判斷插件列表是否存在插件
            if( !allPlugins.contains(plugin) ){
                allPlugins.add( plugin );
                //根據優先級排序
                Collections.sort(allPlugins);
                //創建只讀的插件列表
                allPlugins_readOnlyCopy = createReadonlyCopy( allPlugins);
                //啟用插件
                enablePlugin(plugin);
                return true;
            }
        }
        return false;
    }

這是啟用插件的方法

    public boolean enablePlugin( PlayPlugin plugin ){
        synchronized( lock ){
            //檢查是否存在插件
            if( allPlugins.contains( plugin )){
                //檢查插件是否已在啟動列表
                if( !enabledPlugins.contains( plugin )){
                    //加入啟動插件
                    enabledPlugins.add( plugin );
                    //排序
                    Collections.sort( enabledPlugins);
                    //創建只讀列表
                    enabledPlugins_readOnlyCopy = createReadonlyCopy( enabledPlugins);
                    //更新插件列表
                    updatePlayPluginsList();
                    Logger.trace("Plugin " + plugin + " enabled");
                    return true;
                }
            }
        }
        return false;
    }

這里有一個問題是,既然加入插件列表時會進行排序,那對List的排序是否就不需要了呢,其實不是的,因為在加入插件列表時會反射生成PlayPlugin的一個實例,如果實例的靜態代碼段對插件進行了修改,就會出現問題。
在插件加入完畢后,會循環列表進行插件的初始化

    for( PlayPlugin plugin : getEnabledPlugins()){
        if( isEnabled(plugin)){
            initializePlugin(plugin);
        }
    }

這里為什么要在循環里再判斷一遍插件是否啟用呢,是為了讓高優先級插件可以禁用低優先級插件。

預編譯

預編譯是為了在prod模式下加快加載速度,預編譯方法的作用是將已經預編譯好的文件讀入或對文件進行預編譯,對文件預編譯包括了java文件的預編譯以及模板文件的預編譯,我們分別來看看

java預編譯

在細說java預編譯過程之前,我覺得有必要先來說一下play framework的類加載機制。
play.classloading.ApplicationClassloader是play框架的類加載器,所有play框架的代碼均由ApplicationClassloader來加載。使用自建的類加載器主要是為了便于處理預編譯后的字節碼以及方便在dev模式下進行即時的熱更新。
play.classloading.ApplicationClasses類是應用代碼的容器,里面存放了所有的class。
ApplicationClassloader調用通過查找ApplicationClasses來獲取對應的類代碼(具體來說沒有這么簡單,因為這邊只談預編譯,所以具體的流程暫且不談,在之后的classloader篇再細說)
java預編譯的入口在ApplicationClassloader.getAllClasses();它的過程如下:

檢查插件是否有編譯源碼的方法,若有跳至4

掃描之前設定的javaPath下存在的java文件,讀取類名,創建ApplicationClass,ApplicationClass類內有類名、java文件、java代碼、編譯后字節碼、增強后字節碼等字段,ApplicationClasses中存放的就是一個個ApplicationClass

使用eclipse JDT對源碼進行編譯,編譯后的字節碼存到對應的ApplicationClass中

遍歷ApplicationClasses中的所有ApplicationClass,判斷其是否需要增強,需要增強的類用插件進行增強并寫入文件

play的編譯器使用的是play.classloading.ApplicationCompiler類,這里對編譯過程就不做更多的闡述。
這里有幾點值得提一下

上面步驟2中掃描java文件只根據java文件名來判斷類名,也就是說在步驟2的時候,ApplicationClasses容器中存放的class還只是單純的外部類,而內部類與匿名內部類還不包括在內,在步驟3使用jdt編譯時,ApplicationCompiler類會將匿名類與內部類都加到ApplicationClasses容器中,所以在第4步遍歷的時候也會將內部類與匿名類的字節碼文件增強然后加到文件中

由于步驟1會檢查是否有插件會編譯源碼,也就意味著我們可以通過自寫插件來覆蓋play原有的編譯過程。這點我覺得非常不錯,因為在使用play做為工程框架時,等到代碼量很大時,每次編譯過程都會非常漫長,因為play默認會將所有java文件全部重新編譯一遍,而這有時候是很不必要的,因為其實只要重新編譯更改的文件即可,針對這一點我寫了一個根據文件更改時間選擇編譯的插件,這個放到之后的play應用中再談。

下面就是java預編譯的主要代碼

    //判斷是否有插件會進行編譯
    if(!Play.pluginCollection.compileSources()) {

        List all = new ArrayList();
        //在javaPath中找所有類
        for (VirtualFile virtualFile : Play.javaPath) {
            all.addAll(getAllClasses(virtualFile));
        }
        List classNames = new ArrayList();
        //將所有類名組成list
        for (int i = 0; i < all.size(); i++) {
                ApplicationClass applicationClass = all.get(i);
            if (applicationClass != null && !applicationClass.compiled && applicationClass.isClass()) {
                classNames.add(all.get(i).name);
            }
        }
        //調用編譯器編譯
        Play.classes.compiler.compile(classNames.toArray(new String[classNames.size()]));
    }

    //遍歷所有類,添加至allClasses,即ApplicationClasses容器中
    for (ApplicationClass applicationClass : Play.classes.all()) {
        //loadApplicationClass方法關鍵代碼如下
        Class clazz = loadApplicationClass(applicationClass.name);
        if (clazz != null) {
            allClasses.add(clazz);
        }
    }
    long start = System.currentTimeMillis();
    //從ApplicationClasses容器中找對應的ApplicationClass
    ApplicationClass applicationClass = Play.classes.getApplicationClass(name);
    if (applicationClass != null) {
        //isDefinable方法就是判斷applicationClass是否已經編譯且存在對應的javaClass
        if (applicationClass.isDefinable()) {
            return applicationClass.javaClass;
        }
        //查找之前是否存在編譯結果
        byte[] bc = BytecodeCache.getBytecode(name, applicationClass.javaSource);

        if (Logger.isTraceEnabled()) {
            Logger.trace("Compiling code for %s", name);
        }
        //判斷applicationClass是一個類還是package-info
        if (!applicationClass.isClass()) {
            definePackage(applicationClass.getPackage(), null, null, null, null, null, null, null);
        } else {
            loadPackage(name);
        }
        //如果之前的編譯結果存在,就用之前的
        if (bc != null) {
            applicationClass.enhancedByteCode = bc;
            applicationClass.javaClass = defineClass(applicationClass.name, applicationClass.enhancedByteCode, 0, applicationClass.enhancedByteCode.length, protectionDomain);
            resolveClass(applicationClass.javaClass);
            if (!applicationClass.isClass()) {
                applicationClass.javaPackage = applicationClass.javaClass.getPackage();
            }

            if (Logger.isTraceEnabled()) {
                Logger.trace("%sms to load class %s from cache", System.currentTimeMillis() - start, name);
            }

            return applicationClass.javaClass;
        }
        //如果applicationClass編譯過了或者編譯后有字節碼,進行字節碼增強
        if (applicationClass.javaByteCode != null || applicationClass.compile() != null) {
            //進行字節碼增強
            applicationClass.enhance();
            applicationClass.javaClass = defineClass(applicationClass.name, applicationClass.enhancedByteCode, 0, applicationClass.enhancedByteCode.length, protectionDomain);
            BytecodeCache.cacheBytecode(applicationClass.enhancedByteCode, name, applicationClass.javaSource);
            resolveClass(applicationClass.javaClass);
            if (!applicationClass.isClass()) {
                applicationClass.javaPackage = applicationClass.javaClass.getPackage();
            }

            if (Logger.isTraceEnabled()) {
                Logger.trace("%sms to load class %s", System.currentTimeMillis() - start, name);
            }

            return applicationClass.javaClass;
        }
        Play.classes.classes.remove(name);
    }
模板預編譯

不同于java的預編譯,模板的預編譯結果雖然也會存放在precompile文件夾,但是在運行過程中模板并不是一次性全部加載的,模板的加載主要通過play.templates.TemplateLoader來進行,TemplateLoader中存放了使用過的模板信息。
在模板預編譯過程中,主要是步驟如下:

掃描Play.templatesPath目錄下所有模板文件

遍歷文件,用TemplateLoader.load來加載源文件,并進行模板編譯,產生Groovy源碼

對編譯后的Groovy源碼進行編譯,并用base64加密寫入文件

這里只對模板預編譯流程做一個梳理,至于編譯的過程放在之后的模板篇再說

play framework的啟動

從上一篇server與servletWrapper中我們可以發現,play framework并不是一定在腳本啟動之后便啟動服務器,在我們使用dev模式進行開發時也會發現,play總是需要接受到一個請求后才會有真正的啟動流程。我們這一節就來看看play的啟動過程是怎么樣的,這個啟動與server或servletWrapper的啟動的區別在于,play啟動后便真正開始業務處理,而server與servletWrapper的啟動僅僅是啟動了監聽端口,當然要清楚的是servletWrapper啟動時也會自動啟動play

public static synchronized void start() {
    try {
        //如果已經啟動了,先停止,這里是為了dev模式的熱更新
        if (started) {
            stop();
        }
        //如果不是獨立的server,即如果不是放在servlet容器中運行,注冊關閉事件
        if( standalonePlayServer) {
            // Can only register shutdown-hook if running as standalone server
            if (!shutdownHookEnabled) {
                //registers shutdown hook - Now there"s a good chance that we can notify
                //our plugins that we"re going down when some calls ctrl+c or just kills our process..
                shutdownHookEnabled = true;
                Runtime.getRuntime().addShutdownHook(new Thread() {
                    public void run() {
                        Play.stop();
                    }
                });
            }
        }
        //如果是dev模式啟動,重新加載所有class和插件
        if (mode == Mode.DEV) {
            // Need a new classloader
            classloader = new ApplicationClassloader();
            // Put it in the current context for any code that relies on having it there
            Thread.currentThread().setContextClassLoader(classloader);
            // Reload plugins
            pluginCollection.reloadApplicationPlugins();

        }

        // 讀取配置文件
        readConfiguration();

        // 配置日志
        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"));

        // 設置語言
        langs = new ArrayList(Arrays.asList(configuration.getProperty("application.langs", "").split(",")));
        if (langs.size() == 1 && langs.get(0).trim().length() == 0) {
            langs = new ArrayList(16);
        }

        // 重新加載模板
        TemplateLoader.cleanCompiledCache();

        // 設置secretKey
        secretKey = configuration.getProperty("application.secret", "").trim();
        if (secretKey.length() == 0) {
            Logger.warn("No secret key defined. Sessions will not be encrypted");
        }

        // 設置默認web encoding
        String _defaultWebEncoding = configuration.getProperty("application.web_encoding");
        if( _defaultWebEncoding != null ) {
            Logger.info("Using custom default web encoding: " + _defaultWebEncoding);
            defaultWebEncoding = _defaultWebEncoding;
            // Must update current response also, since the request/response triggering
            // this configuration-loading in dev-mode have already been
            // set up with the previous encoding
            if( Http.Response.current() != null ) {
                Http.Response.current().encoding = _defaultWebEncoding;
            }
        }

        // 加載所有class
        Play.classloader.getAllClasses();

        // 加載路由
        Router.detectChanges(ctxPath);

        // 初始化緩存
        Cache.init();

        // 運行插件onApplicationStart方法
        try {
            pluginCollection.onApplicationStart();
        } catch (Exception e) {
            if (Play.mode.isProd()) {
                Logger.error(e, "Can"t start in PROD mode with errors");
            }
            if (e instanceof RuntimeException) {
                throw (RuntimeException) e;
            }
            throw new UnexpectedException(e);
        }

        if (firstStart) {
            Logger.info("Application "%s" is now started !", configuration.getProperty("application.name", ""));
            firstStart = false;
        }

        // We made it
        started = true;
        startedAt = System.currentTimeMillis();

        // 運行插件afterApplicationStart方法
        pluginCollection.afterApplicationStart();

    } catch (PlayException e) {
        started = false;
        try { Cache.stop(); } catch (Exception ignored) {}
        throw e;
    } catch (Exception e) {
        started = false;
        try { Cache.stop(); } catch (Exception ignored) {}
        throw new UnexpectedException(e);
    }
}

可以看出play的啟動和play的初始化有很多相同的地方,包括加載配置,加載日志等,啟動過程有很多有意思的地方

play的啟動不會重置模塊列表,也就是說如果在dev模式下對配置文件進行修改增加了模塊必須要重啟服務器,熱更新是無效的。

在play的初始化過程中,如果是使用容器打開服務器時就會加載預編譯文件,這時候已經讀取一遍預編譯源碼了,所有在啟動過程中的getAllClasses就不會再去查找了

插件的onApplicationStart方法一般就是執行些插件初始化的工作,所以遇到錯誤就會中斷啟動過程,而afterApplicationStart是在啟動完成后在運行的,很有意思的是,用@OnApplicationStart標識的job類是在afterApplicationStart階段運行的,而不是onApplicationStart階段。那為什么要叫這個名字

可以看出路由的解析是在play的啟動過程中進行的,具體過程就是讀取路徑下的路由文件,然后路由的具體解析過程放在模板篇一起講吧

總結

Play類的初始化與啟動已經說的差不多了,下一篇我們來看下ActionInvoker與mvc

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/70955.html

相關文章

  • Play framework源碼解析 Part1:Play framework 介紹、項目構成及啟動

    摘要:注本系列文章所用版本為介紹是個輕量級的框架,致力于讓程序員實現快速高效開發,它具有以下幾個方面的優勢熱加載。在調試模式下,所有修改會及時生效。拋棄配置文件。約定大于配置。 注:本系列文章所用play版本為1.2.6 介紹 Play framework是個輕量級的RESTful框架,致力于讓java程序員實現快速高效開發,它具有以下幾個方面的優勢: 熱加載。在調試模式下,所有修改會及時...

    Riddler 評論0 收藏0
  • Play framework源碼解析 Part2:ServerServletWrapper

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

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

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

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

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

    voidking 評論0 收藏0

發表評論

0條評論

xuxueli

|高級講師

TA的文章

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