公司的管理后台是用SSM开发的,出于好奇,自己也研究了一下SSM框架,然后发现Spring中使用注解的方式实现依赖注入很有意思,恰好自己做的项目依赖的组件越来越多,每次新增一个组件都会使得初始化代码更丑陋了一点,于是我想,有没有一种类似于Spring的依赖注入方式,来解决Python项目中的各种组件依赖问题呢?
晚上回去琢磨了一会儿,发现这样的实现其实很简单,利用装饰器就好了。
设计概要
理想中的依赖注入有几个点:
- 提供一个注册接口用于注册服务
- 提供注入接口用于注入服务,就我目前的工程来说,需要满足: 支持异步方法注入
- 注册的服务需要支持继承(最核心的是要支持super关键字)
根据这种思想,假设我的注解中心叫PIOC,那么注册操作为:
@PIOC.service('service_name', 'service_require1', .., 'service_requireN')
class OneService(SuperClass):
def __init__(self, service_require1=None, ..., service_requireN=None):
pass
这里service装饰器第一个参数定义了该服务的检索名称,后面的参数定义了该服务依赖的其它服务,在调用__init__方法时框架会将依赖的服务注入到传入参数中,之所以使用key=value的方式是为了不干扰其它参数
对应的注入操作为
@PIOC.require('service1', 'service2')
def func(service1=None, service2=None):
pass
这样,在调用func的时候框架会自动将service1和service2传入到方法的参数中去
这样的话一个简单的服务注册和注入框架就设计完成了,接下来看代码实现:
### 代码实现
class PIOC(object):
_objects = {}
_mapping = {}
@classmethod
def process_dependency(cls, *services):
out = {}
for service in services:
logging.debug('get service: %s', service)
inst = cls._objects.get(service, None)
if not inst:
cls_def, requirements = cls._mapping[service]
logging.debug('init instance service %s with requirements: %s', service, requirements)
inst = cls_def(**cls.process_dependency(*requirements)) if requirements else cls_def()
cls._objects[service] = inst
out[service] = inst
return out
@classmethod
def service(cls, name, *requirements, **settings):
def outer(cls_def):
if name in cls._mapping:
raise ValueError('%s already registered' % name)
logging.debug('process inst: %s for service %s', requirements, name)
cls._mapping[name] = (cls_def, requirements)
if not settings.get('lazy', True):
# 是否启用懒惰初始化
# 继承关系无法使用lazy参数
cls._objects[name] = cls_def(**cls.process_dependency(*requirements)) if requirements else cls_def()
return cls_def
return outer
@classmethod
def require(cls, *services):
def outer(func):
@wraps(func)
def inner(*args, **kwargs):
kwargs.update(cls.process_dependency(*services))
return func(*args, **kwargs)
return inner
return outer
@classmethod
def require_co(cls, *services):
def outer(func):
@tornado.gen.coroutine
@wraps(func)
def inner(*args, **kwargs):
kwargs.update(cls.process_dependency(*services))
ret = yield func(*args, **kwargs)
raise tornado.gen.Return(ret)
return inner
return outer
@classmethod
def destroy(cls):
for inst in cls._objects.values():
exit_func = getattr(inst, 'destroy', lambda: None)
exit_func()
cls._objects = {}
这里依赖注入时有两个方法require和require_co,分别对应的非协程版本和协程版本,后者是专门为Tornado定制的,
destroy方法用来主动释放资源。
改进点
这个框架不是线程安全的,同时服务装饰器的settings参数在面对集成类时会出现异常