Какие знаешь типы Bean?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Типы Spring Bean (Области видимости)
Bean — это объект, создаваемый и управляемый Spring контейнером. Spring предоставляет несколько типов (scopes) с разным жизненным циклом и поведением. Это фундаментальная концепция Spring Framework.
1. Singleton Scope (По умолчанию)
Один экземпляр на весь контекст приложения. Самый часто используемый.
@Configuration
public class SingletonBeanConfig {
@Bean
public UserRepository userRepository() {
return new UserRepository();
}
}
// Или через @Component
@Service
public class UserService {
// Создаётся один раз при запуске приложения
}
// Использование
@RestController
public class UserController {
@Autowired
private UserService service; // один и тот же экземпляр
}
// Проверка того же экземпляра
@Test
public void testSingletonScope() {
UserService service1 = context.getBean(UserService.class);
UserService service2 = context.getBean(UserService.class);
assertTrue(service1 == service2); // true — один объект
}
Преимущества:
- Минимальное использование памяти
- Быстрая доступность (инициализирован один раз)
- Хорошо для stateless сервисов
Недостатки:
- Нет thread-safety по умолчанию
- Сложнее тестировать
// ❌ Проблема с Singleton и состоянием
@Service
public class BadUserService {
private User currentUser; // ОПАСНО! Shared state
public void processUser(User user) {
this.currentUser = user; // проблема при многопоточности
}
}
// ✅ Правильно — stateless Singleton
@Service
public class GoodUserService {
private final UserRepository repository;
public void processUser(User user) {
// не сохраняем состояние в полях
repository.save(user);
}
}
2. Prototype Scope
Новый экземпляр создаётся каждый раз при запросе.
@Configuration
public class PrototypeBeanConfig {
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public UserRequest userRequest() {
return new UserRequest();
}
}
// Или через @Component
@Component
@Scope("prototype")
public class UserRequest {
private UUID requestId = UUID.randomUUID();
public UUID getRequestId() {
return requestId;
}
}
// Использование
@Test
public void testPrototypeScope() {
UserRequest request1 = context.getBean(UserRequest.class);
UserRequest request2 = context.getBean(UserRequest.class);
assertNotEquals(request1.getRequestId(), request2.getRequestId()); // true — разные объекты
}
// Инъекция Prototype в Singleton
@Service
public class UserService {
@Autowired
private UserRequest request; // Получит один и тот же prototype bean!
// Это НЕ то, что ожидаем
}
// ✅ Правильное решение — Provider
@Service
public class UserService {
@Autowired
private Provider<UserRequest> requestProvider;
public void processRequest() {
UserRequest request = requestProvider.get(); // новый экземпляр каждый раз
}
}
Преимущества:
- Каждый запрос получает свою копию
- Хорошо для stateful объектов
- Нет проблем с многопоточностью
Недостатки:
- Больше потребления памяти
- Spring не управляет полным жизненным циклом
3. Request Scope
Один экземпляр на один HTTP запрос.
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestContext {
private UUID requestId = UUID.randomUUID();
private long startTime = System.currentTimeMillis();
public UUID getRequestId() {
return requestId;
}
public long getElapsedTime() {
return System.currentTimeMillis() - startTime;
}
}
@RestController
public class UserController {
@Autowired
private RequestContext requestContext;
@GetMapping("/users/{id}")
public User getUser(@PathVariable UUID id) {
System.out.println("Request ID: " + requestContext.getRequestId());
return userService.findById(id);
}
}
// Каждый HTTP запрос получает свой RequestContext
// GET /users/1 → RequestContext с id1
// GET /users/2 → RequestContext с id2
Проксирование (ScopedProxyMode):
// SCOPE_REQUEST требует прокси для работы в Singleton контексте
@Component
@Scope(
value = WebApplicationContext.SCOPE_REQUEST,
proxyMode = ScopedProxyMode.TARGET_CLASS // динамический прокси
)
public class RequestData {
private String data;
}
// Spring создаёт CGLIB прокси
// Каждый вызов метода роутируется к правильному экземпляру для текущего запроса
Преимущества:
- Идеально для request-specific данных
- Автоматическое управление жизненным циклом
Недостатки:
- Только в web приложениях
- Overhead от прокси
4. Session Scope
Один экземпляр на HTTP сессию.
@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserSession {
private User currentUser;
private List<String> visitedPages = new ArrayList<>();
private LocalDateTime createdAt = LocalDateTime.now();
public void setCurrentUser(User user) {
this.currentUser = user;
}
public User getCurrentUser() {
return currentUser;
}
public void addVisitedPage(String page) {
visitedPages.add(page);
}
public List<String> getVisitedPages() {
return visitedPages;
}
}
@RestController
public class UserController {
@Autowired
private UserSession userSession;
@GetMapping("/profile")
public UserProfileDTO getProfile() {
userSession.addVisitedPage("/profile");
User user = userSession.getCurrentUser();
return mapToDTO(user);
}
@GetMapping("/history")
public List<String> getHistory() {
return userSession.getVisitedPages();
}
}
// Разные пользователи получают разные SessionBeans
Преимущества:
- Хранение состояния пользователя между запросами
- Встроенная сериализация для persistence
Недостатки:
- Потребление памяти
- Сложность в распределённых системах
5. Application Scope
Один экземпляр на весь жизненный цикл ServletContext.
@Component
@Scope(WebApplicationContext.SCOPE_APPLICATION)
public class ApplicationMetrics {
private long totalRequests = 0;
private long startTime = System.currentTimeMillis();
private final Map<String, Long> endpointCalls = new ConcurrentHashMap<>();
public void recordRequest(String endpoint) {
totalRequests++;
endpointCalls.merge(endpoint, 1L, Long::sum);
}
public Map<String, Object> getMetrics() {
return Map.of(
"totalRequests", totalRequests,
"uptime", System.currentTimeMillis() - startTime,
"endpointCalls", endpointCalls
);
}
}
@RestController
public class MetricsController {
@Autowired
private ApplicationMetrics metrics;
@GetMapping("/metrics")
public Map<String, Object> getMetrics() {
return metrics.getMetrics();
}
}
6. WebSocket Scope
Один экземпляр на WebSocket сессию.
@Component
@Scope("websocket")
public class WebSocketSession {
private String sessionId = UUID.randomUUID().toString();
private List<Message> messages = new ArrayList<>();
public void addMessage(Message msg) {
messages.add(msg);
}
}
@Component
public class WebSocketHandler implements WebSocketHandler {
@Autowired
private WebSocketSession wsSession; // один на WebSocket соединение
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message)
throws Exception {
wsSession.addMessage(new Message(message.getPayload()));
}
}
7. Custom Scope
Для специальных нужд можем создать свой scope.
public class ThreadScopeRegistry implements Scope {
private static final ThreadLocal<Map<String, Object>> threadBeans =
ThreadLocal.withInitial(HashMap::new);
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Map<String, Object> beanMap = threadBeans.get();
if (!beanMap.containsKey(name)) {
beanMap.put(name, objectFactory.getObject());
}
return beanMap.get(name);
}
@Override
public Object remove(String name) {
return threadBeans.get().remove(name);
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
// нужно реализовать если требуется cleanup
}
@Override
public Object resolveContextualObject(String key) {
return null;
}
@Override
public String getConversationId() {
return Thread.currentThread().getName();
}
}
@Configuration
public class CustomScopeConfig {
@Bean
public static CustomScopeConfigurer customScopeConfigurer() {
CustomScopeConfigurer configurer = new CustomScopeConfigurer();
configurer.addScope("thread", new ThreadScopeRegistry());
return configurer;
}
}
// Использование
@Component
@Scope("thread")
public class ThreadLocalBean {
// один экземпляр на каждый поток
}
Сравнение типов Bean
| Scope | Создание | Жизненный цикл | Использование | Thread-safe |
|---|---|---|---|---|
| Singleton | 1 раз | Всё приложение | Сервисы, репозитории | ✓ (если stateless) |
| Prototype | Каждый раз | До garbage collection | Stateful объекты | ✓ |
| Request | Один на запрос | Один HTTP запрос | Request-specific данные | ✓ |
| Session | Один на сессию | Сессия пользователя | Пользовательское состояние | ✓ |
| Application | 1 раз | Жизненный цикл приложения | Глобальные метрики | ✓ (при правильном использовании) |
| WebSocket | Один на соединение | Жизненный цикл соединения | WebSocket коммуникация | ✓ |
Лучшие практики
// ✅ Singleton (по умолчанию) для stateless сервисов
@Service
public class UserService {
private final UserRepository repository;
// Зависимости через constructor
public UserService(UserRepository repository) {
this.repository = repository;
}
}
// ✅ Prototype для stateful объектов
@Component
@Scope("prototype")
public class ReportBuilder {
private List<ReportLine> lines = new ArrayList<>();
public void addLine(ReportLine line) {
lines.add(line);
}
}
// ❌ Singleton с состоянием
@Service
public class BadService {
private List<User> users; // общее состояние — проблема!
}
// ✅ Request scope для контекста запроса
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST,
proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestInfo {
private UUID requestId = UUID.randomUUID();
}
Проксирование (ScopedProxyMode)
// ScopedProxyMode.NO (по умолчанию)
// Spring создаёт простой объект
// ScopedProxyMode.TARGET_CLASS
// Spring создаёт CGLIB прокси класса
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
// ScopedProxyMode.INTERFACES
// Spring создаёт JDK прокси интерфейса
@Scope(value = "request", proxyMode = ScopedProxyMode.INTERFACES)
public interface RequestData {
String getData();
}
Заключение
- Singleton — 80% случаев (сервисы, репозитории)
- Prototype — для stateful объектов
- Request/Session — для веб-приложений
- Избегай состояния в Singleton beans
- Всегда используй dependency injection через конструктор
Правильный выбор scope критичен для масштабируемости и надёжности приложения.