Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Создание собственного Scope для бинов в Spring
Отличный вопрос о Spring Container и управлении жизненным циклом бинов! Короткий ответ: ДА, полностью можно — Spring предоставляет механизм для создания custom scopes через интерфейс Scope. Это мощный инструмент для специальных сценариев.
Встроенные Scopes в Spring
Сначала напомню встроенные scopes:
- Singleton — один экземпляр на весь контейнер (по умолчанию)
- Prototype — новый экземпляр при каждом запросе
- Request — один экземпляр на HTTP запрос (web приложения)
- Session — один экземпляр на HTTP сессию
- Application — один экземпляр на ServletContext
- WebSocket — один экземпляр на WebSocket сессию
@Component
@Scope("singleton") // По умолчанию
public class UserService {}
@Component
@Scope("prototype") // Новый при каждом запросе
public class RequestHandler {}
@Component
@Scope("request") // Один на HTTP request
public class HttpRequestData {}
Создание собственного Scope
Шаг 1: Реализуем интерфейс Scope
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
public class TenantScope implements Scope {
private final Map<String, Object> beans = new ConcurrentHashMap<>();
// Возвращает объект из scope по имени
public Object get(String name, ObjectFactory<?> objectFactory) {
return beans.computeIfAbsent(name, k -> objectFactory.getObject());
}
// Удаляет объект из scope
public Object remove(String name) {
return beans.remove(name);
}
// Регистрирует callback для разрушения объекта
public void registerDestructionCallback(String name, Runnable callback) {
// Можно сохранить callback и вызвать при необходимости
}
// Возвращает идентификатор scope сессии
public Object getConversationId() {
return Thread.currentThread().getId();
}
// Очистка scope
public void clear() {
beans.clear();
}
}
Шаг 2: Регистрируем Scope в контейнере
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ScopeConfiguration {
@Bean
public static CustomScopeConfigurer customScopeConfigurer() {
CustomScopeConfigurer configurer = new CustomScopeConfigurer();
configurer.addScope("tenant", new TenantScope());
return configurer;
}
}
Шаг 3: Используем custom scope
@Component
@Scope("tenant") // Используем наш собственный scope
public class TenantData {
private String tenantId;
public String getTenantId() {
return tenantId;
}
public void setTenantId(String tenantId) {
this.tenantId = tenantId;
}
}
@Service
public class MultiTenantService {
private final TenantData tenantData;
public MultiTenantService(TenantData tenantData) {
this.tenantData = tenantData;
}
public void processTenant(String tenantId) {
tenantData.setTenantId(tenantId);
System.out.println("Processing for tenant: " + tenantData.getTenantId());
}
}
Пример: Thread-Local Scope
public class ThreadLocalScope implements Scope {
private final ThreadLocal<Map<String, Object>> threadLocal =
ThreadLocal.withInitial(HashMap::new);
public Object get(String name, ObjectFactory<?> objectFactory) {
Map<String, Object> scopedObjects = threadLocal.get();
return scopedObjects.computeIfAbsent(name, k -> objectFactory.getObject());
}
public Object remove(String name) {
Map<String, Object> scopedObjects = threadLocal.get();
return scopedObjects.remove(name);
}
public void registerDestructionCallback(String name, Runnable callback) {
// При необходимости вызываем callback
}
public Object getConversationId() {
return Thread.currentThread().getId();
}
public void clear() {
threadLocal.remove();
}
}
// Конфигурация
@Configuration
public class ThreadScopeConfig {
@Bean
public static CustomScopeConfigurer customScopeConfigurer() {
CustomScopeConfigurer configurer = new CustomScopeConfigurer();
configurer.addScope("threadLocal", new ThreadLocalScope());
return configurer;
}
}
// Использование
@Component
@Scope("threadLocal")
public class ThreadContext {
private String userId;
public String getUserId() { return userId; }
public void setUserId(String userId) { this.userId = userId; }
}
Пример: Request-Context Scope (более продвинутый)
public class RequestContextScope implements Scope {
private final Map<String, Object> beans = new ConcurrentHashMap<>();
private final Map<String, List<Runnable>> destructionCallbacks = new ConcurrentHashMap<>();
public Object get(String name, ObjectFactory<?> objectFactory) {
return beans.computeIfAbsent(name, k -> objectFactory.getObject());
}
public Object remove(String name) {
List<Runnable> callbacks = destructionCallbacks.remove(name);
if (callbacks != null) {
callbacks.forEach(Runnable::run);
}
return beans.remove(name);
}
public void registerDestructionCallback(String name, Runnable callback) {
destructionCallbacks.computeIfAbsent(name, k -> new ArrayList<>()).add(callback);
}
public Object getConversationId() {
return UUID.randomUUID();
}
// Разрушаем все бины scope
public void destroy() {
destructionCallbacks.values().forEach(callbacks ->
callbacks.forEach(Runnable::run)
);
beans.clear();
destructionCallbacks.clear();
}
}
Практический пример: Web-Context Scope
public class WebContextScope implements Scope {
private static final ThreadLocal<Map<String, Object>> contextHolder =
ThreadLocal.withInitial(HashMap::new);
public static void setContext(Map<String, Object> context) {
contextHolder.set(context);
}
public static void clearContext() {
contextHolder.remove();
}
public Object get(String name, ObjectFactory<?> objectFactory) {
Map<String, Object> context = contextHolder.get();
return context.computeIfAbsent(name, k -> objectFactory.getObject());
}
public Object remove(String name) {
return contextHolder.get().remove(name);
}
public void registerDestructionCallback(String name, Runnable callback) {}
public Object getConversationId() {
return Thread.currentThread().getId();
}
}
// Конфигурация
@Configuration
public class WebContextConfig {
@Bean
public static CustomScopeConfigurer customScopeConfigurer() {
CustomScopeConfigurer configurer = new CustomScopeConfigurer();
configurer.addScope("webContext", new WebContextScope());
return configurer;
}
}
// Использование в контроллере
@RestController
@RequestMapping("/api")
public class UserController {
@GetMapping("/user/{id}")
public ResponseEntity<UserDTO> getUser(@PathVariable String id) {
WebContextScope.setContext(new HashMap<>(Map.of(
"userId", id,
"timestamp", System.currentTimeMillis()
)));
try {
UserDTO user = userService.getUser(id);
return ResponseEntity.ok(user);
} finally {
WebContextScope.clearContext();
}
}
}
@Component
@Scope("webContext")
public class UserContext {
private String userId;
private long timestamp;
public String getUserId() { return userId; }
public void setUserId(String userId) { this.userId = userId; }
}
Когда создавать custom scope?
| Сценарий | Решение |
|---|---|
| HTTP запрос | @Scope("request") |
| HTTP сессия | @Scope("session") |
| Один на весь app | @Scope("singleton") |
| Новый каждый раз | @Scope("prototype") |
| Multi-tenant приложение | Custom Scope |
| Thread-specific данные | Custom Scope (ThreadLocal) |
| Время жизни = Context | Custom Scope |
Best Practices
// Всегда регистрируйте destruction callbacks
public void registerDestructionCallback(String name, Runnable callback) {
// Важно для очистки ресурсов
destructionCallbacks.put(name, callback);
}
// Используйте thread-safe коллекции
private final Map<String, Object> beans = new ConcurrentHashMap<>();
// Документируйте время жизни
@Component
@Scope("custom")
// Время жизни: завязано на requestId, разрушается в конце обработки
public class RequestScopedService {}
// Предпочитайте composition вместо наследования
public class CustomScope implements Scope {
// Лучше компонировать, чем расширять
}
Выводы
- Custom Scope мощный инструмент для специальных сценариев
- Реализуется через интерфейс Scope с методами get/remove
- Регистрируется через CustomScopeConfigurer
- Полезен для multi-tenancy, request context, thread context
- Требует аккуратности с управлением ресурсами и очисткой