← Назад к вопросам
Какие знаешь области видимости бинов в Spring?
2.2 Middle🔥 151 комментариев
#Spring Framework
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Области видимости бинов в Spring (Scopes)
Scope определяет жизненный цикл бина и как он будет переиспользоваться в приложении. Spring поддерживает несколько встроенных и пользовательских scopes.
1. Singleton (по умолчанию)
Один экземпляр бина на весь контекст приложения:
@Configuration
public class SingletonScopeExample {
// По умолчанию все бины — singleton
@Bean
public UserService userService() {
return new UserService();
}
// Явно указываем singleton
@Bean
@Scope("singleton")
public UserRepository userRepository() {
return new UserRepository();
}
}
@Service
// По умолчанию @Service имеет scope = singleton
public class UserService {
// Одинцкий экземпляр на всё приложение
}
public class SingletonBehavior {
public static void main(String[] args) {
ApplicationContext context =
new AnnotationConfigApplicationContext(SingletonScopeExample.class);
UserService service1 = context.getBean(UserService.class);
UserService service2 = context.getBean(UserService.class);
System.out.println(service1 == service2); // true! Один объект
System.out.println(System.identityHashCode(service1));
System.out.println(System.identityHashCode(service2)); // Одинаковые
}
}
Характеристики:
- Один экземпляр на SessionFactory
- Потокобезопасность: нужна сама
- Производительность: максимальная (кэширование)
- Использование: для stateless сервисов (UserService, Repository)
Потокобезопасная реализация:
@Service
@Scope("singleton")
public class SingletonCacheService {
// Потокобезопасный кэш
private final ConcurrentHashMap<String, String> cache =
new ConcurrentHashMap<>();
public void put(String key, String value) {
cache.put(key, value);
}
public String get(String key) {
return cache.get(key);
}
}
2. Prototype
Новый экземпляр при каждом запросе:
@Configuration
public class PrototypeScopeExample {
@Bean
@Scope("prototype")
public RequestContext requestContext() {
return new RequestContext();
}
}
public class PrototypeBehavior {
public static void main(String[] args) {
ApplicationContext context =
new AnnotationConfigApplicationContext(PrototypeScopeExample.class);
RequestContext ctx1 = context.getBean(RequestContext.class);
RequestContext ctx2 = context.getBean(RequestContext.class);
System.out.println(ctx1 == ctx2); // false! Разные объекты
}
}
Характеристики:
- Новый экземпляр при каждом getBean()
- Состояние изолировано: каждый клиент получает свой объект
- Использование: для stateful объектов (контекст запроса, форма)
- Внимание: Spring НЕ вызывает destroy методы (утечки ресурсов возможны)
Правильное использование:
@Component
@Scope("prototype")
public class RequestData implements DisposableBean {
private String data;
private Connection connection;
public RequestData() {
this.connection = createConnection();
}
// Вызывается вручную, Spring не вызывает для prototype
@Override
public void destroy() throws Exception {
if (connection != null) {
connection.close();
}
}
private Connection createConnection() {
// ...
return null;
}
}
3. Request (веб-приложения)
Новый экземпляр для каждого HTTP запроса:
@Configuration
@EnableWebMvc
public class RequestScopeExample {
@Bean
@Scope("request")
public UserContext userContext() {
return new UserContext();
}
}
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserContext userContext; // Разный для каждого запроса
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
// userContext содержит данные текущего запроса
return userContext.getUserData(id);
}
}
// Контекст запроса
@Component
@Scope("request")
public class UserContext {
private Long userId;
private String username;
private LocalDateTime requestTime = LocalDateTime.now();
public Long getUserId() {
return userId;
}
public String getUsername() {
return username;
}
}
Характеристики:
- Один экземпляр на HTTP запрос
- Автоматическое очищение после завершения запроса
- Использование: для данных запроса, авторизации, контекста
4. Session (веб-приложения)
Новый экземпляр для каждой HTTP сессии:
@Configuration
@EnableWebMvc
public class SessionScopeExample {
@Bean
@Scope("session")
public ShoppingCart shoppingCart() {
return new ShoppingCart();
}
}
@RestController
@RequestMapping("/api/cart")
public class CartController {
@Autowired
private ShoppingCart cart; // Одна корзина на сессию
@PostMapping("/add")
public void addItem(@RequestBody Item item) {
cart.add(item);
}
@GetMapping
public List<Item> getItems() {
return cart.getItems();
}
}
// Сессионная корзина
@Component
@Scope("session")
public class ShoppingCart {
private List<Item> items = new ArrayList<>();
private LocalDateTime createdAt = LocalDateTime.now();
public void add(Item item) {
items.add(item);
}
public List<Item> getItems() {
return items;
}
public void clear() {
items.clear();
}
}
Характеристики:
- Один экземпляр на пользовательскую сессию
- Доступен внутри сессии: разные пользователи = разные объекты
- Использование: корзина покупок, данные сессии
5. Application (ServletContext scope)
Один экземпляр на всё приложение (как singleton, но для веб):
@Component
@Scope("application")
public class ApplicationSettings {
private String appName;
private String version;
// Общие настройки приложения
}
6. WebSocket Scope
Для WebSocket соединений (Spring 4.0+):
@Component
@Scope("websocket")
public class WebSocketContext {
private String sessionId;
private Map<String, Object> attributes = new ConcurrentHashMap<>();
}
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Bean
@Scope("websocket")
public WebSocketHandler webSocketHandler() {
return new WebSocketHandler() {
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) {
// Один handler на WebSocket соединение
}
@Override
public void afterConnectionEstablished(WebSocketSession session) {}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) {}
@Override
public boolean supportsPartialMessages() {
return false;
}
};
}
}
Comparison of Scopes
public class ScopeComparison {
public static void main(String[] args) {
// Singleton: один объект на приложение
UserService service = context.getBean(UserService.class);
UserService service2 = context.getBean(UserService.class);
// service == service2 (true)
// Prototype: новый объект каждый раз
UserData data = context.getBean(UserData.class);
UserData data2 = context.getBean(UserData.class);
// data == data2 (false)
// Request: один на запрос
// Session: один на сессию
// Application: один на приложение
}
}
Пользовательские Scopes
// Создание пользовательского scope
@Configuration
public class CustomScopeConfig {
@Bean
public static CustomScopeConfigurer customScopeConfigurer() {
CustomScopeConfigurer configurer = new CustomScopeConfigurer();
// Создаём свой scope
configurer.addScope("tenant", new TenantScope());
return configurer;
}
}
// Реализация scope
public class TenantScope implements Scope {
private final ThreadLocal<Map<String, Object>> threadLocalScope =
ThreadLocal.withInitial(HashMap::new);
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Map<String, Object> scope = threadLocalScope.get();
return scope.computeIfAbsent(name, key -> objectFactory.getObject());
}
@Override
public Object remove(String name) {
Map<String, Object> scope = threadLocalScope.get();
return scope.remove(name);
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
// Регистрация callback'a на удаление
}
@Override
public Object resolveContextualObject(String key) {
return null;
}
@Override
public String getConversationId() {
return Thread.currentThread().getName();
}
}
// Использование
@Component
@Scope("tenant")
public class TenantData {
private String tenantId;
// Разные данные для каждого tenant'а
}
Проблемы с Scopes
Singleton Bean с Prototype Dependency
// Проблема: singleton получает prototype один раз
@Service
public class UserService {
@Autowired
private UserContext context; // Prototype
public void processUser() {
// context всегда одинаковый!
String userId = context.getUserId();
}
}
// Решение 1: ObjectProvider
@Service
public class FixedUserService {
@Autowired
private ObjectProvider<UserContext> contextProvider;
public void processUser() {
UserContext context = contextProvider.getIfAvailable();
String userId = context.getUserId();
}
}
// Решение 2: Proxy
@Service
public class ProxyUserService {
@Autowired
@Lazy
private UserContext context; // Proxy для ленивой инициализации
public void processUser() {
String userId = context.getUserId();
}
}
// Решение 3: Method injection
@Service
public class MethodInjectionService {
public void processUser() {
UserContext context = createUserContext();
}
@Lookup
protected UserContext createUserContext() {
// Spring переопределяет этот метод
return null;
}
}
Best Practices
public class ScopeBestPractices {
// 1. ✅ Используй Singleton для stateless сервисов
// 2. ✅ Используй Prototype для stateful объектов
// 3. ✅ Используй Request для контекста запроса
// 4. ✅ Используй Session для данных сессии
// 5. ✅ Используй ObjectProvider для внедрения prototype в singleton
// 6. ❌ Избегай prototype если нужна высокая производительность
// 7. ✅ Явно указывай scope (не полагайся на defaults)
// 8. ✅ Помни про destroy callbacks для prototype beans
}
Таблица Scopes
| Scope | Жизненный цикл | Использование | Thread-safe |
|---|---|---|---|
| Singleton | Приложение | Services, Repos | Нужна сама |
| Prototype | Каждый getBean | Forms, Requests | Да (изолировано) |
| Request | HTTP запрос | Context данных | Да |
| Session | HTTP сессия | Корзина, Данные | Да |
| Application | Приложение | Settings | Да |
| WebSocket | WS соединение | WS handlers | Да |
Вывод
Основные scopes в Spring:
- Singleton (default) — один на приложение
- Prototype — новый при каждом запросе
- Request — один на HTTP запрос
- Session — один на сессию пользователя
- Application — один на приложение
- WebSocket — один на WS соединение
Правильный выбор scope критичен для корректности и производительности приложения.