作者:shihuaping0918@163.com朋蔫,轉(zhuǎn)載請(qǐng)注明作者
以https://github.com/NetEase/chatofpomelo/tree/master/game-server服務(wù)器為例子來(lái)說(shuō)明配置讀取是怎么完成的隔节,chatofpomelo是pomelo做為聊天服務(wù)器的例子。它的入口點(diǎn)是app.js嫂丙。代碼內(nèi)容如下:
var pomelo = require('pomelo');
var routeUtil = require('./app/util/routeUtil');
/**
* Init app for client.
*/
var app = pomelo.createApp();
//這一行也關(guān)注一下
app.set('name', 'chatofpomelo');
// app configure
app.configure('production|development', function() {
// route configures
app.route('chat', routeUtil.chat);
//這一段代碼是我們要關(guān)心的
app.set('connectorConfig', {
connector: pomelo.connectors.sioconnector,
// 'websocket', 'polling-xhr', 'polling-jsonp', 'polling'
transports: ['websocket', 'polling'],
heartbeats: true,
closeTimeout: 60 * 1000,
heartbeatTimeout: 60 * 1000,
heartbeatInterval: 25 * 1000
});
// filter configures
app.filter(pomelo.timeout());
});
// start app
app.start();
//全局吃掉未處理異常
process.on('uncaughtException', function(err) {
console.error(' Caught exception: ' + err.stack);
});
從代碼中看,都是通過(guò)app.set添加配置——這是pomelo比較不同的地方,常見(jiàn)的配置都是分成多個(gè)文件冯遂,然后通過(guò)include方式集中到一個(gè)文件脐往【慵茫或者就只有一個(gè)文件。但是pomelo的配置可以零散的這配一點(diǎn)钙勃,那配一點(diǎn)蛛碌。app.set這個(gè)函數(shù)的定義在application.js里面,內(nèi)容如下:
/**
* Assign `setting` to `val`, or return `setting`'s value.
*
* Example:
*
* app.set('key1', 'value1');
* app.get('key1'); // 'value1'
* app.key1; // undefined
*
* app.set('key2', 'value2', true);
* app.get('key2'); // 'value2'
* app.key2; // 'value2'
*
* @param {String} setting the setting of application
* @param {String} val the setting's value
* @param {Boolean} attach whether attach the settings to application
* @return {Server|Mixed} for chaining, or the setting value
* @memberOf Application
*/
Application.set = function (setting, val, attach) {
if (arguments.length === 1) {
return this.settings[setting];
}
this.settings[setting] = val;
if(attach) {
this[setting] = val;
}
return this;
};
這個(gè)函數(shù)的注釋寫得是非常 的詳細(xì)了辖源,相對(duì)我前面剛分析的那些c代碼來(lái)說(shuō)蔚携,那些代碼是基本沒(méi)注釋。從代碼中可以看出來(lái)克饶,實(shí)際上是把配置加到了settings里去了酝蜒。settings的初始化是setttings={},也就是初始是個(gè)空對(duì)象矾湃。
到這里配置的設(shè)置就分析完了亡脑,pomelo就是這么簡(jiǎn)單直接。配置的key是connectorConfig,value是一個(gè)js對(duì)象霉咨。下面看一下它是在哪里讀取的蛙紫。它一定是在Application.start里面讀取的。
/**
* Start application. It would load the default components and start all the loaded components.
*
* @param {Function} cb callback function
* @memberOf Application
*/
Application.start = function(cb) {
this.startTime = Date.now();
if(this.state > STATE_INITED) {
utils.invokeCallback(cb, new Error('application has already start.'));
return;
}
var self = this;
appUtil.startByType(self, function() { // 先調(diào)startByType
appUtil.loadDefaultComponents(self); //然后startByType回調(diào)從這開(kāi)始
var startUp = function() {
appUtil.optComponents(self.loaded, Constants.RESERVED.START, function(err) {
self.state = STATE_START;
if(err) {
utils.invokeCallback(cb, err);
} else {
logger.info('%j enter after start...', self.getServerId());
self.afterStart(cb);
}
});
};
var beforeFun = self.lifecycleCbs[Constants.LIFECYCLE.BEFORE_STARTUP];
if(!!beforeFun) {
beforeFun.call(null, self, startUp);
} else {
startUp();
}
});
};
appUtil.startByType這一篇不涉及途戒,就直接看回調(diào)了坑傅,回調(diào)第一行就是appUtil.loadDefaultComponents,這個(gè)函數(shù)會(huì)去讀設(shè)置喷斋。但是這個(gè)函數(shù)有個(gè)非常不好的行為唁毒,就是字符常量基本都是硬編碼,也就是大家常說(shuō)的星爪,在代碼里寫死浆西。如果不來(lái)看代碼,誰(shuí)也不知道這個(gè)配置就一定是要叫這個(gè)名字顽腾。
/**
* Load default components for application.
*/
module.exports.loadDefaultComponents = function(app) {
var pomelo = require('../pomelo');
// load system default components
if (app.serverType === Constants.RESERVED.MASTER) {
app.load(pomelo.master, app.get('masterConfig'));
} else {
app.load(pomelo.proxy, app.get('proxyConfig'));
if (app.getCurServer().port) {
app.load(pomelo.remote, app.get('remoteConfig'));
}
if (app.isFrontend()) {
app.load(pomelo.connection, app.get('connectionConfig'));
//就是它室谚,在這里被加載了
app.load(pomelo.connector, app.get('connectorConfig'));
app.load(pomelo.session, app.get('sessionConfig'));
// compatible for schedulerConfig
if(app.get('schedulerConfig')) {
app.load(pomelo.pushScheduler, app.get('schedulerConfig'));
} else {
app.load(pomelo.pushScheduler, app.get('pushSchedulerConfig'));
}
}
app.load(pomelo.backendSession, app.get('backendSessionConfig'));
app.load(pomelo.channel, app.get('channelConfig'));
app.load(pomelo.server, app.get('serverConfig'));
}
app.load(pomelo.monitor, app.get('monitorConfig'));
};
分析到這里還沒(méi)完成,因?yàn)橹环治龅搅伺渲帽蛔x出來(lái)崔泵,讀出來(lái)以后的行為還沒(méi)有分析到秒赤。也就是說(shuō)這個(gè)配置到底用來(lái)干什么?這就要看app.load了憎瘸。app.load第一個(gè)參數(shù)叫pomelo.connector入篮,這個(gè)東西實(shí)際上是用__defineGetter__
來(lái)做了一次函數(shù)的封裝,它實(shí)際上是一個(gè)函數(shù)幌甘。__defineGetter__
不是標(biāo)準(zhǔn)里面的潮售。
/**
* Load component
*
* @param {String} name (optional) name of the component
* @param {Object} component component instance or factory function of the component
* @param {[type]} opts (optional) construct parameters for the factory function
* @return {Object} app instance for chain invoke
* @memberOf Application
*/
Application.load = function(name, component, opts) {
if(typeof name !== 'string') { //name是函數(shù)
opts = component; //參數(shù)移位
component = name; //參數(shù)移位
name = null;
if(typeof component.name === 'string') {
name = component.name;
}
}
if(typeof component === 'function') { //移位后component是函數(shù)
component = component(this, opts);
}
if(!name && typeof component.name === 'string') {
name = component.name;
}
if(name && this.components[name]) {
// ignore duplicat component
logger.warn('ignore duplicate component: %j', name);
return;
}
this.loaded.push(component);
if(name) {
// components with a name would get by name throught app.components later.
this.components[name] = component;
}
return this;
};
所以在load這個(gè)函數(shù)里,name傳進(jìn)來(lái)實(shí)際是個(gè)函數(shù)锅风,對(duì)這個(gè)函數(shù)做調(diào)用酥诽,把opts作為參數(shù)傳進(jìn)去。這樣就實(shí)現(xiàn)了模塊的加載皱埠。
以pomelo.connector為例解釋一下模塊加載的過(guò)程肮帐,上面講到了pomelo.connector實(shí)際上是函數(shù),這是怎么實(shí)現(xiàn)的呢边器?首先到pomelo.js里去搜索训枢,肯定是找不到.connector的。因?yàn)樗峭ㄟ^(guò)下面這幾行代碼添加進(jìn)去的忘巧。在components目錄下有一個(gè)文件叫connector.js
/**
* Auto-load bundled components with getters.
*/
fs.readdirSync(__dirname + '/components').forEach(function (filename) {
if (!/\.js$/.test(filename)) {
return;
}
//對(duì)于connector.js恒界,返回connector
var name = path.basename(filename, '.js');
//這個(gè)bind下面再講
var _load = load.bind(null, './components/', name);
Pomelo.components.__defineGetter__(name, _load);
//看這里,__defineGetter__設(shè)置了砚嘴,當(dāng)訪問(wèn)pomelo.getter的時(shí)候十酣,
//調(diào)_load函數(shù)
Pomelo.__defineGetter__(name, _load);
});
從代碼中可以看出來(lái)涩拙,當(dāng)pomelo.connector被訪問(wèn)時(shí),會(huì)被調(diào)用一個(gè)_load函數(shù)耸采。
下面再來(lái)看看這個(gè)load函數(shù)做了什么兴泥?load函數(shù)實(shí)際上是執(zhí)行了require,加載模塊洋幻。
function load(path, name) {
if (name) {
return require(path + name);
}
return require(path);
}
但是_load呢,是load.bind的結(jié)果翅娶。實(shí)際是把this指針設(shè)為null了文留。
bind的說(shuō)明:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
這里的require會(huì)返回一個(gè)函數(shù),require('connector')會(huì)返回函數(shù)竭沫≡锍幔看代碼吧,conpoment/connector.js里有一句話蜕提。
module.exports = function(app, opts) {
return new Component(app, opts);
};
這個(gè)函數(shù)才是app.load真正調(diào)用的函數(shù)森书,也就是pomelo.connector返回的值,也就是require返回的值谎势。
所以app.load(pomelo.connector, app.get('connectorConfig'));
這行代碼實(shí)際上是加載component/connector模塊凛膏,然后執(zhí)行模塊exports的函數(shù),將配置傳進(jìn)去脏榆,從而創(chuàng)建component猖毫。這個(gè)過(guò)程我是覺(jué)得已經(jīng)講得很詳細(xì)了,各位觀眾能不能理解就不好說(shuō)了须喂。