springbootAI 1.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- spring/__init__.py +66 -0
- spring/ai/__init__.py +78 -0
- spring/ai/advisors.py +139 -0
- spring/ai/annotations.py +74 -0
- spring/ai/autoconfig.py +481 -0
- spring/ai/core.py +391 -0
- spring/ai/etl.py +188 -0
- spring/ai/memory.py +109 -0
- spring/ai/observability.py +129 -0
- spring/ai/providers.py +789 -0
- spring/ai/resilience.py +258 -0
- spring/ai/tools.py +106 -0
- spring/ai/vectorstore.py +303 -0
- spring/annotations/__init__.py +188 -0
- spring/annotations/cache.py +126 -0
- spring/annotations/cloud.py +207 -0
- spring/annotations/conditional.py +272 -0
- spring/annotations/core.py +864 -0
- spring/annotations/messaging.py +107 -0
- spring/aop/__init__.py +4 -0
- spring/aop/cloud_aop.py +404 -0
- spring/aop/comprehensive_aop.py +1015 -0
- spring/aop/method_interceptor.py +19 -0
- spring/aop/proxy_factory.py +55 -0
- spring/cloud/__init__.py +76 -0
- spring/cloud/discovery.py +364 -0
- spring/cloud/feign.py +469 -0
- spring/cloud/gateway.py +452 -0
- spring/cloud/load_balancer.py +149 -0
- spring/cloud/seata.py +557 -0
- spring/cloud/sentinel.py +525 -0
- spring/cloud/tracer.py +337 -0
- spring/config/__init__.py +21 -0
- spring/config/binding.py +206 -0
- spring/config/config_loader.py +405 -0
- spring/context/__init__.py +13 -0
- spring/context/application_context.py +589 -0
- spring/context/bean_definition.py +70 -0
- spring/context/bean_factory.py +1052 -0
- spring/context/registry.py +58 -0
- spring/context/scanner.py +106 -0
- spring/core/__init__.py +3 -0
- spring/core/graceful_shutdown.py +196 -0
- spring/core/typing_utils.py +50 -0
- spring/csv/__init__.py +52 -0
- spring/csv/annotations.py +402 -0
- spring/csv/converters.py +69 -0
- spring/csv/easy_csv.py +95 -0
- spring/csv/exceptions.py +27 -0
- spring/csv/reader.py +195 -0
- spring/csv/writer.py +155 -0
- spring/data/__init__.py +54 -0
- spring/data/page.py +181 -0
- spring/data/repository.py +274 -0
- spring/data/specification.py +228 -0
- spring/datasource/__init__.py +66 -0
- spring/datasource/annotations.py +133 -0
- spring/datasource/context.py +69 -0
- spring/datasource/dynamic.py +148 -0
- spring/event/__init__.py +7 -0
- spring/event/publisher.py +69 -0
- spring/excel/__init__.py +51 -0
- spring/excel/annotations.py +405 -0
- spring/excel/converters.py +231 -0
- spring/excel/easy_excel.py +94 -0
- spring/excel/exceptions.py +31 -0
- spring/excel/reader.py +254 -0
- spring/excel/style.py +95 -0
- spring/excel/writer.py +197 -0
- spring/i18n/__init__.py +97 -0
- spring/i18n/accessor.py +94 -0
- spring/i18n/auto_config.py +177 -0
- spring/i18n/holder.py +106 -0
- spring/i18n/locale.py +152 -0
- spring/i18n/locale_resolver.py +367 -0
- spring/i18n/message_source.py +250 -0
- spring/i18n/middleware.py +79 -0
- spring/i18n/properties.py +168 -0
- spring/i18n/sources.py +255 -0
- spring/logging/__init__.py +1 -0
- spring/logging/loguru_logger.py +228 -0
- spring/main.py +378 -0
- spring/messaging/__init__.py +1 -0
- spring/messaging/rabbitmq.py +302 -0
- spring/monitoring/__init__.py +1 -0
- spring/monitoring/prometheus.py +199 -0
- spring/orm/__init__.py +258 -0
- spring/orm/database.py +222 -0
- spring/orm/ddl_auto.py +1217 -0
- spring/orm/migration.py +419 -0
- spring/orm/mybatis_integration.py +400 -0
- spring/orm/pymybatis/__init__.py +86 -0
- spring/orm/pymybatis/annotations/__init__.py +30 -0
- spring/orm/pymybatis/annotations/annotations.py +332 -0
- spring/orm/pymybatis/cache/__init__.py +47 -0
- spring/orm/pymybatis/cache/cache.py +371 -0
- spring/orm/pymybatis/cache/redis_cache.py +434 -0
- spring/orm/pymybatis/circuit_breaker/__init__.py +21 -0
- spring/orm/pymybatis/circuit_breaker/circuit_breaker.py +424 -0
- spring/orm/pymybatis/configuration.py +525 -0
- spring/orm/pymybatis/core/__init__.py +10 -0
- spring/orm/pymybatis/core/sql_session.py +1382 -0
- spring/orm/pymybatis/core/sql_session_factory.py +76 -0
- spring/orm/pymybatis/dialect/__init__.py +9 -0
- spring/orm/pymybatis/dialect/dialect.py +445 -0
- spring/orm/pymybatis/dynamic_sql/__init__.py +9 -0
- spring/orm/pymybatis/dynamic_sql/dynamic_sql.py +900 -0
- spring/orm/pymybatis/interceptor/__init__.py +31 -0
- spring/orm/pymybatis/interceptor/interceptor.py +427 -0
- spring/orm/pymybatis/mapper/__init__.py +9 -0
- spring/orm/pymybatis/mapper/mapper.py +540 -0
- spring/orm/pymybatis/metrics/__init__.py +41 -0
- spring/orm/pymybatis/metrics/metrics.py +595 -0
- spring/orm/pymybatis/pool/__init__.py +9 -0
- spring/orm/pymybatis/pool/connection_pool.py +711 -0
- spring/orm/pymybatis/security/__init__.py +19 -0
- spring/orm/pymybatis/security/access_control.py +415 -0
- spring/orm/pymybatis/security/password_encoder.py +293 -0
- spring/orm/pymybatis/security/sensitive_data_masker.py +326 -0
- spring/orm/pymybatis/security/sql_injection_detector.py +675 -0
- spring/orm/pymybatis/transaction/__init__.py +9 -0
- spring/orm/pymybatis/transaction/transaction.py +288 -0
- spring/orm/pymybatis/type_handler/__init__.py +37 -0
- spring/orm/pymybatis/type_handler/type_handler.py +473 -0
- spring/orm/pymybatis/version.py +9 -0
- spring/orm/pymybatis/xml_parser/__init__.py +9 -0
- spring/orm/pymybatis/xml_parser/xml_parser.py +761 -0
- spring/retry/__init__.py +12 -0
- spring/retry/retry_annotations.py +71 -0
- spring/retry/retry_decorator.py +155 -0
- spring/scheduling/__init__.py +3 -0
- spring/scheduling/scheduler.py +389 -0
- spring/security/__init__.py +39 -0
- spring/security/jwt_utils.py +281 -0
- spring/security/replay_protection.py +206 -0
- spring/security/secret_manager.py +226 -0
- spring/security/security_aop.py +248 -0
- spring/security/security_context.py +172 -0
- spring/test/__init__.py +45 -0
- spring/test/slicing.py +341 -0
- spring/tracing/__init__.py +11 -0
- spring/tracing/skywalking.py +229 -0
- spring/tx/__init__.py +52 -0
- spring/tx/events.py +172 -0
- spring/tx/synchronization.py +143 -0
- spring/utils/__init__.py +5 -0
- spring/utils/banner.py +32 -0
- spring/utils/logger.py +73 -0
- spring/utils/redis_client.py +526 -0
- spring/validation/__init__.py +55 -0
- spring/validation/aop.py +141 -0
- spring/validation/constraints.py +357 -0
- spring/validation/exceptions.py +55 -0
- spring/validation/validator.py +139 -0
- spring/web/__init__.py +12 -0
- spring/web/actuator.py +319 -0
- spring/web/exception_handler.py +61 -0
- spring/web/health.py +399 -0
- spring/web/interceptor.py +91 -0
- spring/web/result.py +44 -0
- spring/web/swagger.py +601 -0
- spring/web/web_context.py +755 -0
- spring/websocket/__init__.py +86 -0
- spring/websocket/annotations.py +169 -0
- spring/websocket/broker.py +238 -0
- spring/websocket/exceptions.py +26 -0
- spring/websocket/handler.py +243 -0
- spring/websocket/router.py +526 -0
- spring/websocket/session.py +216 -0
- springbootai-1.8.0.dist-info/METADATA +2796 -0
- springbootai-1.8.0.dist-info/RECORD +175 -0
- springbootai-1.8.0.dist-info/WHEEL +5 -0
- springbootai-1.8.0.dist-info/entry_points.txt +2 -0
- springbootai-1.8.0.dist-info/licenses/LICENSE +7 -0
- springbootai-1.8.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1052 @@
|
|
|
1
|
+
from typing import Type, Optional, Any, Dict, Callable, List, get_args, get_origin, Union
|
|
2
|
+
from spring.context.bean_definition import BeanDefinition
|
|
3
|
+
from spring.annotations.core import Autowired, Qualifier, Slf4j, PostConstruct, PreDestroy, Primary, Transactional, Cacheable, Retryable, Async, Value
|
|
4
|
+
from spring.annotations.cache import CachePut, CacheEvict, CacheConfig, Caching
|
|
5
|
+
from spring.aop.proxy_factory import ProxyFactory
|
|
6
|
+
import inspect
|
|
7
|
+
import time
|
|
8
|
+
import asyncio
|
|
9
|
+
import functools
|
|
10
|
+
import threading
|
|
11
|
+
import hashlib
|
|
12
|
+
import pickle
|
|
13
|
+
import concurrent.futures
|
|
14
|
+
import types
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# 全局线程池,用于@Async注解的异步执行
|
|
18
|
+
_ASYNC_EXECUTOR = concurrent.futures.ThreadPoolExecutor(
|
|
19
|
+
max_workers=8,
|
|
20
|
+
thread_name_prefix="SpringAsync-"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _get_tx_sync_manager():
|
|
25
|
+
"""最佳努力获取事务同步管理器;``spring.tx`` 不可用时返回 ``None``。
|
|
26
|
+
|
|
27
|
+
``@Transactional`` 切面在事务边界触发 ``@TransactionalEventListener`` 回调;
|
|
28
|
+
若 ``spring.tx`` 未安装则回退到原始事务行为,保持向后兼容。
|
|
29
|
+
"""
|
|
30
|
+
try:
|
|
31
|
+
from spring.tx.synchronization import TransactionSynchronizationManager
|
|
32
|
+
return TransactionSynchronizationManager
|
|
33
|
+
except ImportError: # pragma: no cover - spring.tx 为内置模块,仅在异常拆分时缺失
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class BeanFactory:
|
|
38
|
+
def __init__(self, config_loader=None):
|
|
39
|
+
self._bean_definitions: Dict[str, BeanDefinition] = {}
|
|
40
|
+
self._bean_instances: Dict[str, Any] = {}
|
|
41
|
+
self._type_to_name: Dict[Type, str] = {}
|
|
42
|
+
# 使用线程本地存储,确保每个线程有独立的状态
|
|
43
|
+
self._thread_local = threading.local()
|
|
44
|
+
self._proxy_factory = ProxyFactory()
|
|
45
|
+
# 缓存支持大小限制和TTL
|
|
46
|
+
self._cache: Dict[str, Any] = {}
|
|
47
|
+
self._cache_metadata: Dict[str, dict] = {} # 存储缓存项的元数据(创建时间、过期时间)
|
|
48
|
+
self._cache_max_size = 1000 # 默认最大缓存数
|
|
49
|
+
self._cache_default_ttl = 300 # 默认TTL(秒)
|
|
50
|
+
self._lock = threading.RLock()
|
|
51
|
+
self._config_loader = config_loader
|
|
52
|
+
|
|
53
|
+
def _get_initializing(self) -> set:
|
|
54
|
+
"""获取当前线程的 initializing 集合"""
|
|
55
|
+
if not hasattr(self._thread_local, 'initializing'):
|
|
56
|
+
self._thread_local.initializing = set()
|
|
57
|
+
return self._thread_local.initializing
|
|
58
|
+
|
|
59
|
+
def _get_transaction_stack(self) -> List[Dict[str, Any]]:
|
|
60
|
+
"""获取当前线程的 transaction_stack"""
|
|
61
|
+
if not hasattr(self._thread_local, 'transaction_stack'):
|
|
62
|
+
self._thread_local.transaction_stack = []
|
|
63
|
+
return self._thread_local.transaction_stack
|
|
64
|
+
|
|
65
|
+
def set_config_loader(self, config_loader):
|
|
66
|
+
self._config_loader = config_loader
|
|
67
|
+
|
|
68
|
+
def register_bean_definition(self, bean_name: str, definition: BeanDefinition) -> None:
|
|
69
|
+
self._bean_definitions[bean_name] = definition
|
|
70
|
+
if definition.bean_class not in self._type_to_name:
|
|
71
|
+
self._type_to_name[definition.bean_class] = bean_name
|
|
72
|
+
|
|
73
|
+
def register_instance(self, bean_name: str, instance: Any) -> None:
|
|
74
|
+
"""
|
|
75
|
+
直接注册一个已实例化的Bean
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
bean_name: Bean名称
|
|
79
|
+
instance: Bean实例
|
|
80
|
+
"""
|
|
81
|
+
self._bean_instances[bean_name] = instance
|
|
82
|
+
bean_class = instance.__class__
|
|
83
|
+
if bean_class not in self._type_to_name:
|
|
84
|
+
self._type_to_name[bean_class] = bean_name
|
|
85
|
+
|
|
86
|
+
def get_bean_definition(self, bean_name: str) -> Optional[BeanDefinition]:
|
|
87
|
+
return self._bean_definitions.get(bean_name)
|
|
88
|
+
|
|
89
|
+
def get_bean(self, bean_name: str) -> Any:
|
|
90
|
+
definition = self.get_bean_definition(bean_name)
|
|
91
|
+
if not definition:
|
|
92
|
+
raise ValueError(f"No bean named '{bean_name}' found")
|
|
93
|
+
|
|
94
|
+
if definition.is_singleton:
|
|
95
|
+
if bean_name not in self._bean_instances:
|
|
96
|
+
with self._lock:
|
|
97
|
+
if bean_name not in self._bean_instances:
|
|
98
|
+
self._bean_instances[bean_name] = self._create_bean(definition)
|
|
99
|
+
return self._bean_instances[bean_name]
|
|
100
|
+
else:
|
|
101
|
+
return self._create_bean(definition)
|
|
102
|
+
|
|
103
|
+
def get_bean_by_type(self, bean_type: Type) -> Any:
|
|
104
|
+
if not isinstance(bean_type, type):
|
|
105
|
+
# ``Optional[T]`` and ``Annotated[T, ...]`` are typing objects,
|
|
106
|
+
# not valid arguments to issubclass().
|
|
107
|
+
bean_type, _, _ = self._unwrap_dependency_annotation(bean_type)
|
|
108
|
+
if not isinstance(bean_type, type):
|
|
109
|
+
raise ValueError(f"Bean 类型不可解析: {bean_type!r}")
|
|
110
|
+
if bean_type in self._type_to_name:
|
|
111
|
+
return self.get_bean(self._type_to_name[bean_type])
|
|
112
|
+
|
|
113
|
+
matching_definitions = []
|
|
114
|
+
for name, definition in self._bean_definitions.items():
|
|
115
|
+
if isinstance(definition.bean_class, type) and issubclass(definition.bean_class, bean_type):
|
|
116
|
+
matching_definitions.append((name, definition))
|
|
117
|
+
|
|
118
|
+
if not matching_definitions:
|
|
119
|
+
raise ValueError(f"No bean of type '{bean_type.__name__}' found")
|
|
120
|
+
|
|
121
|
+
if len(matching_definitions) == 1:
|
|
122
|
+
return self.get_bean(matching_definitions[0][0])
|
|
123
|
+
|
|
124
|
+
primary_definitions = [md for md in matching_definitions
|
|
125
|
+
if Primary._annotation_type in md[1].annotations]
|
|
126
|
+
|
|
127
|
+
if primary_definitions:
|
|
128
|
+
return self.get_bean(primary_definitions[0][0])
|
|
129
|
+
|
|
130
|
+
raise ValueError(f"Multiple beans of type '{bean_type.__name__}' found, use @Qualifier to specify")
|
|
131
|
+
|
|
132
|
+
def _create_bean(self, definition: BeanDefinition) -> Any:
|
|
133
|
+
initializing = self._get_initializing()
|
|
134
|
+
if definition.bean_name in initializing:
|
|
135
|
+
raise RuntimeError(f"Circular dependency detected for bean: {definition.bean_name}")
|
|
136
|
+
|
|
137
|
+
initializing.add(definition.bean_name)
|
|
138
|
+
|
|
139
|
+
try:
|
|
140
|
+
if definition.factory_method:
|
|
141
|
+
factory_instance = None
|
|
142
|
+
if definition.factory_class:
|
|
143
|
+
factory_instance = self.get_bean_by_type(definition.factory_class)
|
|
144
|
+
# 如果有factory_class,传递factory_instance;否则直接调用
|
|
145
|
+
if definition.factory_class:
|
|
146
|
+
instance = definition.factory_method(factory_instance)
|
|
147
|
+
else:
|
|
148
|
+
instance = definition.factory_method()
|
|
149
|
+
else:
|
|
150
|
+
instance = self._instantiate_bean(definition)
|
|
151
|
+
|
|
152
|
+
self._apply_aop_proxy(instance, definition)
|
|
153
|
+
self._populate_bean(definition, instance)
|
|
154
|
+
self._populate_config_values(definition, instance)
|
|
155
|
+
self._process_slf4j(instance, definition)
|
|
156
|
+
self._initialize_bean(definition, instance)
|
|
157
|
+
|
|
158
|
+
return instance
|
|
159
|
+
finally:
|
|
160
|
+
initializing.discard(definition.bean_name)
|
|
161
|
+
|
|
162
|
+
def _populate_config_values(self, definition: BeanDefinition, instance: Any) -> None:
|
|
163
|
+
if self._config_loader is None:
|
|
164
|
+
return
|
|
165
|
+
|
|
166
|
+
for name, value in vars(instance.__class__).items():
|
|
167
|
+
if isinstance(value, Value):
|
|
168
|
+
setattr(instance, name, self._resolve_value(value))
|
|
169
|
+
|
|
170
|
+
# Keep NacosValue optional: the framework can bind a local
|
|
171
|
+
# configuration snapshot even when the Nacos SDK is not installed.
|
|
172
|
+
try:
|
|
173
|
+
from spring.annotations.cloud import NacosValue
|
|
174
|
+
except ImportError:
|
|
175
|
+
NacosValue = ()
|
|
176
|
+
if NacosValue and isinstance(value, NacosValue):
|
|
177
|
+
setattr(instance, name, self._resolve_value(value))
|
|
178
|
+
|
|
179
|
+
property_annotations = definition.annotations.get('properties', [])
|
|
180
|
+
if property_annotations:
|
|
181
|
+
config = self._config_loader.get_prefix_config(
|
|
182
|
+
property_annotations[0].prefix
|
|
183
|
+
)
|
|
184
|
+
for key, value in config.items():
|
|
185
|
+
attribute = key.replace('-', '_')
|
|
186
|
+
if hasattr(instance, attribute):
|
|
187
|
+
setattr(instance, attribute, value)
|
|
188
|
+
|
|
189
|
+
def _resolve_value(self, annotation: Value) -> Any:
|
|
190
|
+
return self._config_loader.resolve_value_expression(
|
|
191
|
+
annotation.value,
|
|
192
|
+
getattr(annotation, 'default', None),
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
def _instantiate_bean(self, definition: BeanDefinition) -> Any:
|
|
196
|
+
constructor = self._find_constructor_with_autowire(definition.bean_class)
|
|
197
|
+
if constructor:
|
|
198
|
+
args = self._resolve_constructor_args(constructor, definition)
|
|
199
|
+
return definition.bean_class(*args)
|
|
200
|
+
return definition.bean_class()
|
|
201
|
+
|
|
202
|
+
def _find_constructor_with_autowire(self, bean_class: Type) -> Optional[Callable]:
|
|
203
|
+
for name, method in inspect.getmembers(bean_class):
|
|
204
|
+
if name == "__init__":
|
|
205
|
+
annotations = getattr(method, '__spring_annotations__', [])
|
|
206
|
+
for annotation in annotations:
|
|
207
|
+
if isinstance(annotation, Autowired):
|
|
208
|
+
return method
|
|
209
|
+
return None
|
|
210
|
+
|
|
211
|
+
def _resolve_constructor_args(self, constructor: Callable, definition: BeanDefinition) -> list:
|
|
212
|
+
sig = inspect.signature(constructor)
|
|
213
|
+
args = []
|
|
214
|
+
autowired = next(
|
|
215
|
+
(
|
|
216
|
+
annotation for annotation in getattr(constructor, '__spring_annotations__', [])
|
|
217
|
+
if isinstance(annotation, Autowired)
|
|
218
|
+
),
|
|
219
|
+
Autowired(),
|
|
220
|
+
)
|
|
221
|
+
for param_name, param in sig.parameters.items():
|
|
222
|
+
if param_name == 'self':
|
|
223
|
+
continue
|
|
224
|
+
|
|
225
|
+
# 检查参数默认值是否是@Value注解
|
|
226
|
+
if isinstance(param.default, Value):
|
|
227
|
+
args.append(self._resolve_value(param.default))
|
|
228
|
+
continue
|
|
229
|
+
|
|
230
|
+
try:
|
|
231
|
+
from spring.annotations.cloud import NacosValue
|
|
232
|
+
except ImportError:
|
|
233
|
+
NacosValue = ()
|
|
234
|
+
if NacosValue and isinstance(param.default, NacosValue):
|
|
235
|
+
args.append(self._resolve_value(param.default))
|
|
236
|
+
continue
|
|
237
|
+
|
|
238
|
+
param_type, inline_qualifier, optional_type = self._unwrap_dependency_annotation(param.annotation)
|
|
239
|
+
if param_type is inspect.Parameter.empty:
|
|
240
|
+
if param.default is not inspect.Parameter.empty:
|
|
241
|
+
args.append(param.default)
|
|
242
|
+
continue
|
|
243
|
+
# 尝试通过参数名查找 Bean
|
|
244
|
+
try:
|
|
245
|
+
args.append(self.get_bean(param_name))
|
|
246
|
+
continue
|
|
247
|
+
except (KeyError, ValueError):
|
|
248
|
+
pass
|
|
249
|
+
if not autowired.required:
|
|
250
|
+
args.append(None)
|
|
251
|
+
continue
|
|
252
|
+
raise ValueError(f"Cannot resolve parameter '{param_name}' without type annotation")
|
|
253
|
+
|
|
254
|
+
qualifier = inline_qualifier or definition.qualifiers.get(param_name)
|
|
255
|
+
if qualifier:
|
|
256
|
+
try:
|
|
257
|
+
args.append(self.get_bean(qualifier))
|
|
258
|
+
except ValueError:
|
|
259
|
+
if not autowired.required or optional_type:
|
|
260
|
+
args.append(None)
|
|
261
|
+
else:
|
|
262
|
+
raise ValueError(f"Cannot resolve parameter '{param_name}'")
|
|
263
|
+
else:
|
|
264
|
+
try:
|
|
265
|
+
args.append(self.get_bean_by_type(param_type))
|
|
266
|
+
except ValueError:
|
|
267
|
+
# 如果通过类型找不到,尝试通过参数名查找
|
|
268
|
+
try:
|
|
269
|
+
args.append(self.get_bean(param_name))
|
|
270
|
+
except (KeyError, ValueError):
|
|
271
|
+
if not autowired.required or optional_type:
|
|
272
|
+
args.append(None)
|
|
273
|
+
continue
|
|
274
|
+
raise ValueError(f"Cannot resolve parameter '{param_name}'")
|
|
275
|
+
return args
|
|
276
|
+
|
|
277
|
+
@staticmethod
|
|
278
|
+
def _unwrap_dependency_annotation(annotation: Any):
|
|
279
|
+
"""Extract ``Annotated[T, Qualifier(...)]`` and ``Optional[T]`` metadata."""
|
|
280
|
+
qualifier = None
|
|
281
|
+
optional_type = False
|
|
282
|
+
if get_origin(annotation) is not None and str(get_origin(annotation)).endswith('Annotated'):
|
|
283
|
+
args = get_args(annotation)
|
|
284
|
+
annotation = args[0]
|
|
285
|
+
for metadata in args[1:]:
|
|
286
|
+
if isinstance(metadata, Qualifier):
|
|
287
|
+
qualifier = metadata.value
|
|
288
|
+
origin = get_origin(annotation)
|
|
289
|
+
if origin is Union or str(origin) == 'types.UnionType':
|
|
290
|
+
candidates = [candidate for candidate in get_args(annotation) if candidate is not type(None)]
|
|
291
|
+
optional_type = len(candidates) != len(get_args(annotation))
|
|
292
|
+
if len(candidates) == 1:
|
|
293
|
+
annotation = candidates[0]
|
|
294
|
+
return annotation, qualifier, optional_type
|
|
295
|
+
|
|
296
|
+
def _populate_bean(self, definition: BeanDefinition, instance: Any) -> None:
|
|
297
|
+
for field_name, field_type in definition.dependencies.items():
|
|
298
|
+
qualifier = definition.qualifiers.get(field_name)
|
|
299
|
+
if qualifier:
|
|
300
|
+
dependency = self.get_bean(qualifier)
|
|
301
|
+
else:
|
|
302
|
+
try:
|
|
303
|
+
dependency = self.get_bean_by_type(field_type)
|
|
304
|
+
except ValueError:
|
|
305
|
+
# 如果通过类型找不到,尝试通过字段名查找
|
|
306
|
+
try:
|
|
307
|
+
dependency = self.get_bean(field_name)
|
|
308
|
+
except (KeyError, ValueError):
|
|
309
|
+
if not definition.dependency_required.get(field_name, True):
|
|
310
|
+
setattr(instance, field_name, None)
|
|
311
|
+
continue
|
|
312
|
+
raise ValueError(f"Cannot resolve field '{field_name}'")
|
|
313
|
+
setattr(instance, field_name, dependency)
|
|
314
|
+
|
|
315
|
+
for name, field in inspect.getmembers(instance.__class__):
|
|
316
|
+
if not name.startswith('_') and hasattr(field, '__spring_annotations__'):
|
|
317
|
+
annotations = field.__spring_annotations__
|
|
318
|
+
for annotation in annotations:
|
|
319
|
+
if isinstance(annotation, Autowired):
|
|
320
|
+
field_type = inspect.get_annotations(instance.__class__).get(name)
|
|
321
|
+
if field_type:
|
|
322
|
+
qualifier_annotation = None
|
|
323
|
+
for ann in annotations:
|
|
324
|
+
if isinstance(ann, Qualifier):
|
|
325
|
+
qualifier_annotation = ann
|
|
326
|
+
break
|
|
327
|
+
if qualifier_annotation:
|
|
328
|
+
dependency = self.get_bean(qualifier_annotation.value)
|
|
329
|
+
else:
|
|
330
|
+
dependency = self.get_bean_by_type(field_type)
|
|
331
|
+
setattr(instance, name, dependency)
|
|
332
|
+
|
|
333
|
+
def _process_slf4j(self, instance: Any, definition: BeanDefinition) -> None:
|
|
334
|
+
if Slf4j._annotation_type in definition.annotations:
|
|
335
|
+
from spring.utils.logger import get_logger
|
|
336
|
+
annotations = definition.annotations[Slf4j._annotation_type]
|
|
337
|
+
if annotations:
|
|
338
|
+
annotation = annotations[0]
|
|
339
|
+
logger_name = annotation.logger_name or instance.__class__.__name__
|
|
340
|
+
logger = get_logger(logger_name)
|
|
341
|
+
setattr(instance, 'logger', logger)
|
|
342
|
+
|
|
343
|
+
def _initialize_bean(self, definition: BeanDefinition, instance: Any) -> None:
|
|
344
|
+
if definition.init_method and hasattr(instance, definition.init_method):
|
|
345
|
+
init_method = getattr(instance, definition.init_method)
|
|
346
|
+
if callable(init_method):
|
|
347
|
+
init_method()
|
|
348
|
+
|
|
349
|
+
if hasattr(instance, 'init') and callable(instance.init):
|
|
350
|
+
instance.init()
|
|
351
|
+
|
|
352
|
+
self._register_rabbit_listeners(instance)
|
|
353
|
+
self._invoke_post_construct(instance)
|
|
354
|
+
|
|
355
|
+
definition.mark_initialized()
|
|
356
|
+
|
|
357
|
+
def _register_rabbit_listeners(self, instance: Any) -> None:
|
|
358
|
+
"""Register annotated bound methods after all AOP wrappers are installed."""
|
|
359
|
+
if getattr(instance, '__rabbit_listeners_registered__', False):
|
|
360
|
+
return
|
|
361
|
+
if self._config_loader is not None:
|
|
362
|
+
if not self._config_loader.get_value('rabbitmq.enabled', False):
|
|
363
|
+
return
|
|
364
|
+
try:
|
|
365
|
+
from spring.messaging.rabbitmq import rabbitmq_client
|
|
366
|
+
except ImportError:
|
|
367
|
+
return
|
|
368
|
+
connection = rabbitmq_client._connection
|
|
369
|
+
if connection is None or getattr(connection, 'is_closed', False):
|
|
370
|
+
return
|
|
371
|
+
try:
|
|
372
|
+
from spring.annotations.messaging import RabbitListener, register_rabbit_listener
|
|
373
|
+
except ImportError:
|
|
374
|
+
return
|
|
375
|
+
|
|
376
|
+
registered = False
|
|
377
|
+
for name, method in inspect.getmembers(instance.__class__):
|
|
378
|
+
if name.startswith('_') or not inspect.isfunction(method):
|
|
379
|
+
continue
|
|
380
|
+
for annotation in getattr(method, '__spring_annotations__', []):
|
|
381
|
+
if isinstance(annotation, RabbitListener):
|
|
382
|
+
register_rabbit_listener(annotation, getattr(instance, name))
|
|
383
|
+
registered = True
|
|
384
|
+
if registered:
|
|
385
|
+
setattr(instance, '__rabbit_listeners_registered__', True)
|
|
386
|
+
|
|
387
|
+
def _invoke_post_construct(self, instance: Any) -> None:
|
|
388
|
+
for name, method in inspect.getmembers(instance.__class__):
|
|
389
|
+
if not name.startswith('_') and inspect.isfunction(method):
|
|
390
|
+
annotations = getattr(method, '__spring_annotations__', [])
|
|
391
|
+
for annotation in annotations:
|
|
392
|
+
if isinstance(annotation, PostConstruct):
|
|
393
|
+
method(instance)
|
|
394
|
+
|
|
395
|
+
def destroy_bean(self, bean_name: str) -> None:
|
|
396
|
+
definition = self.get_bean_definition(bean_name)
|
|
397
|
+
if not definition or definition._destroyed:
|
|
398
|
+
return
|
|
399
|
+
|
|
400
|
+
if bean_name in self._bean_instances:
|
|
401
|
+
instance = self._bean_instances[bean_name]
|
|
402
|
+
|
|
403
|
+
self._invoke_pre_destroy(instance)
|
|
404
|
+
|
|
405
|
+
if definition.destroy_method and hasattr(instance, definition.destroy_method):
|
|
406
|
+
destroy_method = getattr(instance, definition.destroy_method)
|
|
407
|
+
if callable(destroy_method):
|
|
408
|
+
destroy_method()
|
|
409
|
+
|
|
410
|
+
if hasattr(instance, 'destroy') and callable(instance.destroy):
|
|
411
|
+
instance.destroy()
|
|
412
|
+
|
|
413
|
+
definition.mark_destroyed()
|
|
414
|
+
del self._bean_instances[bean_name]
|
|
415
|
+
|
|
416
|
+
def _invoke_pre_destroy(self, instance: Any) -> None:
|
|
417
|
+
for name, method in inspect.getmembers(instance.__class__):
|
|
418
|
+
if not name.startswith('_') and inspect.isfunction(method):
|
|
419
|
+
annotations = getattr(method, '__spring_annotations__', [])
|
|
420
|
+
for annotation in annotations:
|
|
421
|
+
if isinstance(annotation, PreDestroy):
|
|
422
|
+
method(instance)
|
|
423
|
+
|
|
424
|
+
def destroy_all(self) -> None:
|
|
425
|
+
for bean_name in list(self._bean_instances.keys()):
|
|
426
|
+
self.destroy_bean(bean_name)
|
|
427
|
+
|
|
428
|
+
def contains_bean(self, bean_name: str) -> bool:
|
|
429
|
+
return bean_name in self._bean_definitions
|
|
430
|
+
|
|
431
|
+
def get_bean_names(self) -> list:
|
|
432
|
+
return list(self._bean_definitions.keys())
|
|
433
|
+
|
|
434
|
+
def get_bean_count(self) -> int:
|
|
435
|
+
return len(self._bean_definitions)
|
|
436
|
+
|
|
437
|
+
def _apply_aop_proxy(self, instance: Any, definition: BeanDefinition) -> None:
|
|
438
|
+
bean_class = instance.__class__
|
|
439
|
+
from spring.utils.logger import SpringLogger
|
|
440
|
+
logger = SpringLogger()
|
|
441
|
+
|
|
442
|
+
logger.info(f"Applying AOP proxy to bean: {definition.bean_name}, instance: {id(instance)}")
|
|
443
|
+
|
|
444
|
+
for name, method in inspect.getmembers(bean_class):
|
|
445
|
+
if not name.startswith('_') and inspect.isfunction(method):
|
|
446
|
+
annotations = getattr(method, '__spring_annotations__', [])
|
|
447
|
+
|
|
448
|
+
if annotations:
|
|
449
|
+
logger.info(f" Found method {name} with annotations: {[type(a).__name__ for a in annotations]}")
|
|
450
|
+
|
|
451
|
+
# 处理新注解(使用 comprehensive_aop)
|
|
452
|
+
try:
|
|
453
|
+
from spring.aop.comprehensive_aop import apply_annotations
|
|
454
|
+
wrapped_method = apply_annotations(instance, method)
|
|
455
|
+
if wrapped_method is not method:
|
|
456
|
+
logger.info(f" Method {name} wrapped successfully")
|
|
457
|
+
method = wrapped_method
|
|
458
|
+
except ImportError as e:
|
|
459
|
+
logger.error(f" Failed to import comprehensive_aop: {e}")
|
|
460
|
+
|
|
461
|
+
# 处理 Cloud 注解(使用 cloud_aop)
|
|
462
|
+
try:
|
|
463
|
+
from spring.aop.cloud_aop import apply_cloud_annotations
|
|
464
|
+
wrapped_method = apply_cloud_annotations(instance, method)
|
|
465
|
+
if wrapped_method is not method:
|
|
466
|
+
logger.info(f" Method {name} wrapped with Cloud AOP successfully")
|
|
467
|
+
method = wrapped_method
|
|
468
|
+
except ImportError as e:
|
|
469
|
+
logger.error(f" Failed to import cloud_aop: {e}")
|
|
470
|
+
|
|
471
|
+
# 固定包装顺序:事务在计算内部,缓存命中可跳过事务,异步最外层调度。
|
|
472
|
+
for annotation in annotations:
|
|
473
|
+
if isinstance(annotation, Transactional):
|
|
474
|
+
method = self._wrap_transactional(instance, method, annotation)
|
|
475
|
+
for annotation in annotations:
|
|
476
|
+
if isinstance(annotation, Cacheable):
|
|
477
|
+
method = self._wrap_cacheable(instance, method, annotation)
|
|
478
|
+
# 缓存增强:@CachePut / @CacheEvict / @Caching(复用 @Cacheable 同一存储)
|
|
479
|
+
for annotation in annotations:
|
|
480
|
+
if isinstance(annotation, CachePut):
|
|
481
|
+
method = self._wrap_cache_put(instance, method, annotation)
|
|
482
|
+
for annotation in annotations:
|
|
483
|
+
if isinstance(annotation, CacheEvict):
|
|
484
|
+
method = self._wrap_cache_evict(instance, method, annotation)
|
|
485
|
+
for annotation in annotations:
|
|
486
|
+
if isinstance(annotation, Caching):
|
|
487
|
+
method = self._wrap_caching(instance, method, annotation)
|
|
488
|
+
for annotation in annotations:
|
|
489
|
+
if isinstance(annotation, Async):
|
|
490
|
+
method = self._wrap_async(instance, method, annotation)
|
|
491
|
+
|
|
492
|
+
# Security stays outermost: it consumes the internal request
|
|
493
|
+
# argument and authenticates before transaction/business logic.
|
|
494
|
+
try:
|
|
495
|
+
from spring.security.security_aop import apply_security_annotations
|
|
496
|
+
method = apply_security_annotations(instance, method)
|
|
497
|
+
except ImportError as e:
|
|
498
|
+
logger.error(f" Failed to import security_aop: {e}")
|
|
499
|
+
|
|
500
|
+
# 创建绑定方法并设置到实例
|
|
501
|
+
bound_method = types.MethodType(method, instance)
|
|
502
|
+
setattr(instance, name, bound_method)
|
|
503
|
+
logger.info(f" Method {name} bound to instance: {id(instance)}")
|
|
504
|
+
|
|
505
|
+
def _wrap_transactional(self, instance: Any, method: Callable, annotation: Transactional) -> Callable:
|
|
506
|
+
def get_session_factory():
|
|
507
|
+
try:
|
|
508
|
+
return self.get_bean('sqlSessionFactory')
|
|
509
|
+
except Exception as exc:
|
|
510
|
+
raise RuntimeError(
|
|
511
|
+
"@Transactional需要已启用的MyBatis SqlSessionFactory"
|
|
512
|
+
) from exc
|
|
513
|
+
|
|
514
|
+
def should_rollback(exc: Exception) -> bool:
|
|
515
|
+
if annotation.no_rollback_for and any(
|
|
516
|
+
isinstance(exc, exc_type) for exc_type in annotation.no_rollback_for
|
|
517
|
+
):
|
|
518
|
+
return False
|
|
519
|
+
if annotation.rollback_for and not any(
|
|
520
|
+
isinstance(exc, exc_type) for exc_type in annotation.rollback_for
|
|
521
|
+
):
|
|
522
|
+
return False
|
|
523
|
+
return True
|
|
524
|
+
|
|
525
|
+
if asyncio.iscoroutinefunction(method):
|
|
526
|
+
@functools.wraps(method)
|
|
527
|
+
async def async_wrapper(*args, **kwargs):
|
|
528
|
+
from spring.orm.mybatis_integration import mybatis_transaction
|
|
529
|
+
tx_sync = _get_tx_sync_manager()
|
|
530
|
+
|
|
531
|
+
owns_sync = (
|
|
532
|
+
tx_sync is not None
|
|
533
|
+
and not tx_sync.is_synchronization_active()
|
|
534
|
+
)
|
|
535
|
+
if owns_sync:
|
|
536
|
+
tx_sync.init_synchronization()
|
|
537
|
+
|
|
538
|
+
deferred_exception = None
|
|
539
|
+
deferred_traceback = None
|
|
540
|
+
committed = False
|
|
541
|
+
try:
|
|
542
|
+
with mybatis_transaction(
|
|
543
|
+
get_session_factory(), str(annotation.propagation).upper()
|
|
544
|
+
):
|
|
545
|
+
try:
|
|
546
|
+
result = await method(*args, **kwargs)
|
|
547
|
+
except Exception as exc:
|
|
548
|
+
if should_rollback(exc):
|
|
549
|
+
raise
|
|
550
|
+
deferred_exception = exc
|
|
551
|
+
deferred_traceback = exc.__traceback__
|
|
552
|
+
result = None
|
|
553
|
+
if owns_sync:
|
|
554
|
+
tx_sync.trigger_before_commit()
|
|
555
|
+
committed = True
|
|
556
|
+
if owns_sync:
|
|
557
|
+
tx_sync.trigger_after_commit()
|
|
558
|
+
if deferred_exception is not None:
|
|
559
|
+
raise deferred_exception.with_traceback(deferred_traceback)
|
|
560
|
+
return result
|
|
561
|
+
except Exception:
|
|
562
|
+
if owns_sync and not committed:
|
|
563
|
+
tx_sync.trigger_after_rollback()
|
|
564
|
+
raise
|
|
565
|
+
finally:
|
|
566
|
+
if owns_sync:
|
|
567
|
+
tx_sync.trigger_after_completion(
|
|
568
|
+
'commit' if committed else 'rollback'
|
|
569
|
+
)
|
|
570
|
+
tx_sync.clear_synchronization()
|
|
571
|
+
|
|
572
|
+
return async_wrapper
|
|
573
|
+
|
|
574
|
+
@functools.wraps(method)
|
|
575
|
+
def wrapper(*args, **kwargs):
|
|
576
|
+
from spring.orm.mybatis_integration import mybatis_transaction
|
|
577
|
+
tx_sync = _get_tx_sync_manager()
|
|
578
|
+
|
|
579
|
+
owns_sync = (
|
|
580
|
+
tx_sync is not None
|
|
581
|
+
and not tx_sync.is_synchronization_active()
|
|
582
|
+
)
|
|
583
|
+
if owns_sync:
|
|
584
|
+
tx_sync.init_synchronization()
|
|
585
|
+
|
|
586
|
+
deferred_exception = None
|
|
587
|
+
deferred_traceback = None
|
|
588
|
+
committed = False
|
|
589
|
+
try:
|
|
590
|
+
with mybatis_transaction(
|
|
591
|
+
get_session_factory(), str(annotation.propagation).upper()
|
|
592
|
+
):
|
|
593
|
+
try:
|
|
594
|
+
result = method(*args, **kwargs)
|
|
595
|
+
except Exception as exc:
|
|
596
|
+
if should_rollback(exc):
|
|
597
|
+
raise
|
|
598
|
+
deferred_exception = exc
|
|
599
|
+
deferred_traceback = exc.__traceback__
|
|
600
|
+
result = None
|
|
601
|
+
# 成功路径(含 no-rollback 异常):提交前触发 BEFORE_COMMIT
|
|
602
|
+
if owns_sync:
|
|
603
|
+
tx_sync.trigger_before_commit()
|
|
604
|
+
committed = True
|
|
605
|
+
if owns_sync:
|
|
606
|
+
tx_sync.trigger_after_commit()
|
|
607
|
+
if deferred_exception is not None:
|
|
608
|
+
raise deferred_exception.with_traceback(deferred_traceback)
|
|
609
|
+
return result
|
|
610
|
+
except Exception:
|
|
611
|
+
if owns_sync and not committed:
|
|
612
|
+
tx_sync.trigger_after_rollback()
|
|
613
|
+
raise
|
|
614
|
+
finally:
|
|
615
|
+
if owns_sync:
|
|
616
|
+
tx_sync.trigger_after_completion(
|
|
617
|
+
'commit' if committed else 'rollback'
|
|
618
|
+
)
|
|
619
|
+
tx_sync.clear_synchronization()
|
|
620
|
+
|
|
621
|
+
return wrapper
|
|
622
|
+
|
|
623
|
+
def _wrap_cacheable(self, instance: Any, method: Callable, annotation: Cacheable) -> Callable:
|
|
624
|
+
signature = inspect.signature(method)
|
|
625
|
+
|
|
626
|
+
def serialize_arg(arg: Any) -> str:
|
|
627
|
+
if isinstance(arg, (int, float, str, bool, type(None))):
|
|
628
|
+
return str(arg)
|
|
629
|
+
if isinstance(arg, (list, tuple)):
|
|
630
|
+
return '[' + ','.join(serialize_arg(item) for item in arg) + ']'
|
|
631
|
+
if isinstance(arg, dict):
|
|
632
|
+
items = sorted(
|
|
633
|
+
((serialize_arg(key), serialize_arg(value)) for key, value in arg.items()),
|
|
634
|
+
key=lambda item: item[0],
|
|
635
|
+
)
|
|
636
|
+
return '{' + ','.join(f"{key}:{value}" for key, value in items) + '}'
|
|
637
|
+
return f"obj_{id(arg)}"
|
|
638
|
+
|
|
639
|
+
def resolve_call(args, kwargs):
|
|
640
|
+
bound = signature.bind_partial(*args, **kwargs)
|
|
641
|
+
bound.apply_defaults()
|
|
642
|
+
cache_arguments = dict(bound.arguments)
|
|
643
|
+
cache_arguments.pop('self', None)
|
|
644
|
+
|
|
645
|
+
condition = annotation.condition
|
|
646
|
+
if condition:
|
|
647
|
+
if callable(condition):
|
|
648
|
+
enabled = bool(condition(**cache_arguments))
|
|
649
|
+
else:
|
|
650
|
+
condition_name = str(condition).strip()
|
|
651
|
+
negate = condition_name.startswith('!')
|
|
652
|
+
if negate:
|
|
653
|
+
condition_name = condition_name[1:]
|
|
654
|
+
if condition_name not in cache_arguments:
|
|
655
|
+
raise ValueError(
|
|
656
|
+
f"@Cacheable condition只支持参数名,未找到: {condition_name}"
|
|
657
|
+
)
|
|
658
|
+
enabled = bool(cache_arguments[condition_name])
|
|
659
|
+
if negate:
|
|
660
|
+
enabled = not enabled
|
|
661
|
+
if not enabled:
|
|
662
|
+
return False, None
|
|
663
|
+
|
|
664
|
+
if annotation.key:
|
|
665
|
+
if '{' in annotation.key:
|
|
666
|
+
try:
|
|
667
|
+
resolved_key = annotation.key.format(**cache_arguments)
|
|
668
|
+
except KeyError as exc:
|
|
669
|
+
raise ValueError(
|
|
670
|
+
f"@Cacheable key引用了不存在的参数: {exc.args[0]}"
|
|
671
|
+
) from exc
|
|
672
|
+
elif annotation.key in cache_arguments:
|
|
673
|
+
resolved_key = serialize_arg(cache_arguments[annotation.key])
|
|
674
|
+
else:
|
|
675
|
+
resolved_key = annotation.key
|
|
676
|
+
# key = cacheName + resolvedKey(对齐 Spring Cache:不含方法名,
|
|
677
|
+
# 使 @CachePut / @CacheEvict 可跨方法更新/失效 @Cacheable 条目)。
|
|
678
|
+
key_data = f"{annotation.value}:{resolved_key}"
|
|
679
|
+
else:
|
|
680
|
+
arguments = ','.join(
|
|
681
|
+
f"{name}:{serialize_arg(value)}"
|
|
682
|
+
for name, value in sorted(cache_arguments.items())
|
|
683
|
+
)
|
|
684
|
+
key_data = f"{annotation.value}:{arguments}"
|
|
685
|
+
return True, hashlib.sha256(key_data.encode('utf-8')).hexdigest()
|
|
686
|
+
|
|
687
|
+
def get_cached(cache_key):
|
|
688
|
+
with self._lock:
|
|
689
|
+
current_time = time.time()
|
|
690
|
+
metadata = self._cache_metadata.get(cache_key)
|
|
691
|
+
if metadata is None or cache_key not in self._cache:
|
|
692
|
+
return False, None
|
|
693
|
+
if current_time > metadata.get('expire_time', current_time):
|
|
694
|
+
self._cache.pop(cache_key, None)
|
|
695
|
+
self._cache_metadata.pop(cache_key, None)
|
|
696
|
+
return False, None
|
|
697
|
+
return True, self._cache[cache_key]
|
|
698
|
+
|
|
699
|
+
def store(cache_key, result):
|
|
700
|
+
current_time = time.time()
|
|
701
|
+
with self._lock:
|
|
702
|
+
if len(self._cache) >= self._cache_max_size:
|
|
703
|
+
oldest_key = min(
|
|
704
|
+
self._cache_metadata,
|
|
705
|
+
key=lambda key: self._cache_metadata[key].get('create_time', 0),
|
|
706
|
+
)
|
|
707
|
+
self._cache.pop(oldest_key, None)
|
|
708
|
+
self._cache_metadata.pop(oldest_key, None)
|
|
709
|
+
self._cache[cache_key] = result
|
|
710
|
+
self._cache_metadata[cache_key] = {
|
|
711
|
+
'create_time': current_time,
|
|
712
|
+
'expire_time': current_time + self._cache_default_ttl,
|
|
713
|
+
# 登记 namespace,供 @CacheEvict(all_entries=True) 按命名空间清空
|
|
714
|
+
'namespace': annotation.value,
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
if asyncio.iscoroutinefunction(method):
|
|
718
|
+
@functools.wraps(method)
|
|
719
|
+
async def async_wrapper(*args, **kwargs):
|
|
720
|
+
enabled, cache_key = resolve_call(args, kwargs)
|
|
721
|
+
if not enabled:
|
|
722
|
+
return await method(*args, **kwargs)
|
|
723
|
+
with self._lock:
|
|
724
|
+
exists = cache_key in self._cache_metadata
|
|
725
|
+
if exists:
|
|
726
|
+
hit, cached = get_cached(cache_key)
|
|
727
|
+
if hit:
|
|
728
|
+
return cached
|
|
729
|
+
result = await method(*args, **kwargs)
|
|
730
|
+
store(cache_key, result)
|
|
731
|
+
return result
|
|
732
|
+
|
|
733
|
+
return async_wrapper
|
|
734
|
+
|
|
735
|
+
@functools.wraps(method)
|
|
736
|
+
def wrapper(*args, **kwargs):
|
|
737
|
+
enabled, cache_key = resolve_call(args, kwargs)
|
|
738
|
+
if not enabled:
|
|
739
|
+
return method(*args, **kwargs)
|
|
740
|
+
with self._lock:
|
|
741
|
+
exists = cache_key in self._cache_metadata
|
|
742
|
+
if exists:
|
|
743
|
+
hit, cached = get_cached(cache_key)
|
|
744
|
+
if hit:
|
|
745
|
+
return cached
|
|
746
|
+
result = method(*args, **kwargs)
|
|
747
|
+
store(cache_key, result)
|
|
748
|
+
return result
|
|
749
|
+
|
|
750
|
+
return wrapper
|
|
751
|
+
|
|
752
|
+
# ==================== 缓存增强:@CachePut / @CacheEvict / @Caching ====================
|
|
753
|
+
# 复用 @Cacheable 同一存储(self._cache / self._cache_metadata / self._lock / TTL)。
|
|
754
|
+
# key 解析逻辑与 _wrap_cacheable 一致(参数名/{param}模板/全参数聚合 + condition),
|
|
755
|
+
# 为保证既有 @Cacheable 行为零回归,本组方法独立实现解析,未改动 _wrap_cacheable。
|
|
756
|
+
|
|
757
|
+
def _resolve_cache_value(self, instance: Any, annotation_value: str) -> str:
|
|
758
|
+
"""解析缓存命名空间:注解 value 为空时回退到类级 ``@CacheConfig`` 默认。"""
|
|
759
|
+
if annotation_value:
|
|
760
|
+
return annotation_value
|
|
761
|
+
# 读类级 @CacheConfig
|
|
762
|
+
from spring.annotations.core import get_spring_annotations
|
|
763
|
+
try:
|
|
764
|
+
for ann in get_spring_annotations(instance.__class__):
|
|
765
|
+
if isinstance(ann, CacheConfig) and ann.cache_names:
|
|
766
|
+
return ann.cache_names[0]
|
|
767
|
+
except Exception:
|
|
768
|
+
pass
|
|
769
|
+
return annotation_value
|
|
770
|
+
|
|
771
|
+
def _cache_serialize_arg(self, arg: Any) -> str:
|
|
772
|
+
"""缓存参数序列化(与 _wrap_cacheable.serialize_arg 一致)。"""
|
|
773
|
+
if isinstance(arg, (int, float, str, bool, type(None))):
|
|
774
|
+
return str(arg)
|
|
775
|
+
if isinstance(arg, (list, tuple)):
|
|
776
|
+
return '[' + ','.join(self._cache_serialize_arg(item) for item in arg) + ']'
|
|
777
|
+
if isinstance(arg, dict):
|
|
778
|
+
items = sorted(
|
|
779
|
+
((self._cache_serialize_arg(k), self._cache_serialize_arg(v)) for k, v in arg.items()),
|
|
780
|
+
key=lambda item: item[0],
|
|
781
|
+
)
|
|
782
|
+
return '{' + ','.join(f"{k}:{v}" for k, v in items) + '}'
|
|
783
|
+
return f"obj_{id(arg)}"
|
|
784
|
+
|
|
785
|
+
def _cache_resolve_call(self, method: Callable, annotation: Any, instance: Any, args, kwargs):
|
|
786
|
+
"""解析一次缓存操作的 (enabled, cache_key, namespace)。返回 (False, None, None) 表示跳过。
|
|
787
|
+
|
|
788
|
+
与 _wrap_cacheable.resolve_call 语义一致:condition 支持 参数名 / ``!参数名`` / callable。
|
|
789
|
+
额外返回 namespace(解析后的 value),供 _cache_store 登记,便于 @CacheEvict(all_entries) 清空。
|
|
790
|
+
"""
|
|
791
|
+
signature = inspect.signature(method)
|
|
792
|
+
bound = signature.bind_partial(*args, **kwargs)
|
|
793
|
+
bound.apply_defaults()
|
|
794
|
+
cache_arguments = dict(bound.arguments)
|
|
795
|
+
cache_arguments.pop('self', None)
|
|
796
|
+
|
|
797
|
+
condition = getattr(annotation, 'condition', None)
|
|
798
|
+
if condition:
|
|
799
|
+
if callable(condition):
|
|
800
|
+
enabled = bool(condition(**cache_arguments))
|
|
801
|
+
else:
|
|
802
|
+
condition_name = str(condition).strip()
|
|
803
|
+
negate = condition_name.startswith('!')
|
|
804
|
+
if negate:
|
|
805
|
+
condition_name = condition_name[1:]
|
|
806
|
+
if condition_name not in cache_arguments:
|
|
807
|
+
raise ValueError(
|
|
808
|
+
f"缓存 condition 只支持参数名,未找到: {condition_name}"
|
|
809
|
+
)
|
|
810
|
+
enabled = bool(cache_arguments[condition_name])
|
|
811
|
+
if negate:
|
|
812
|
+
enabled = not enabled
|
|
813
|
+
if not enabled:
|
|
814
|
+
return False, None, None
|
|
815
|
+
|
|
816
|
+
value = self._resolve_cache_value(instance, getattr(annotation, 'value', '') or '')
|
|
817
|
+
key_expr = getattr(annotation, 'key', None)
|
|
818
|
+
if key_expr:
|
|
819
|
+
if '{' in key_expr:
|
|
820
|
+
try:
|
|
821
|
+
resolved_key = key_expr.format(**cache_arguments)
|
|
822
|
+
except KeyError as exc:
|
|
823
|
+
raise ValueError(
|
|
824
|
+
f"缓存 key 引用了不存在的参数: {exc.args[0]}"
|
|
825
|
+
) from exc
|
|
826
|
+
elif key_expr in cache_arguments:
|
|
827
|
+
resolved_key = self._cache_serialize_arg(cache_arguments[key_expr])
|
|
828
|
+
else:
|
|
829
|
+
resolved_key = key_expr
|
|
830
|
+
# key = namespace + resolvedKey(与 _wrap_cacheable 一致,不含方法名,
|
|
831
|
+
# 使 @CachePut / @CacheEvict 与 @Cacheable 跨方法共享同一缓存条目)。
|
|
832
|
+
key_data = f"{value}:{resolved_key}"
|
|
833
|
+
else:
|
|
834
|
+
arguments = ','.join(
|
|
835
|
+
f"{name}:{self._cache_serialize_arg(val)}"
|
|
836
|
+
for name, val in sorted(cache_arguments.items())
|
|
837
|
+
)
|
|
838
|
+
key_data = f"{value}:{arguments}"
|
|
839
|
+
return True, hashlib.sha256(key_data.encode('utf-8')).hexdigest(), value
|
|
840
|
+
|
|
841
|
+
def _cache_get(self, cache_key: str):
|
|
842
|
+
"""读取缓存条目(过期则视为未命中并清理)。返回 (hit, value)。"""
|
|
843
|
+
with self._lock:
|
|
844
|
+
current_time = time.time()
|
|
845
|
+
metadata = self._cache_metadata.get(cache_key)
|
|
846
|
+
if metadata is None or cache_key not in self._cache:
|
|
847
|
+
return False, None
|
|
848
|
+
if current_time > metadata.get('expire_time', current_time):
|
|
849
|
+
self._cache.pop(cache_key, None)
|
|
850
|
+
self._cache_metadata.pop(cache_key, None)
|
|
851
|
+
return False, None
|
|
852
|
+
return True, self._cache[cache_key]
|
|
853
|
+
|
|
854
|
+
def _cache_store(self, cache_key: str, result: Any, namespace: str = "") -> None:
|
|
855
|
+
"""写入缓存条目(复用 @Cacheable 的容量淘汰与 TTL)。
|
|
856
|
+
|
|
857
|
+
``namespace`` 登记到 metadata,供 ``@CacheEvict(all_entries=True)`` 按命名空间清空。
|
|
858
|
+
"""
|
|
859
|
+
current_time = time.time()
|
|
860
|
+
with self._lock:
|
|
861
|
+
if len(self._cache) >= self._cache_max_size:
|
|
862
|
+
oldest_key = min(
|
|
863
|
+
self._cache_metadata,
|
|
864
|
+
key=lambda k: self._cache_metadata[k].get('create_time', 0),
|
|
865
|
+
)
|
|
866
|
+
self._cache.pop(oldest_key, None)
|
|
867
|
+
self._cache_metadata.pop(oldest_key, None)
|
|
868
|
+
self._cache[cache_key] = result
|
|
869
|
+
self._cache_metadata[cache_key] = {
|
|
870
|
+
'create_time': current_time,
|
|
871
|
+
'expire_time': current_time + self._cache_default_ttl,
|
|
872
|
+
'namespace': namespace,
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
def _cache_evict_key(self, cache_key: str) -> None:
|
|
876
|
+
with self._lock:
|
|
877
|
+
self._cache.pop(cache_key, None)
|
|
878
|
+
self._cache_metadata.pop(cache_key, None)
|
|
879
|
+
|
|
880
|
+
def _cache_evict_prefix(self, namespace: str) -> int:
|
|
881
|
+
"""清空整个缓存命名空间。
|
|
882
|
+
|
|
883
|
+
``cache_key`` 由 ``sha256(...)`` 生成,无法按前缀匹配;因此在 ``_cache_store`` 时把
|
|
884
|
+
``namespace`` 登记到 metadata,这里扫描 metadata 按 namespace 清空(含 @Cacheable 条目)。
|
|
885
|
+
"""
|
|
886
|
+
removed = 0
|
|
887
|
+
with self._lock:
|
|
888
|
+
victims = [k for k, meta in self._cache_metadata.items()
|
|
889
|
+
if meta.get('namespace') == namespace]
|
|
890
|
+
for k in victims:
|
|
891
|
+
self._cache.pop(k, None)
|
|
892
|
+
self._cache_metadata.pop(k, None)
|
|
893
|
+
removed += 1
|
|
894
|
+
return removed
|
|
895
|
+
|
|
896
|
+
def _wrap_cache_put(self, instance: Any, method: Callable, annotation: CachePut) -> Callable:
|
|
897
|
+
"""``@CachePut``:方法总是执行,把返回值写入缓存。"""
|
|
898
|
+
if asyncio.iscoroutinefunction(method):
|
|
899
|
+
@functools.wraps(method)
|
|
900
|
+
async def async_wrapper(*args, **kwargs):
|
|
901
|
+
enabled, cache_key, ns = self._cache_resolve_call(method, annotation, instance, args, kwargs)
|
|
902
|
+
result = await method(*args, **kwargs)
|
|
903
|
+
if enabled:
|
|
904
|
+
self._cache_store(cache_key, result, ns)
|
|
905
|
+
return result
|
|
906
|
+
return async_wrapper
|
|
907
|
+
|
|
908
|
+
@functools.wraps(method)
|
|
909
|
+
def wrapper(*args, **kwargs):
|
|
910
|
+
enabled, cache_key, ns = self._cache_resolve_call(method, annotation, instance, args, kwargs)
|
|
911
|
+
result = method(*args, **kwargs)
|
|
912
|
+
if enabled:
|
|
913
|
+
self._cache_store(cache_key, result, ns)
|
|
914
|
+
return result
|
|
915
|
+
return wrapper
|
|
916
|
+
|
|
917
|
+
def _wrap_cache_evict(self, instance: Any, method: Callable, annotation: CacheEvict) -> Callable:
|
|
918
|
+
"""``@CacheEvict``:失效缓存。
|
|
919
|
+
|
|
920
|
+
- ``before_invocation=True``:方法调用前失效(无论成功与否)。
|
|
921
|
+
- ``before_invocation=False``(默认):方法成功后失效(异常时不失效)。
|
|
922
|
+
- ``all_entries=True``:清空整个命名空间;否则按 key 失效。
|
|
923
|
+
"""
|
|
924
|
+
def _do_evict(args, kwargs):
|
|
925
|
+
if annotation.all_entries:
|
|
926
|
+
self._cache_evict_prefix(
|
|
927
|
+
self._resolve_cache_value(instance, annotation.value or ''))
|
|
928
|
+
return
|
|
929
|
+
enabled, cache_key, _ns = self._cache_resolve_call(method, annotation, instance, args, kwargs)
|
|
930
|
+
if enabled:
|
|
931
|
+
self._cache_evict_key(cache_key)
|
|
932
|
+
|
|
933
|
+
if asyncio.iscoroutinefunction(method):
|
|
934
|
+
@functools.wraps(method)
|
|
935
|
+
async def async_wrapper(*args, **kwargs):
|
|
936
|
+
if annotation.before_invocation:
|
|
937
|
+
_do_evict(args, kwargs)
|
|
938
|
+
result = await method(*args, **kwargs)
|
|
939
|
+
if not annotation.before_invocation:
|
|
940
|
+
_do_evict(args, kwargs)
|
|
941
|
+
return result
|
|
942
|
+
return async_wrapper
|
|
943
|
+
|
|
944
|
+
@functools.wraps(method)
|
|
945
|
+
def wrapper(*args, **kwargs):
|
|
946
|
+
if annotation.before_invocation:
|
|
947
|
+
_do_evict(args, kwargs)
|
|
948
|
+
result = method(*args, **kwargs)
|
|
949
|
+
if not annotation.before_invocation:
|
|
950
|
+
_do_evict(args, kwargs)
|
|
951
|
+
return result
|
|
952
|
+
return wrapper
|
|
953
|
+
|
|
954
|
+
def _wrap_caching(self, instance: Any, method: Callable, annotation: Caching) -> Callable:
|
|
955
|
+
"""``@Caching``:组合多个缓存操作,按 cacheable -> put -> evict 顺序叠加包装。
|
|
956
|
+
|
|
957
|
+
每个子操作调用对应的 wrap,逐层包装(与 Spring ``@Caching`` 应用多操作语义一致)。
|
|
958
|
+
"""
|
|
959
|
+
wrapped = method
|
|
960
|
+
for op in (annotation.cacheable or []):
|
|
961
|
+
wrapped = self._wrap_cacheable(instance, wrapped, op)
|
|
962
|
+
for op in (annotation.put or []):
|
|
963
|
+
wrapped = self._wrap_cache_put(instance, wrapped, op)
|
|
964
|
+
for op in (annotation.evict or []):
|
|
965
|
+
wrapped = self._wrap_cache_evict(instance, wrapped, op)
|
|
966
|
+
return wrapped
|
|
967
|
+
|
|
968
|
+
def _wrap_retryable(self, instance: Any, method: Callable, annotation: Retryable) -> Callable:
|
|
969
|
+
@functools.wraps(method)
|
|
970
|
+
def wrapper(*args, **kwargs):
|
|
971
|
+
last_exception = None
|
|
972
|
+
|
|
973
|
+
for attempt in range(annotation.max_attempts):
|
|
974
|
+
try:
|
|
975
|
+
return method(instance, *args, **kwargs)
|
|
976
|
+
except Exception as e:
|
|
977
|
+
last_exception = e
|
|
978
|
+
|
|
979
|
+
if annotation.value and not any(isinstance(e, exc_type) for exc_type in annotation.value):
|
|
980
|
+
raise
|
|
981
|
+
|
|
982
|
+
if attempt < annotation.max_attempts - 1:
|
|
983
|
+
time.sleep(annotation.backoff / 1000)
|
|
984
|
+
|
|
985
|
+
raise last_exception
|
|
986
|
+
|
|
987
|
+
return wrapper
|
|
988
|
+
|
|
989
|
+
def _wrap_async(self, instance: Any, method: Callable, annotation: Async) -> Callable:
|
|
990
|
+
@functools.wraps(method)
|
|
991
|
+
def wrapper(*args, **kwargs):
|
|
992
|
+
if asyncio.iscoroutinefunction(method):
|
|
993
|
+
try:
|
|
994
|
+
loop = asyncio.get_running_loop()
|
|
995
|
+
except RuntimeError:
|
|
996
|
+
return _ASYNC_EXECUTOR.submit(
|
|
997
|
+
asyncio.run, method(*args, **kwargs)
|
|
998
|
+
)
|
|
999
|
+
return loop.create_task(method(*args, **kwargs))
|
|
1000
|
+
|
|
1001
|
+
future = _ASYNC_EXECUTOR.submit(method, *args, **kwargs)
|
|
1002
|
+
try:
|
|
1003
|
+
asyncio.get_running_loop()
|
|
1004
|
+
except RuntimeError:
|
|
1005
|
+
return future
|
|
1006
|
+
return asyncio.wrap_future(future)
|
|
1007
|
+
|
|
1008
|
+
return wrapper
|
|
1009
|
+
|
|
1010
|
+
def get_transaction_stack(self) -> List[Dict[str, Any]]:
|
|
1011
|
+
return self._get_transaction_stack()
|
|
1012
|
+
|
|
1013
|
+
def get_cache(self) -> Dict[str, Any]:
|
|
1014
|
+
return self._cache
|
|
1015
|
+
|
|
1016
|
+
def clear_cache(self) -> None:
|
|
1017
|
+
self._cache.clear()
|
|
1018
|
+
self._cache_metadata.clear()
|
|
1019
|
+
|
|
1020
|
+
def refresh_configuration(self) -> List[str]:
|
|
1021
|
+
"""Rebind refresh-scoped and auto-refreshed Nacos values in live Beans.
|
|
1022
|
+
|
|
1023
|
+
Python keeps object identity stable during refresh so collaborators that
|
|
1024
|
+
already hold a Bean reference see the new values immediately. This is
|
|
1025
|
+
intentionally different from Spring Cloud's target-swapping proxy and
|
|
1026
|
+
avoids exposing stale Python references.
|
|
1027
|
+
"""
|
|
1028
|
+
refreshed = []
|
|
1029
|
+
try:
|
|
1030
|
+
from spring.annotations.cloud import NacosValue
|
|
1031
|
+
except ImportError:
|
|
1032
|
+
NacosValue = ()
|
|
1033
|
+
|
|
1034
|
+
for bean_name, instance in list(self._bean_instances.items()):
|
|
1035
|
+
definition = self._bean_definitions.get(bean_name)
|
|
1036
|
+
if definition is None:
|
|
1037
|
+
continue
|
|
1038
|
+
is_refresh_scope = bool(definition.annotations.get('refresh_scope'))
|
|
1039
|
+
dynamic_values = [
|
|
1040
|
+
value for value in vars(instance.__class__).values()
|
|
1041
|
+
if NacosValue and isinstance(value, NacosValue) and value.auto_refreshed
|
|
1042
|
+
]
|
|
1043
|
+
if not is_refresh_scope and not dynamic_values:
|
|
1044
|
+
continue
|
|
1045
|
+
self._populate_config_values(definition, instance)
|
|
1046
|
+
refreshed.append(bean_name)
|
|
1047
|
+
return refreshed
|
|
1048
|
+
|
|
1049
|
+
def set_cache_config(self, max_size: int = 1000, default_ttl: int = 300) -> None:
|
|
1050
|
+
"""设置缓存配置"""
|
|
1051
|
+
self._cache_max_size = max_size
|
|
1052
|
+
self._cache_default_ttl = default_ttl
|