← Назад к вопросам

Какие знаешь Scope помимо Singleton и Prototype?

1.8 Middle🔥 191 комментариев
#Spring Framework

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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 объектыДа
RequestHTTP запросДанные конкретного запросаДа
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 — это архитектурное решение, которое влияет на всю систему.

Какие знаешь Scope помимо Singleton и Prototype? | PrepBro