← Назад к вопросам
Стоит ли по умолчанию Scope Singleton?
2.3 Middle🔥 281 комментариев
#Spring Framework
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Spring Singleton Scope: Почему это по умолчанию
Вопрос: Стоит ли по умолчанию Singleton scope в Spring?
Ответ: Да, это правильный выбор по умолчанию для большинства компонентов.
Почему Singleton по умолчанию
1. Производительность
Singleton:
- Создание один раз при стартапе
- Один объект на всё приложение
- Быстрое получение из кеша
Prototype:
- Создание для КАЖДОГО запроса
- N объектов за сессию
- Медленнее и больше памяти
Метрика: для 1000 запросов
Singleton: 1 создание объекта
Prototype: 1000 созданий объектов
2. Контролируемое состояние
// ✓ Singleton работает если компонент stateless
@Service
public class UserService {
@Autowired
private UserRepository repository; // Dependence, не state
public User findById(Long id) {
return repository.findById(id);
}
// Один объект = безопасно
}
// ❌ Prototype нужен если есть state
@Service
@Scope("prototype")
public class StatefulService {
private int counter; // State в объекте
public void increment() {
counter++; // Каждый запрос своё значение
}
}
3. Инициализация при стартапе
// Singleton инициализируется один раз при стартапе
@Service
public class CacheService {
@PostConstruct
public void init() {
// Загружаем кеш один раз
loadAllDataIntoMemory();
}
}
// Prototype создаёт новый объект каждый раз
// @PostConstruct вызывается ДЛЯ КАЖДОГО объекта
// Это неэффективно
Архитектура Spring приложения
Spring Container
│
├─ Service Layer (Singleton)
│ ├─ UserService
│ ├─ OrderService
│ └─ PaymentService
│
├─ Repository Layer (Singleton)
│ ├─ UserRepository
│ ├─ OrderRepository
│ └─ PaymentRepository
│
├─ Controller Layer (Singleton)
│ ├─ UserController
│ ├─ OrderController
│ └─ PaymentController
│
└─ Request-scoped beans (Request Scope)
├─ RequestContext
└─ CurrentUserContext
Когда использовать другие Scope
RequestScope (для request-specific data)
@Component
@RequestScope // Новый на каждый HTTP request
public class RequestContext {
private String userId;
private String tenantId;
private List<String> errors;
// Getters/setters
}
@Service
public class UserService {
@Autowired
private RequestContext requestContext;
public User getCurrentUser() {
// Получить текущего пользователя из контекста
String userId = requestContext.getUserId();
return findById(userId);
}
}
SessionScope (для session state)
@Component
@SessionScope // Новый на каждую сессию
public class UserSession {
private User currentUser;
private Locale locale;
private List<Notification> notifications;
// Getters/setters
}
// Использование в контроллере
@RestController
public class DashboardController {
@Autowired
private UserSession userSession;
@GetMapping("/dashboard")
public Dashboard getDashboard() {
User user = userSession.getCurrentUser();
// ...
}
}
Prototype (редко, для stateful objects)
@Component
@Scope("prototype")
public class QueryBuilder {
private String query = "SELECT ";
public QueryBuilder withField(String field) {
query += field + ", ";
return this;
}
public String build() {
return query + "FROM table";
}
}
// Каждый запрос ориентирует новый объект
@Service
public class ReportService {
@Autowired
private QueryBuilder queryBuilder;
public String generateQuery() {
// Новый QueryBuilder для каждого вызова
return queryBuilder
.withField("id")
.withField("name")
.build();
}
}
Проблема с Singleton при неправильном использовании
// ❌ ОПАСНО: Singleton с state
@Service
public class BadUserService {
private List<User> cachedUsers; // ❌ Shared state!
public void loadUsers() {
this.cachedUsers = repository.findAll();
}
public List<User> getUsers() {
return cachedUsers;
}
}
// Проблемы:
// - Thread-safety issues если несколько потоков обращаются
// - Память: кеш растёт
// - Сложно тестировать
// ✓ ПРАВИЛЬНО: использовать внешний кеш
@Service
public class GoodUserService {
@Autowired
private UserRepository repository;
@Autowired
private RedisTemplate<String, User> redisTemplate; // Внешний кеш
public User getUser(Long id) {
return redisTemplate.opsForValue().get("user:" + id)
.orElseGet(() -> repository.findById(id).orElse(null));
}
}
Сравнение Scope
┌─────────────┬───────────┬────────────┬──────────────────┐
│ Scope │ Создание │ Параллели │ Когда использовать│
├─────────────┼───────────┼────────────┼──────────────────┤
│ singleton │ Один раз │ Многие │ Services, repos │
│ prototype │ Каждый │ Один │ Stateful (редко) │
│ request │ На request│ Один/request│ Request context │
│ session │ На session│ Один/session│ User session │
│ application │ Один раз │ All │ Global config │
└─────────────┴───────────┴────────────┴──────────────────┘
Singleton thread-safety
// Singleton ДОЛЖЕН быть thread-safe
// ✓ ПРАВИЛЬНО: Immutable
@Service
public class ImmutableService {
private final ConfigProperties config;
// Не меняется после инициализации
@Autowired
public ImmutableService(ConfigProperties config) {
this.config = config;
}
}
// ✓ ПРАВИЛЬНО: Stateless
@Service
public class StatelessService {
@Autowired
private UserRepository repository;
public User find(Long id) {
// Использует параметры, не field
return repository.findById(id);
}
}
// ✓ ПРАВИЛЬНО: Внешняя синхронизация
@Service
public class SynchronizedService {
@Autowired
private ConcurrentHashMap<String, Object> cache;
// ConcurrentHashMap — thread-safe
}
На собеседовании
Правильный ответ:
"Да, Singleton scope по умолчанию — это правильное решение, потому что:
- Производительность: один объект на всё приложение, быстрое получение
- Память: экономия ресурсов
- Инициализация: один раз при стартапе
- Большинство компонентов stateless (Services, Repositories, Controllers)
Но это требует что компонент ДОЛЖЕН быть thread-safe:
- Не должен хранить state в полях
- Все зависимости через constructor injection
- Если нужна request-specific data — использовать @RequestScope
Prototype scope редко нужен, потому что требует создания нового объекта для каждого использования, что медленно и дорого."
Best Practices
// ✓ Правильная архитектура
@Service
public class OrderService {
@Autowired
private OrderRepository repository; // Inject, не store
@Autowired
private RequestContext context; // Request-scoped
public Order create(OrderRequest request) {
// Используем параметры и injected зависимости
String userId = context.getUserId();
// ...
}
}
Ключевые выводы
- Singleton по умолчанию — правильно для большинства
- Требует stateless компоненты — это обязательно
- RequestScope для request-specific данных
- SessionScope для user session данных
- Prototype очень редко — почти никогда не нужен
- Thread-safety — ответственность разработчика
- Immutable или stateless — золотое правило