Odoo中文网|Odoo实施培训

 找回密码
 立即注册
搜索
热搜: Odoo OpenERP 实施
查看: 6106|回复: 0

OpenERP 模块动态加载原理及启动代码分析

[复制链接]

119

主题

119

帖子

566

积分

高级会员

Rank: 4

积分
566
发表于 2016-11-3 21:43:54 | 显示全部楼层 |阅读模式
一般来说我们在编程中,对象定义都是预先定义好的。一些 OOP 语言(包括 Python/Java)允许对象是 自省的(也称为 反射)。即,自省对象能够描述自己:实例属于哪个类?类有哪些祖先?对象可以用哪些方法和属性?自省让处理对象的函数或方法根据传递给函数或方法的对象类型来做决定。即允许对象在运行时动态改变方法成员等属性。

得益于OpenERP ORM 模型的精巧设计,实际上 OpenERP 运行时也是动态读取模块信息并动态构建对象的。如在模块开发中,继承了 'res.users', 新增一个方法或新增一个字段。在OpenERP 导入该模块后, OpenERP 会马上重构 'res.users' 对象并将新增的方法或字段添加到该对象。那么,OpenERP 是如何做到这点的呢? 让我们从OpenERP 的启动部分开始分析:

首先,OpenERP 启动相关的服务, 这时并没有建立数据库链接和载入对象
  1. if not config["stop_after_init"]:
  2.         setup_pid_file()
  3.         # Some module register themselves when they are loaded so we need the
  4.         # services to be running before loading any registry.
  5.         if config['workers']:
  6.             openerp.service.start_services_workers()
  7.         else:
  8.             openerp.service.start_services()
复制代码

不过可以在配置文件中指定 'db_name' 参数,可以让 OpenERP 在启动时加载指定数据库的对象并启动 Cron。 实际生产环境中建议启用该参数,否则需要在启动OpenERP后,登录一次OpenERP 在会加载对象并启动CRON
  1. if config['db_name']:
  2.         for dbname in config['db_name'].split(','):
  3.             preload_registry(dbname)
复制代码
不指定 'db_name' 参数情况下,OpenERP 会直到用户登录时才会初始化指定数据库。
  1. def login(db, login, password):
  2.     pool = pooler.get_pool(db)
  3.     user_obj = pool.get('res.users')
  4.     return user_obj.login(db, login, password)
复制代码
打开 get_db 和 get_db_and_pool 定义
  1. def get_db(db_name):
  2.     """Return a database connection. The corresponding registry is initialized."""
  3.     return get_db_and_pool(db_name)[0]


  4. def get_db_and_pool(db_name, force_demo=False, status=None, update_module=False):
  5.     """Create and return a database connection and a newly initialized registry."""
  6.     registry = RegistryManager.get(db_name, force_demo, status, update_module)
  7.     return registry.db, registry
复制代码
顺藤摸瓜,看RegistryManager.new 定义
  1.      try:
  2.                 # This should be a method on Registry
  3.                 openerp.modules.load_modules(registry.db, force_demo, status, update_module)
  4.             except Exception:
  5.                 del cls.registries[db_name]
  6.                 raise
复制代码
然后看到 load_modules, 终于要加载了,嘿嘿。
  1.   # STEP 1: LOAD BASE (must be done before module dependencies can be computed for later steps)
  2.         graph = openerp.modules.graph.Graph()
  3.         graph.add_module(cr, 'base', force)
  4.         if not graph:
  5.             _logger.critical('module base cannot be loaded! (hint: verify addons-path)')
  6.             raise osv.osv.except_osv(_('Could not load base module'), _('module base cannot be loaded! (hint: verify addons-path)'))

  7.         # processed_modules: for cleanup step after install
  8.         # loaded_modules: to avoid double loading
  9.         report = pool._assertion_report
  10.         loaded_modules, processed_modules = load_module_graph(cr, graph, status, perform_checks=update_module, report=report)

  11.         if tools.config['load_language']:
  12.             for lang in tools.config['load_language'].split(','):
  13.                 tools.load_language(cr, lang)

  14.         # STEP 2: Mark other modules to be loaded/updated
  15.         if update_module:
  16.             modobj = pool.get('ir.module.module')
  17.             if ('base' in tools.config['init']) or ('base' in tools.config['update']):
  18.                 _logger.info('updating modules list')
  19.                 modobj.update_list(cr, SUPERUSER_ID)

  20.             _check_module_names(cr, itertools.chain(tools.config['init'].keys(), tools.config['update'].keys()))

  21.             mods = [k for k in tools.config['init'] if tools.config['init'][k]]
  22.             if mods:
  23.                 ids = modobj.search(cr, SUPERUSER_ID, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
  24.                 if ids:
  25.                     modobj.button_install(cr, SUPERUSER_ID, ids)

  26.             mods = [k for k in tools.config['update'] if tools.config['update'][k]]
  27.             if mods:
  28.                 ids = modobj.search(cr, SUPERUSER_ID, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
  29.                 if ids:
  30.                     modobj.button_upgrade(cr, SUPERUSER_ID, ids)

  31.             cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))


  32.         # STEP 3: Load marked modules (skipping base which was done in STEP 1)
  33.         # IMPORTANT: this is done in two parts, first loading all installed or
  34.         #            partially installed modules (i.e. installed/to upgrade), to
  35.         #            offer a consistent system to the second part: installing
  36.         #            newly selected modules.
  37.         #            We include the modules 'to remove' in the first step, because
  38.         #            they are part of the "currently installed" modules. They will
  39.         #            be dropped in STEP 6 later, before restarting the loading
  40.         #            process.
  41.         states_to_load = ['installed', 'to upgrade', 'to remove']
  42.         processed = load_marked_modules(cr, graph, states_to_load, force, status, report, loaded_modules, update_module)
  43.         processed_modules.extend(processed)
  44.         if update_module:
  45.             states_to_load = ['to install']
  46.             processed = load_marked_modules(cr, graph, states_to_load, force, status, report, loaded_modules, update_module)
  47.             processed_modules.extend(processed)
复制代码
这里,第一步是加载核心模块 ['base'],第二步加载需要升级或预载的模块,第三步加载已安装的模块。实际加载语句是:
程序代码: [url=][选择][/url]
        processed = load_marked_modules(cr, graph, states_to_load, force, status, report, loaded_modules, update_module)

查看 load_marked_module 定义:
  1. def load_marked_modules(cr, graph, states, force, progressdict, report, loaded_modules, perform_checks):
  2.     """Loads modules marked with ``states``, adding them to ``graph`` and
  3.        ``loaded_modules`` and returns a list of installed/upgraded modules."""
  4.     processed_modules = []
  5.     while True:
  6.         cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(states),))
  7.         module_list = [name for (name,) in cr.fetchall() if name not in graph]
  8.         graph.add_modules(cr, module_list, force)
  9.         _logger.debug('Updating graph with %d more modules', len(module_list))
  10.         loaded, processed = load_module_graph(cr, graph, progressdict, report=report, skip_modules=loaded_modules, perform_checks=perform_checks)
  11.         processed_modules.extend(processed)
  12.         loaded_modules.extend(loaded)
  13.         if not processed: break
  14.     return processed_modules
复制代码

重点是 load_module_graph
  1. # register, instantiate and initialize models for each modules
  2.     for index, package in enumerate(graph):
  3.         module_name = package.name
  4.         module_id = package.id

  5.         if skip_modules and module_name in skip_modules:
  6.             continue

  7.         _logger.debug('module %s: loading objects', package.name)
  8.         migrations.migrate_module(package, 'pre')
  9.         load_openerp_module(package.name)

  10.         models = pool.load(cr, package)

  11.         loaded_modules.append(package.name)
  12.         if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
  13.             init_module_models(cr, package.name, models)
  14.         pool._init_modules.add(package.name)
  15.         status['progress'] = float(index) / len(graph)
复制代码
主要是下面2句,
  1.     load_openerp_module(package.name)

  2.         models = pool.load(cr, package)
复制代码
先看 load_openerp_module
  1.   initialize_sys_path()
  2.     try:
  3.         mod_path = get_module_path(module_name)
  4.         zip_mod_path = '' if not mod_path else mod_path + '.zip'
  5.         if not os.path.isfile(zip_mod_path):
  6.             __import__('openerp.addons.' + module_name)
  7.         else:
  8.             zimp = zipimport.zipimporter(zip_mod_path)
  9.             zimp.load_module(module_name)
复制代码
上面代码中 import 了一个模块。下面就涉及到元类了:
import 的时候 就会调用元类的构造函数
  1. def __init__(self, name, bases, attrs):
  2.         if not self._register:
  3.             self._register = True
  4.             super(MetaModel, self).__init__(name, bases, attrs)
  5.             return

  6.         # The (OpenERP) module name can be in the `openerp.addons` namespace
  7.         # or not. For instance module `sale` can be imported as
  8.         # `openerp.addons.sale` (the good way) or `sale` (for backward
  9.         # compatibility).
  10.         module_parts = self.__module__.split('.')
  11.         if len(module_parts) > 2 and module_parts[0] == 'openerp' and \
  12.             module_parts[1] == 'addons':
  13.             module_name = self.__module__.split('.')[2]
  14.         else:
  15.             module_name = self.__module__.split('.')[0]
  16.         if not hasattr(self, '_module'):
  17.             self._module = module_name

  18.         # Remember which models to instanciate for this module.
  19.         if not self._custom:
  20.             self.module_to_models.setdefault(self._module, []).append(self)
复制代码
上面的代码基本上就是将自身类加入到 module_to_models 字典中。

然后我们来看pool.load
  1. def load(self, cr, module):
  2.         """ Load a given module in the registry.

  3.         At the Python level, the modules are already loaded, but not yet on a
  4.         per-registry level. This method populates a registry with the given
  5.         modules, i.e. it instanciates all the classes of a the given module
  6.         and registers them in the registry.

  7.         """
  8.         models_to_load = [] # need to preserve loading order
  9.         # Instantiate registered classes (via the MetaModel automatic discovery
  10.         # or via explicit constructor call), and add them to the pool.
  11.         for cls in openerp.osv.orm.MetaModel.module_to_models.get(module.name, []):
  12.             # models register themselves in self.models
  13.             model = cls.create_instance(self, cr)
  14.             if model._name not in models_to_load:
  15.                 # avoid double-loading models whose declaration is split
  16.                 models_to_load.append(model._name)
  17.         return [self.models[m] for m in models_to_load]
复制代码
这里我们可以看到 MetaModel 的身影,cls.create_instance(self, cr) 这里就是动态构造对象的核心代码。

  1. parent_names = getattr(cls, '_inherit', None)
  2.         # 判断是否有继承父类
  3.         if parent_names:
  4.             if isinstance(parent_names, (str, unicode)):
  5.                 name = cls._name or parent_names
  6.                 parent_names = [parent_names]
  7.             else:
  8.                 name = cls._name
  9.             if not name:
  10.                 raise TypeError('_name is mandatory in case of multiple inheritance')

  11.             for parent_name in ((type(parent_names)==list) and parent_names or [parent_names]):
  12.                 # 读取父类
  13.                 parent_model = pool.get(parent_name)
  14.                 if not parent_model:
  15.                     raise TypeError('The model "%s" specifies an unexisting parent class "%s"\n'
  16.                         'You may need to add a dependency on the parent class\' module.' % (name, parent_name))
  17.                 if not getattr(cls, '_original_module', None) and name == parent_model._name:
  18.                     cls._original_module = parent_model._original_module
  19.                 parent_class = parent_model.__class__
  20.                 nattr = {}
  21.                 # 复制父类属性
  22.                 for s in attributes:
  23.                     new = copy.copy(getattr(parent_model, s, {}))
  24.                     if s == '_columns':
  25.                         # Don't _inherit custom fields.
  26.                         for c in new.keys():
  27.                             if new[c].manual:
  28.                                 del new[c]
  29.                         # Duplicate float fields because they have a .digits
  30.                         # cache (which must be per-registry, not server-wide).
  31.                         for c in new.keys():
  32.                             if new[c]._type == 'float':
  33.                                 new[c] = copy.copy(new[c])
  34.                     if hasattr(new, 'update'):
  35.                         new.update(cls.__dict__.get(s, {}))
  36.                     elif s=='_constraints':
  37.                         for c in cls.__dict__.get(s, []):
  38.                             exist = False
  39.                             for c2 in range(len(new)):
  40.                                 #For _constraints, we should check field and methods as well
  41.                                 if new[c2][2]==c[2] and (new[c2][0] == c[0] \
  42.                                         or getattr(new[c2][0],'__name__', True) == \
  43.                                             getattr(c[0],'__name__', False)):
  44.                                     # If new class defines a constraint with
  45.                                     # same function name, we let it override
  46.                                     # the old one.

  47.                                     new[c2] = c
  48.                                     exist = True
  49.                                     break
  50.                             if not exist:
  51.                                 new.append(c)
  52.                     else:
  53.                         new.extend(cls.__dict__.get(s, []))
  54.                     nattr[s] = new

  55.                 # Keep links to non-inherited constraints, e.g. useful when exporting translations
  56.                 nattr['_local_constraints'] = cls.__dict__.get('_constraints', [])
  57.                 nattr['_local_sql_constraints'] = cls.__dict__.get('_sql_constraints', [])
  58.                 # 调用元类构造函数
  59.                 cls = type(name, (cls, parent_class), dict(nattr, _register=False))
  60.         else:
  61.             cls._local_constraints = getattr(cls, '_constraints', [])
  62.             cls._local_sql_constraints = getattr(cls, '_sql_constraints', [])

  63.         if not getattr(cls, '_original_module', None):
  64.             cls._original_module = cls._module
  65.         # 构造对象
  66.         obj = object.__new__(cls)
  67.         # 初始化对象
  68.         obj.__init__(pool, cr)
  69.         return obj
复制代码
上面代码很重要,可以看到首先是判断该对象是否有继承父类,如果没有就直接构造,和动态没有什么关系。

如果有继承父类, 就复制父类属性, 这里就是动态构建类的做法。

假如有不同模块,都继承了同一个父类,那么如何保证类成员和属性是否加载完整或覆盖呢? 答案在于这句代码:

程序代码: [url=][选择][/url]
                parent_model = pool.get(parent_name)

Registry.get 的定义
程序代码: [url=][选择][/url]
    def get(self, model_name):        """ Return a model for a given name or None if it doesn't exist."""        return self.models.get(model_name)

最后看看obj.__init__(pool, cr)初始化对象,做了什么动作?
程序代码: [url=][选择][/url]
    def __init__(self, pool, cr):        """ Initialize a model and make it part of the given registry.        - copy the stored fields' functions in the osv_pool,        - update the _columns with the fields found in ir_model_fields,        - ensure there is a many2one for each _inherits'd parent,        - update the children's _columns,        - give a chance to each field to initialize itself.        """        pool.add(self._name, self)        self.pool = pool

pool.add(self._name, self) 定义如下:
程序代码: [url=][选择][/url]
    def add(self, model_name, model):        """ Add or replace a model in the registry."""        self.models[model_name] = model

到这里应该很非常清楚,Registry.models 保存了对象的 model 信息。这样多个对象继承同一父类时,按照加载顺序先后动态构建相关的类。

至此,OpenERP 启动时动态加载模块分析完成。如模块安装、升级、卸载等, 则是通过 signal_registry_change 和 check_registry_signaling 处理,重新载入 Registry, 然后重新构建 OpenERP 对象。

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|小黑屋|技术支持|开发手册|Odoo中文网-远鼎旗下odoo培训网站 ( 苏ICP备15039516号 )

GMT+8, 2024-3-29 16:59 , Processed in 0.015168 second(s), 10 queries , Xcache On.

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表