公司的管理后台是用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参数在面对集成类时会出现异常