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

資訊專欄INFORMATION COLUMN

Play framework源碼解析 Part3:Play的初始化與啟動(dòng)

xuxueli / 2416人閱讀

摘要:使用自建的類加載器主要是為了便于處理預(yù)編譯后的字節(jié)碼以及方便在模式下進(jìn)行即時(shí)的熱更新。

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

在上一篇中,我們分析了play的2種啟動(dòng)方式,這一篇,我們來(lái)看看Play類的初始化過(guò)程

Play類

無(wú)論是Server還是ServletWrapper方式運(yùn)行,在他們的入口中都會(huì)運(yùn)行Play.init()來(lái)對(duì)Play類進(jìn)行初始化。那在解析初始化之前,我們先來(lái)看看Play類是做什么的,它里面有什么重要的方法。
首先要明確的一點(diǎn)是,Play類是整個(gè)Play framework框架的管理、配置中心,它存放了大部分框架需要的成員變量,例如id,配置信息,所有加載的class,使用的插件管理器等等。下圖就是Play類中的方法列表。

這其中加注釋的幾個(gè)方法是比較重要的,我們下面便來(lái)從init開始一點(diǎn)點(diǎn)剖析Play類中的各個(gè)方法。

Play的初始化
    public static void init(File root, String id) {
        // Simple things
        Play.id = id;
        Play.started = false;
        Play.applicationPath = root;

        // 加載所有 play.static 中的記錄的類
        initStaticStuff();
        //猜測(cè)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());
        //設(shè)置臨時(shí)文件夾
        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)");
                }
            }
        }

        // 設(shè)置運(yùn)行模式
        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);

        // 設(shè)置文件路徑
        VirtualFile appRoot = VirtualFile.open(applicationPath);
        roots.add(appRoot);
        javaPath = new CopyOnWriteArrayList();
        javaPath.add(appRoot.child("app"));
        javaPath.add(appRoot.child("conf"));

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

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

        // 設(shè)置模塊路徑
        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 = "";
        }

        // 設(shè)置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直接啟動(dòng)
        if (mode == Mode.PROD || System.getProperty("precompile") != null) {
            mode = Mode.PROD;
            //預(yù)編譯
            if (preCompile() && System.getProperty("precompile") == null) {
                start();
            } else {
                return;
            }
        } else {
            Logger.warn("You"re running Play! in DEV mode");
        }

        pluginCollection.onApplicationReady();

        Play.initialized = true;
    }

如上面的代碼所示,初始化過(guò)程主要的順序?yàn)椋?/p>

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

獲取框架路徑

讀取配置文件

初始化日志

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

加載模塊

加載插件

若為prod模式進(jìn)行預(yù)編譯

我們來(lái)依次看看Play在這些過(guò)程中做了什么事情。

加載play.static

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

    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);
            }
        }
    }
獲取框架路徑

獲取框架路徑很簡(jiǎn)單,就是判斷是用jar模式運(yùn)行還是直接用文件運(yùn)行,然后讀取對(duì)應(yīng)的路徑

    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);
        }
    }
讀取配置文件

首先要說(shuō)明一下,我們這里討論的是在Play類初始化過(guò)程中的讀取配置文件過(guò)程,為什么要指出這一點(diǎn)呢,因?yàn)榕渲梦募x取后會(huì)調(diào)用插件的onConfigurationRead方法,而在初始化過(guò)程中,配置文件是優(yōu)先于插件加載的,所以在初始化過(guò)程中插件的方法并不會(huì)生效。等到Play調(diào)用start方法啟動(dòng)服務(wù)器時(shí),會(huì)重新讀取配置文件,那時(shí)候插件列表已經(jīng)更新完畢,會(huì)執(zhí)行onConfigurationRead方法。
配置文件的讀取和啟動(dòng)腳本中的解析方式基本一樣,步驟就是下面幾步:

讀取application.conf,將其中的項(xiàng)全部加入Properties

將所有配置項(xiàng)用正則過(guò)濾找出真實(shí)配置名,并找出所用id的配置項(xiàng)替換

將配置項(xiàng)中的${..}替換為對(duì)應(yīng)值

讀取@include引用的配置

我們來(lái)看下play對(duì)${..}替換過(guò)程

    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;
            //重點(diǎn)是下面這個(gè)判斷
            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},我們還可以通過(guò)使用參數(shù)和修改環(huán)境變量來(lái)添加可替換的值

初始化日志

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

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

查找是否有l(wèi)og4j.xml,存在跳至4

查找是否有l(wèi)og4j.properties,存在跳至4

根據(jù)文件后綴使用對(duì)應(yīng)的日志配置解析器

如果是測(cè)試模式則加上寫入測(cè)試結(jié)果文件的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);
        }
        //找不到配置文件就關(guān)閉日志
        if (log4jConf == null) {
            Properties shutUp = new Properties();
            shutUp.setProperty("log4j.rootLogger", "OFF");
            PropertyConfigurator.configure(shutUp);
        } else if (Logger.log4j == null) {
            //判斷日志配置文件是否在應(yīng)用目錄下,加這條是因?yàn)閜lay軟件包目錄下有默認(rèn)的日志配置文件
            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;
            }
            //根據(jù)不同的類型解析
            if (isXMLConfig) {
                DOMConfigurator.configure(log4jConf);
            } else {
                PropertyConfigurator.configure(log4jConf);
            }
            Logger.log4j = org.apache.log4j.Logger.getLogger("play");
            // 測(cè)試模式下,將日志追加到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的優(yōu)先級(jí)高于log4j.properties,這里可以發(fā)現(xiàn)一個(gè)問題,Play的日志初始化完全是針對(duì)log4j來(lái)進(jìn)行的,但是翻看Logger類的代碼可以看到,所有的日志輸出方法都會(huì)先判斷是否使用java.util.logging.Logger來(lái)輸出日志,在Logger類中也有一個(gè)forceJuli字段來(lái)判斷是否使用,但是這個(gè)字段一直沒有使用過(guò),也就是說(shuō)只要不手動(dòng)改,那forceJuli一直是false。
如果沒有做任何配置,那么直接使用Logger來(lái)輸出日志,那么顯示的類名就只會(huì)是Play,需要在配置文件中加入application.log.recordCaller=true來(lái)將日志輸出時(shí)顯示的類變?yōu)檎{(diào)用類名

查找文件路徑

Play類中記錄的路徑有5種,分別為根目錄路徑,java文件路徑,模板文件路徑,主路由路徑,模塊路由路徑。文件路徑的添加很簡(jiǎn)單,就是將文件路徑記錄在對(duì)應(yīng)的變量中,這里要提的一點(diǎn)是,conf文件夾是被加入到j(luò)ava文件路徑中的,所以寫在conf文件夾中的java源碼也是可以使用的。當(dāng)然,在conf文件夾里寫源碼大概會(huì)被罵死

加載模塊

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

    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());
        }
    }

可以看出引入模塊其實(shí)就是在各個(gè)路徑下加入模塊路徑

加載插件

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

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

讀取每一個(gè)play.plugins,play.plugins中的記錄由2部分組成,一個(gè)是插件優(yōu)先級(jí),一個(gè)是類名,逐行讀取后根據(jù)優(yōu)先級(jí)和類目組成LoadingPluginInfo,并加入List

根據(jù)優(yōu)先級(jí)對(duì)List排序

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

初始化插件

更新插件列表

我們先來(lái)看看將插件加入所有插件列表的過(guò)程

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

這是啟用插件的方法

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

這里有一個(gè)問題是,既然加入插件列表時(shí)會(huì)進(jìn)行排序,那對(duì)List的排序是否就不需要了呢,其實(shí)不是的,因?yàn)樵诩尤氩寮斜頃r(shí)會(huì)反射生成PlayPlugin的一個(gè)實(shí)例,如果實(shí)例的靜態(tài)代碼段對(duì)插件進(jìn)行了修改,就會(huì)出現(xiàn)問題。
在插件加入完畢后,會(huì)循環(huán)列表進(jìn)行插件的初始化

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

這里為什么要在循環(huán)里再判斷一遍插件是否啟用呢,是為了讓高優(yōu)先級(jí)插件可以禁用低優(yōu)先級(jí)插件。

預(yù)編譯

預(yù)編譯是為了在prod模式下加快加載速度,預(yù)編譯方法的作用是將已經(jīng)預(yù)編譯好的文件讀入或?qū)ξ募M(jìn)行預(yù)編譯,對(duì)文件預(yù)編譯包括了java文件的預(yù)編譯以及模板文件的預(yù)編譯,我們分別來(lái)看看

java預(yù)編譯

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

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

掃描之前設(shè)定的javaPath下存在的java文件,讀取類名,創(chuàng)建ApplicationClass,ApplicationClass類內(nèi)有類名、java文件、java代碼、編譯后字節(jié)碼、增強(qiáng)后字節(jié)碼等字段,ApplicationClasses中存放的就是一個(gè)個(gè)ApplicationClass

使用eclipse JDT對(duì)源碼進(jìn)行編譯,編譯后的字節(jié)碼存到對(duì)應(yīng)的ApplicationClass中

遍歷ApplicationClasses中的所有ApplicationClass,判斷其是否需要增強(qiáng),需要增強(qiáng)的類用插件進(jìn)行增強(qiáng)并寫入文件

play的編譯器使用的是play.classloading.ApplicationCompiler類,這里對(duì)編譯過(guò)程就不做更多的闡述。
這里有幾點(diǎn)值得提一下

上面步驟2中掃描java文件只根據(jù)java文件名來(lái)判斷類名,也就是說(shuō)在步驟2的時(shí)候,ApplicationClasses容器中存放的class還只是單純的外部類,而內(nèi)部類與匿名內(nèi)部類還不包括在內(nèi),在步驟3使用jdt編譯時(shí),ApplicationCompiler類會(huì)將匿名類與內(nèi)部類都加到ApplicationClasses容器中,所以在第4步遍歷的時(shí)候也會(huì)將內(nèi)部類與匿名類的字節(jié)碼文件增強(qiáng)然后加到文件中

由于步驟1會(huì)檢查是否有插件會(huì)編譯源碼,也就意味著我們可以通過(guò)自寫插件來(lái)覆蓋play原有的編譯過(guò)程。這點(diǎn)我覺得非常不錯(cuò),因?yàn)樵谑褂胮lay做為工程框架時(shí),等到代碼量很大時(shí),每次編譯過(guò)程都會(huì)非常漫長(zhǎng),因?yàn)閜lay默認(rèn)會(huì)將所有java文件全部重新編譯一遍,而這有時(shí)候是很不必要的,因?yàn)槠鋵?shí)只要重新編譯更改的文件即可,針對(duì)這一點(diǎn)我寫了一個(gè)根據(jù)文件更改時(shí)間選擇編譯的插件,這個(gè)放到之后的play應(yīng)用中再談。

下面就是java預(yù)編譯的主要代碼

    //判斷是否有插件會(huì)進(jìn)行編譯
    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);
            }
        }
        //調(diào)用編譯器編譯
        Play.classes.compiler.compile(classNames.toArray(new String[classNames.size()]));
    }

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

        if (Logger.isTraceEnabled()) {
            Logger.trace("Compiling code for %s", name);
        }
        //判斷applicationClass是一個(gè)類還是package-info
        if (!applicationClass.isClass()) {
            definePackage(applicationClass.getPackage(), null, null, null, null, null, null, null);
        } else {
            loadPackage(name);
        }
        //如果之前的編譯結(jié)果存在,就用之前的
        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編譯過(guò)了或者編譯后有字節(jié)碼,進(jìn)行字節(jié)碼增強(qiáng)
        if (applicationClass.javaByteCode != null || applicationClass.compile() != null) {
            //進(jìn)行字節(jié)碼增強(qiáng)
            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);
    }
模板預(yù)編譯

不同于java的預(yù)編譯,模板的預(yù)編譯結(jié)果雖然也會(huì)存放在precompile文件夾,但是在運(yùn)行過(guò)程中模板并不是一次性全部加載的,模板的加載主要通過(guò)play.templates.TemplateLoader來(lái)進(jìn)行,TemplateLoader中存放了使用過(guò)的模板信息。
在模板預(yù)編譯過(guò)程中,主要是步驟如下:

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

遍歷文件,用TemplateLoader.load來(lái)加載源文件,并進(jìn)行模板編譯,產(chǎn)生Groovy源碼

對(duì)編譯后的Groovy源碼進(jìn)行編譯,并用base64加密寫入文件

這里只對(duì)模板預(yù)編譯流程做一個(gè)梳理,至于編譯的過(guò)程放在之后的模板篇再說(shuō)

play framework的啟動(dòng)

從上一篇server與servletWrapper中我們可以發(fā)現(xiàn),play framework并不是一定在腳本啟動(dòng)之后便啟動(dòng)服務(wù)器,在我們使用dev模式進(jìn)行開發(fā)時(shí)也會(huì)發(fā)現(xiàn),play總是需要接受到一個(gè)請(qǐng)求后才會(huì)有真正的啟動(dòng)流程。我們這一節(jié)就來(lái)看看play的啟動(dòng)過(guò)程是怎么樣的,這個(gè)啟動(dòng)與server或servletWrapper的啟動(dòng)的區(qū)別在于,play啟動(dòng)后便真正開始業(yè)務(wù)處理,而server與servletWrapper的啟動(dòng)僅僅是啟動(dòng)了監(jiān)聽端口,當(dāng)然要清楚的是servletWrapper啟動(dòng)時(shí)也會(huì)自動(dòng)啟動(dòng)play

public static synchronized void start() {
    try {
        //如果已經(jīng)啟動(dòng)了,先停止,這里是為了dev模式的熱更新
        if (started) {
            stop();
        }
        //如果不是獨(dú)立的server,即如果不是放在servlet容器中運(yùn)行,注冊(cè)關(guān)閉事件
        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模式啟動(dòng),重新加載所有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"));

        // 設(shè)置語(yǔ)言
        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();

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

        // 設(shè)置默認(rèn)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();

        // 運(yùn)行插件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();

        // 運(yùn)行插件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的啟動(dòng)和play的初始化有很多相同的地方,包括加載配置,加載日志等,啟動(dòng)過(guò)程有很多有意思的地方

play的啟動(dòng)不會(huì)重置模塊列表,也就是說(shuō)如果在dev模式下對(duì)配置文件進(jìn)行修改增加了模塊必須要重啟服務(wù)器,熱更新是無(wú)效的。

在play的初始化過(guò)程中,如果是使用容器打開服務(wù)器時(shí)就會(huì)加載預(yù)編譯文件,這時(shí)候已經(jīng)讀取一遍預(yù)編譯源碼了,所有在啟動(dòng)過(guò)程中的getAllClasses就不會(huì)再去查找了

插件的onApplicationStart方法一般就是執(zhí)行些插件初始化的工作,所以遇到錯(cuò)誤就會(huì)中斷啟動(dòng)過(guò)程,而afterApplicationStart是在啟動(dòng)完成后在運(yùn)行的,很有意思的是,用@OnApplicationStart標(biāo)識(shí)的job類是在afterApplicationStart階段運(yùn)行的,而不是onApplicationStart階段。那為什么要叫這個(gè)名字

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

總結(jié)

Play類的初始化與啟動(dòng)已經(jīng)說(shuō)的差不多了,下一篇我們來(lái)看下ActionInvoker與mvc

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/70955.html

相關(guān)文章

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

    摘要:注本系列文章所用版本為介紹是個(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í)...

    Riddler 評(píng)論0 收藏0
  • Play framework源碼解析 Part2:ServerServletWrapper

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

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

    摘要:本文將著重介紹使用來(lái)部署一個(gè)基于的應(yīng)用程序會(huì)多么便捷,當(dāng)然這個(gè)過(guò)程主要基于插件。如你所見,這是一個(gè)基于的應(yīng)用程序。這個(gè)基于的應(yīng)用程序?qū)o(wú)法被訪問。總結(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)證。在這...

    2501207950 評(píng)論0 收藏0
  • Play Framework升級(jí)到2.6.x填坑記錄

    摘要:為了使用最新的,升級(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...

    voidking 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

xuxueli

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<