from typing import Any
- https://github.com/seatools-py/cookiecutter-seatools-python
- https://gitee.com/seatools-py/cookiecutter-seatools-python
启动器列表地址具体见:https://gitee.com/seatools-py
联系方式, qq: 521274311 邮箱: 521274311@qq.com
pip install seatools --upgrade
Seatools 参考 Java Spring 独立设计一套相似 API 的轻量级 IOC 工具,对 Java Spring 熟悉的开发者能够快速上手 核心功能:
- IOC 容器管理
- IOC 配置管理
- starter 机制简化开发
- aop 切面 (实验中)
- 便捷的API(与Java Spring类似)
- 与Java Spring类似通过包模块扫描,将需要IOC创建管理容器的配置信息放入队列,在IOC初始化完成后,将队列中的容器配置全部取出依次生成容器
- Autowired, Value均通过代理对象+懒加载实现,在运行过程中需要用到容器时才从ioc中取出容器,从而避免循环依赖问题,其中自动注入依赖容器也通过Autowired的懒加载实现
- 由于Value是代理对象的原因,Python解释器在对基本数据类型做常见运算/操作,例如数值类型的
+
,-
,*
,/
,字符串的+
等,会对类型做校验从而导致异常, 针对基本数据类型, Value提供一套基本类型转换属性来规避该问题, 例如存在配置a: 1,使用Value时使用该方式获取int类型的数据类型来完成计算Value('${a}').int + 2
- 复杂依赖场景 (含循环依赖)
- 需要解耦灵活调整抽象实现场景,假设业务顶级包名为
xxx
, 以下为代码示例:
"""
xxx/service.py
"""
import abc
class XXXService(abc.ABC):
def do_anything(self):
raise NotImplementedError
"""
xxx/service_impl.py
"""
class XXXAService(XXXService):
def do_anything(self):
print("XXXA")
class XXXBService(XXXAService):
def do_anything(self):
print("XXXB")
"""
xxx/config.py
"""
from seatools.ioc import Bean
@Bean(primary=True)
def new_xxxa_service(): # 涉及依赖可写在方法参数里
return XXXAService()
@Bean(name='xxxb')
def new_xxxb_service():
return XXXBService()
"""
xxx/main.py
"""
from seatools.ioc import run, Autowired
# 启动ioc依赖
run(scan_package_names='xxx', config_dir='./config')
xxxa = Autowired(cls=XXXService) # 由于XXXService有XXXAService和XXXBService两个子类, 并且均定义了IOC容器实例, 当仅传递类型时, 会自动扫描获取primary=True的Bean返回
xxxa.do_anything() # 输出:XXXA
xxxb = Autowired('xxxb', cls=XXXService) # 通过名称+类型获取 XXXBService IOC 容器实例
xxxb.do_anything() # 输出:XXXB
"""
一些建议:
在需要解耦灵活调整抽象实现场景下,通常我们可直接通过Autowired(cls=抽象类型)来获取该类型的实例,而具体抽象类型实现的Bean我们可以在ioc配置的python文件中定义需要用到的子类型实例容器,从而实现在不修改逻辑的情况下动态修改实现
"""
seatools.ioc.boot.run
-function
: ioc 启动函数, 类似 Java Spring 的@SpringBootApplication
,需要声明扫描的包与配置目录, 目前默认读取配置目录的application.[yml|yaml|json|properties|xml|py]
,application-[dev|test|pro].[yml|yaml|json|properties|xml|py]
, 使用示例:
from seatools.ioc import run
run(scan_package_names='xxx', config_dir='config') # 其中scan_package_names参数支持字符串, 字符串列表, 按顺序扫描包
seatools.ioc.Bean
-decorator
: ioc 容器装饰器, 支持装饰[类型, 方法],类似 Java Spring 的@Bean
,使用示例如下:
from seatools.ioc import run, Bean
"""
@Bean: 为类A创建一个容器实例并由ioc管理
支持参数如下:
name: bean名称, 不填默认小写驼峰名称, 与java spring一致
primary: 是否为该类型的默认bean, 不填默认否false, 与java spring @Primary 效果一致
"""
@Bean
class A:
...
run(scan_package_names='xxx', config_dir='config')
seatools.ioc.Autowired
-class
: ioc 容器自动注入类型, 与 Java Spring 的@Autowired
类似,使用示例如下:
from seatools.ioc import run, Bean, Autowired
"""
@Bean: 为类A创建一个容器实例并由ioc管理
支持参数如下:
name: bean名称, 不填默认小写驼峰名称, 与java spring一致
primary: 是否为该类型的默认bean, 不填默认否false, 与java spring @Primary 效果一致
"""
@Bean
class A:
def hello():
print('this is A.')
run(scan_package_names='xxx', config_dir='config')
"""
Autowired: 自动注入容器
支持参数如下:
value: bean名称, 填入该值会基于名称获取
cls: bean类型, 填入该值会基于类型获取, 若同时填入value则会两个条件同时查找
required: 是否强制要求获取容器, 为True时无法获取容器则会抛出ValueError(后续改为其他异常,会异常ValueError),为False找不到容器时会默认返回None
"""
# 从IOC容器中获取A实例
a = Autowired(cls=A) # 基于容器类型获取
a2 = Autowired('a') # 基于容器名称获取
a.hello()
"""
输出: this is A.
"""
print(a == a2) # 返回 True
# 也支持方法创建Bean
@Bean(name='a2')
def a2():
return A()
seatools.ioc.Value
-class
: ioc 配置自动装载类型, 类似 Java Spring 的@Value
, 使用示例如下:
"""
config/application.yml 配置文件如下
a: 1
b: x
c:
a: 2
b: y
"""
from seatools.ioc import run, Value
run(scan_package_names='xxx', config_dir='config')
a = Value('${a}') # 属性需要使用${}包裹, 该处为获取a配置的值即1
# 需要注意:Value获取基本数据类型, 无法直接使用, 需要使用对应类型的属性才可使用, 示例如下:
print(a.int + 5)
"""
输出: 6
"""
b = Value('${b}')
print(b.str + 'aa')
"""
输出: xaa
"""
# Value也允许直接装配Model
from seatools.models import BaseModel
class C(BaseModel):
a: int
b: str
class Config(BaseModel):
a: int
b: str
c: C
# Value获取的model每次都是一个新的对象, 若获取的对象无需操作, 建议只获取一次即可
c = Value('${c}', cls=C)
print(c.a, c.b)
"""
输出: 2 y
"""
config = Value('${}', cls=Config)
print(config.a, config.b, config.c.a, config.c.b)
"""
输出 1 x 2 y
"""
seatools.ioc.ConfigurationPropertiesBean
-decorator
: ioc 的配置属性自动装配bean实例装饰器, 类似 Java Spring 的@ConfigurationProperties + @Bean 的组合, 使用示例如下:
"""
config/application.yml 配置文件如下
a: 1
b: x
c:
a: 2
b: y
"""
from typing import Optional
from seatools.ioc import run, ConfigurationPropertiesBean, Autowired
from seatools.models import BaseModel
@ConfigurationPropertiesBean(prop='c')
class C(BaseModel):
a: int
b: str
@ConfigurationPropertiesBean()
class Config(BaseModel):
a: int
b: str
c: C
run(scan_package_names='xxx', config_dir='config')
c = Autowired(cls=C)
print(c.a, c.b)
"""
输出: 2 y
"""
config = Autowired(cls=Config)
print(config.a, config.b, config.c.a, config.c.b)
"""
输出 1 x 2 y
"""
# 也支持方法创建Bean
class Config2(BaseModel):
a: Optional[int] = None
b: Optional[str] = None
c: Optional[C] = None
@ConfigurationPropertiesBean()
def config2():
return Config2()
seatools.ioc.beans.factory.InitializingBean
-abstract class
: 初始化bean后的后置方法, 可在bean初始化属性完成后执行额外操作, 与 Java SpringInitializingBean
类似, 使用示例:
from seatools.ioc import run, Autowired, Bean
from seatools.ioc.beans.factory import InitializingBean
@Bean
class A(InitializingBean):
def after_properties_set(self):
print('initializaing A')
def hello(self):
print('hello A')
run(scan_package_names='xxx', config_dir='config')
a = Autowired(cls=A)
a.hello()
"""
输出:
initializaing A
hello A
"""
- 依赖自动注入示例:
"""
config/application.yml 配置文件如下
a: 1
b: x
c:
a: 2
b: y
"""
from seatools.models import BaseModel
from seatools.ioc import run, Autowired, Bean, ConfigurationPropertiesBean
class C(BaseModel):
a: int
b: str
@ConfigurationPropertiesBean()
class Config(BaseModel):
a: int
b: str
c: C
@Bean
class XXXService:
def __init__(self, config: Config): # 必填参数会自动装配, 有默认值不会自动装配, 若想通过默认值自动装配, 可写为 def __init__(self, config = Autowired(cls=Config)):
self._config = config
def print(self):
print(self._config)
run(scan_package_names='xxx', config_dir='config')
xxx_service = Autowired(cls=XXXService)
xxx_service.print()
"""
输出: a=1 y='x' c=C(a=2, b='y')
"""
- 使用示例, 以
xxx.py
为例, 并且PYTHONPATH=.
(虽然支持AOP但仅建议在日志、监控等场景使用AOP, 若在业务中使用AOP将会导致逻辑不直观, 谨慎使用!)
from typing import Any
from seatools.ioc import run, Bean, Aspect, Autowired, AbstractAspect, JoinPoint
@Bean
class A:
def run(self, name: str):
print("run")
return f"A {name}"
@Aspect
class AAop(AbstractAspect):
pointcut = "execution(xxx.A.*)" # 其中xxx.A.* 表示xxx模块中的类A中的所有公共方法 (暂仅支持公共方法, 以下划线开始的方法均不支持),切点不能为Aspect实例
def before(self, point: JoinPoint, **kwargs) -> None:
# before
print("before")
def after(self, point: JoinPoint, **kwargs) -> None:
# after
print("after")
def around(self, point: JoinPoint, **kwargs) -> Any:
# do point method
print("around before")
ans = point.proceed()
print("around after")
return ans
def after_returning(self, point: JoinPoint, return_value: Any, **kwargs) -> None:
# success returning
print("after_returning")
def after_exception(self, point: JoinPoint, ex: Exception, **kwargs) -> None:
# failure exception
print("after_exception")
# 启动IOC
run(scan_package_names='xxx', config_dir='config',
# 使用aop启动需开启该参数
enable_aspect=True)
# 获取A类型容器
a = Autowired(cls=A)
print(a.run("啦啦啦"))
"""
输出:
before
around before
run
around after
after returning
after
"""