Будет ли создан один экземпляр бина, если два класса используют один и тот же бин?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Один экземпляр бина для двух классов в Spring?
Этот вопрос проверяет понимание Spring контейнера и жизненного цикла бинов. Ответ: ДА, по умолчанию будет создан ОДИН экземпляр (scope = singleton).
Пример
// Сервис
@Service
public class UserService {
private int instanceCounter = 0;
public UserService() {
instanceCounter++;
System.out.println("UserService создан. Номер экземпляра: " + instanceCounter);
}
public void processUser(String name) {
System.out.println("Processing: " + name);
}
}
// Первый класс использует сервис
@Component
public class OrderService {
private final UserService userService;
@Autowired
public OrderService(UserService userService) {
this.userService = userService;
System.out.println("OrderService получил UserService");
}
public void createOrder(String userName) {
userService.processUser(userName);
}
}
// Второй класс тоже использует сервис
@Component
public class ReportService {
private final UserService userService;
@Autowired
public ReportService(UserService userService) {
this.userService = userService;
System.out.println("ReportService получил UserService");
}
public void generateReport(String userName) {
userService.processUser(userName);
}
}
// Main
public class Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Application.class);
OrderService orderService = context.getBean(OrderService.class);
ReportService reportService = context.getBean(ReportService.class);
orderService.createOrder("John");
reportService.generateReport("John");
}
}
// Вывод:
// UserService создан. Номер экземпляра: 1
// OrderService получил UserService
// ReportService получил UserService
// Processing: John
// Processing: John
// ВСЕ ИСПОЛЬЗУЮТ ОДИНАКОВЫЙ ЭКЗЕМПЛЯР USERSERVICE!
Доказательство: один экземпляр
@Service
public class UserService {
private int counter = 0;
public synchronized void increment() {
counter++;
}
public int getCounter() {
return counter;
}
}
@Component
public class ServiceA {
@Autowired
private UserService userService;
public void process() {
userService.increment();
System.out.println("ServiceA вызвал increment. Counter = " + userService.getCounter());
}
}
@Component
public class ServiceB {
@Autowired
private UserService userService;
public void process() {
userService.increment();
System.out.println("ServiceB вызвал increment. Counter = " + userService.getCounter());
}
}
public class TestSingleton {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(TestSingleton.class);
ServiceA serviceA = context.getBean(ServiceA.class);
ServiceB serviceB = context.getBean(ServiceB.class);
serviceA.process(); // Counter = 1
serviceB.process(); // Counter = 2 (увеличился!)
// Если бы были разные экземпляры UserService,
// counter в ServiceB был бы 1, а не 2
}
}
// Вывод:
// ServiceA вызвал increment. Counter = 1
// ServiceB вызвал increment. Counter = 2
// ✓ Это доказывает, что используется ОДНА копия UserService
ПОЧЕМУэто работает?
Spring использует Singleton паттерн по умолчанию
// Spring контейнер
public class DefaultListableBeanFactory implements BeanFactory {
private Map<String, Object> singletons = new HashMap<>();
@Override
public Object getBean(String beanName) {
// Проверяем, создан ли бин уже
if (singletons.containsKey(beanName)) {
return singletons.get(beanName); // Возвращаем существующий
}
// Создаём новый
Object bean = createBean(beanName);
singletons.put(beanName, bean); // Сохраняем
return bean;
}
}
Scope бинов в Spring
По умолчанию scope = singleton, но можно изменить:
1. Singleton (по умолчанию)
@Service
@Scope("singleton") // или без аннотации
public class UserService {
// Один экземпляр на всё приложение
}
// Все компоненты используют один и тот же объект
UserService service1 = context.getBean(UserService.class);
UserService service2 = context.getBean(UserService.class);
assert service1 == service2; // true - один объект
2. Prototype (новый на каждый запрос)
@Service
@Scope("prototype")
public class UserService {
// Новый экземпляр каждый раз
}
// Разные компоненты получают разные объекты
UserService service1 = context.getBean(UserService.class);
UserService service2 = context.getBean(UserService.class);
assert service1 != service2; // true - разные объекты
3. Request (для веб-приложений)
@Service
@Scope("request") // Один экземпляр на HTTP запрос
public class RequestService {
// Новый экземпляр на каждый HTTP request
}
4. Session
@Service
@Scope("session") // Один экземпляр на сессию
public class SessionService {
// Новый экземпляр на каждую сессию пользователя
}
Практический пример: потенциальные проблемы
Проблема: Mutable state в singleton бине
@Service
public class NotThreadSafeService {
private List<String> items = new ArrayList<>(); // ОПАСНО!
public void addItem(String item) {
items.add(item); // Без синхронизации!
}
public List<String> getItems() {
return items;
}
}
// Проблема: Race condition!
OrderService и ReportService используют один экземпляр
Оба могут менять items одновременно
Может быть corruption данных
Решение 1: Синхронизация
@Service
public class ThreadSafeService {
private final List<String> items = Collections.synchronizedList(new ArrayList<>());
public void addItem(String item) {
items.add(item); // Безопасно
}
}
Решение 2: Immutable поля
@Service
public class ImmutableService {
private final List<String> items = new ArrayList<>();
public void addItem(String item) {
// items = new ArrayList<>(items); // Неправильно
items.add(item); // items остаётся final, но содержимое может меняться
}
}
Решение 3: Dependency на scope
@Service
@Scope("prototype")
public class StatefulService {
private List<String> items = new ArrayList<>(); // OK
public void addItem(String item) {
items.add(item); // Каждый использующий получит свой экземпляр
}
}
Best Practices
1. Делай бины stateless (когда возможно)
// ✓ Хорошо - stateless
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User getUser(Long id) {
return userRepository.findById(id);
}
}
// ✗ Плохо - stateful
@Service
public class OrderProcessor {
private Order currentOrder; // State!
private User currentUser; // State!
public void processOrder() {
// Race conditions!
}
}
2. Если нужен state - используй prototype или request scope
@Service
@Scope("prototype")
public class OrderProcessor {
private Order currentOrder; // OK, каждый экземпляр свой
private User currentUser;
}
3. Используй constructor injection
// ✓ Явно показывает зависимости
@Service
public class UserService {
private final UserRepository userRepository;
private final Logger logger;
@Autowired
public UserService(UserRepository userRepository, Logger logger) {
this.userRepository = userRepository;
this.logger = logger;
}
}
// vs field injection (скрывает зависимости)
@Service
public class UserService {
@Autowired
private UserRepository userRepository; // Неявно
}
Ответ резюме
ДА — если двум классам inject'ить один бин, они получат ОДИН экземпляр (по умолчанию scope=singleton).
Исключения:
- Если бин имеет scope=prototype → будут разные экземпляры
- Если бин имеет scope=request/session → для разных request/session
- Если вручную создавать через new → разные объекты, но это не Spring бины
Моральный: Singleton бины должны быть stateless или thread-safe, иначе могут быть race conditions.