Какие знаешь Scope помимо Singleton и Prototype?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Bean Scopes в Spring Framework
Помимо Singleton и Prototype, в Spring Framework существуют несколько других областей видимости (scopes) для управления жизненным циклом и доступностью бинов. Знание этих scopes критично для правильной архитектуры веб-приложений и многопоточных систем.
1. Request Scope
Request scope — бин создаётся и существует только в контексте одного HTTP-запроса.
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST,
proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestData {
private String userId;
private LocalDateTime requestTime;
public RequestData() {
this.requestTime = LocalDateTime.now();
System.out.println("RequestData создан для запроса");
}
}
@RestController
@RequestMapping("/api")
public class UserController {
private final RequestData requestData;
public UserController(RequestData requestData) {
this.requestData = requestData;
}
@GetMapping("/user")
public String getUser() {
return "Данные запроса: " + requestData.getRequestTime();
}
}
Особенности:
- Новый бин для каждого HTTP-запроса
- Полезен для хранения данных, специфичных для пользователя в пределах одного запроса
- Требует проксирования (
proxyMode) из-за разницы в жизненном цикле
2. Session Scope
Session scope — бин существует в контексте HTTP-сессии пользователя.
@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION,
proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserSession {
private String username;
private List<String> visitedPages = new ArrayList<>();
private LocalDateTime sessionStart;
@PostConstruct
public void init() {
this.sessionStart = LocalDateTime.now();
}
public void recordPageVisit(String page) {
visitedPages.add(page);
}
public List<String> getPageHistory() {
return new ArrayList<>(visitedPages);
}
}
@RestController
public class SessionController {
private final UserSession userSession;
public SessionController(UserSession userSession) {
this.userSession = userSession;
}
@GetMapping("/history")
public List<String> getHistory() {
return userSession.getPageHistory();
}
}
Особенности:
- Один бин на пользовательскую сессию
- Идеален для хранения состояния пользователя (корзина покупок, предпочтения)
- Требует того же проксирования, что и Request scope
- Автоматически удаляется при завершении сессии
3. Application Scope (ServletContext Scope)
Application scope — бин существует в контексте всего приложения (аналог Singleton для веб-приложений).
@Component
@Scope(WebApplicationContext.SCOPE_APPLICATION)
public class AppStatistics {
private AtomicLong totalRequests = new AtomicLong(0);
private AtomicLong totalUsers = new AtomicLong(0);
public void incrementRequests() {
totalRequests.incrementAndGet();
}
public void incrementUsers() {
totalUsers.incrementAndGet();
}
public long getTotalRequests() {
return totalRequests.get();
}
public long getTotalUsers() {
return totalUsers.get();
}
}
@RestController
public class StatsController {
private final AppStatistics statistics;
public StatsController(AppStatistics statistics) {
this.statistics = statistics;
}
@GetMapping("/stats")
public Map<String, Long> getStats() {
return Map.of(
"totalRequests", statistics.getTotalRequests(),
"totalUsers", statistics.getTotalUsers()
);
}
}
4. Custom Scope
Можно создать собственный scope для специфичных требований приложения.
// Определение кастомного scope
public class ThreadScope implements Scope {
private static final ThreadLocal<Map<String, Object>> threadObjects =
ThreadLocal.withInitial(HashMap::new);
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Map<String, Object> objects = threadObjects.get();
return objects.computeIfAbsent(name, k -> objectFactory.getObject());
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
// Регистрация колбеков очистки
}
@Override
public Object resolveContextualObject(String key) {
return threadObjects.get().get(key);
}
@Override
public String getConversationId() {
return Thread.currentThread().getName();
}
}
// Регистрация кастомного scope
@Configuration
public class ScopeConfiguration {
@Bean
public static CustomScopeConfigurer customScopeConfigurer() {
CustomScopeConfigurer configurer = new CustomScopeConfigurer();
configurer.addScope("thread", new ThreadScope());
return configurer;
}
}
// Использование
@Component
@Scope("thread")
public class ThreadLocalData {
private String value;
// ...
}
5. WebSocket Scope (Spring 4.0+)
WebSocket scope — бин существует в контексте одного WebSocket соединения.
@Component
@Scope(value = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class WebSocketMessage {
private Queue<String> messageQueue = new LinkedQueue<>();
public void addMessage(String message) {
messageQueue.offer(message);
}
public List<String> getMessages() {
return new ArrayList<>(messageQueue);
}
}
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new ChatHandler(), "/chat")
.setAllowedOrigins("*");
}
}
Сравнение Scopes
| Scope | Жизненный цикл | Использование | Потокобезопасность |
|---|---|---|---|
| Singleton | Приложение | Stateless сервисы | Да (но сложнее) |
| Prototype | Каждое использование | Stateful объекты | Да |
| Request | HTTP запрос | Данные конкретного запроса | Да |
| Session | Сессия пользователя | Состояние пользователя | Да |
| Application | Приложение | Статистика приложения | Нужна синхронизация |
| Custom | Определяется | Специальные требования | Зависит от реализации |
Важные замечания
1. Проксирование WebApplicationContext scopes
// ❌ Ошибка: Request scope без проксирования в Singleton
@Service
public class UserService {
private final RequestData requestData; // Проблема!
public UserService(RequestData requestData) {
this.requestData = requestData; // Кэшируется при создании Service
}
}
// ✅ Правильно: Используй проксирование
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST,
proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestData { }
2. Потокобезопасность в Application scope
@Component
@Scope(WebApplicationContext.SCOPE_APPLICATION)
public class AppCache {
// ❌ Небезопасно при конкурентном доступе
private Map<String, String> cache = new HashMap<>();
// ✅ Правильно
private Map<String, String> cache =
Collections.synchronizedMap(new HashMap<>());
// Или используй ConcurrentHashMap
private final ConcurrentHashMap<String, String> asyncCache =
new ConcurrentHashMap<>();
}
Заключение
Понимание различных scopes в Spring критично для:
- Правильного управления памятью
- Обеспечения потокобезопасности
- Оптимизации производительности
- Корректной работы многопользовательского приложения
Выбор правильного scope — это архитектурное решение, которое влияет на всю систему.