Как реализован Observer в Java?
4. @Async для тяжёлых операций 5. WeakReference для предотвращения утечек 6. Backpressure в Reactive Streams 7. Spring Events для enterprise приложений
🟢 Junior Level
Observer (Наблюдатель) — паттерн, при котором один объект (Subject) уведомляет других наблюдателей (Observers) об изменениях.
Простая аналогия: Подписка на YouTube канал. Когда автор публикует видео, все подписчики получают уведомление.
Пример:
// Наблюдатель
interface NewsListener {
void onNewVideo(String title);
}
// Subject (издатель)
class YouTuber {
private List<NewsListener> listeners = new ArrayList<>();
public void subscribe(NewsListener listener) {
listeners.add(listener);
}
public void unsubscribe(NewsListener listener) {
listeners.remove(listener);
}
public void publishVideo(String title) {
System.out.println("New video: " + title);
// Уведомляем всех подписчиков
for (NewsListener listener : listeners) {
listener.onNewVideo(title);
}
}
}
// Использование
YouTuber youtuber = new YouTuber();
youtuber.subscribe(title -> System.out.println("I'll watch: " + title));
youtuber.publishVideo("Java Tutorial");
Где используется:
- Кнопки и события в UI
- Подписка на новости
- Реактивные системы
🟡 Middle Level
⚠️
java.util.Observableиjava.util.Observerdeprecated с Java 9. Альтернативы:PropertyChangeListener, Flow API (Java 9+), Spring Events.
Java Bean PropertyChangeListener
// Встроенная реализация Observer в Java
public class Model {
private final PropertyChangeSupport support = new PropertyChangeSupport(this);
private String name;
public void addPropertyChangeListener(PropertyChangeListener listener) {
support.addPropertyChangeListener(listener);
}
public void setName(String name) {
String old = this.name;
this.name = name;
support.firePropertyChange("name", old, name); // Уведомление!
}
}
// Использование
Model model = new Model();
model.addPropertyChangeListener(evt ->
System.out.println(evt.getPropertyName() + ": " + evt.getOldValue() + " → " + evt.getNewValue())
);
model.setName("New Name");
Functional Observer (Java 8+)
// Вместо интерфейса — лямбды
public class EventSource {
private final List<Consumer<String>> listeners = new ArrayList<>();
public void onEvent(Consumer<String> listener) {
listeners.add(listener);
}
public void emit(String event) {
listeners.forEach(listener -> listener.accept(event));
}
}
// Использование
EventSource source = new EventSource();
source.onEvent(e -> System.out.println("Listener 1: " + e));
source.onEvent(e -> System.out.println("Listener 2: " + e));
source.emit("Hello!");
Типичные проблемы
- Исключение в слушателе останавливает всех
// ❌ Если один слушатель кидает exception — остальные не получат уведомление for (NewsListener listener : listeners) { listener.onNewVideo(title); // Exception → цикл прерывается! } // ✅ Изоляция слушателей for (NewsListener listener : listeners) { try { listener.onNewVideo(title); } catch (Exception e) { log.error("Listener failed", e); // Продолжаем уведомлять остальных } } - ConcurrentModificationException
// ❌ Слушатель удаляет себя во время уведомления for (NewsListener listener : listeners) { listener.onNewVideo(title); // Внутри: listeners.remove(this) → Exception! } // ✅ Копия списка или CopyOnWriteArrayList private final List<NewsListener> listeners = new CopyOnWriteArrayList<>(); - Утечка памяти
// ❌ Слушатель не удалён → утечка model.addPropertyChangeListener(listener); // Забыли удалить → модель держит слушатель вечно // ✅ Всегда удаляйте model.removePropertyChangeListener(listener);
🔴 Senior Level
Event-driven архитектура
// Spring Events — Observer на стероидах
@Component
public class OrderService {
private final ApplicationEventPublisher publisher;
public void createOrder(Order order) {
// Сохраняем заказ
orderRepository.save(order);
// Публикуем событие
publisher.publishEvent(new OrderCreatedEvent(order));
}
}
// Слушатель
@Component
public class NotificationService {
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
sendEmail(event.getOrder().getEmail());
}
}
// Асинхронный слушатель
@Component
public class AnalyticsService {
@Async
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
trackAnalytics(event.getOrder()); // Не блокирует основной поток!
}
}
Reactive Streams (Java 9+)
// Flow API — современная реализация Observer
public class NewsPublisher implements Flow.Publisher<String> {
private final List<Flow.Subscriber<String>> subscribers = new ArrayList<>();
@Override
public void subscribe(Flow.Subscriber<String> subscriber) {
subscribers.add(subscriber);
subscriber.onSubscribe(new Flow.Subscription() {
public void request(long n) { /* backpressure */ }
public void cancel() { subscribers.remove(subscriber); }
});
}
public void publish(String news) {
subscribers.forEach(s -> s.onNext(news));
}
}
Thread Safety
// Проблемы многопоточности в Observer
public class EventSource {
// ❌ ArrayList не thread-safe
private List<Consumer<String>> listeners = new ArrayList<>();
// ✅ CopyOnWriteArrayList для частого чтения, редкой записи
private List<Consumer<String>> listeners = new CopyOnWriteArrayList<>();
// ✅ Или synchronized
public synchronized void addListener(Consumer<String> listener) {
listeners.add(listener);
}
// ✅ AtomicReference для singleton слушателя
private final AtomicReference<Consumer<String>> listener = new AtomicReference<>();
}
WeakReference Observers
// Предотвращение утечек через слабые ссылки
public class WeakEventSource {
private final List<WeakReference<Consumer<String>>> listeners = new ArrayList<>();
public void addListener(Consumer<String> listener) {
listeners.add(new WeakReference<>(listener));
}
public void emit(String event) {
// Копируем список чтобы избежать ConcurrentModificationException
// если listener добавит новый listener во время notify
new ArrayList<>(listeners).forEach(l -> l.onEvent(event));
}
}
Production Experience
Реальный сценарий: Blocking listener убил API
- Синхронный @EventListener отправлял email
- Email сервис тормозил → весь API ждал
- Решение: @Async для слушателя
- Результат: API < 100ms стабильно
Best Practices
- Изолируйте слушателей — try/catch вокруг каждого
- CopyOnWriteArrayList для thread-safety
- Удаляйте слушателей при уничтожении
- @Async для тяжёлых операций
- WeakReference для предотвращения утечек
- Backpressure в Reactive Streams
- Spring Events для enterprise приложений
Резюме для Senior
- Observer = основа event-driven архитектуры
- Spring @EventListener = production-ready реализация
- Reactive Streams = backpressure + non-blocking
- Thread-safety: CopyOnWriteArrayList или synchronized
- Async listeners не блокируют издателя
- WeakReference предотвращает утечки
- Isolate каждый слушатель (try/catch)
🎯 Шпаргалка для интервью
Обязательно знать:
- java.util.Observable и Observer deprecated с Java 9, альтернативы: PropertyChangeListener, Flow API, Spring Events
- Spring @EventListener — production-ready Observer: синхронный и @Async для неблокирующих слушателей
- Flow API (Java 9+) — Reactive Streams с backpressure: Publisher, Subscriber, Subscription
- Thread-safety: CopyOnWriteArrayList для частого чтения/редкой записи, или synchronized
- Исключение в одном слушателе не должно останавливать остальных — try/catch вокруг каждого
- WeakReference Observers предотвращают утечки памяти при забывчивости удаления
- ConcurrentModificationException при удалении слушателя во время уведомления — решается копией списка
Частые уточняющие вопросы:
- Почему Observable/Observer deprecated? — Устаревший дизайн, нет поддержки backpressure, синхронный
- Как Spring Events решает проблему Observer? — @EventListener + ApplicationEventPublisher, @Async для неблокирующих
- Что такое backpressure в Reactive Streams? — Механизм, позволяющий подписчику контролировать скорость получения данных
- Как избежать утечки памяти в Observer? — Всегда удалять слушателей, использовать WeakReference
Красные флаги (НЕ говорить):
- “Я использую java.util.Observable” — deprecated с Java 9
- “Слушатели не могут вызвать deadlock” — синхронный слуатель + блокировка = deadlock
- “Конкурентная модификация не проблема” — ConcurrentModificationException при удалении во время notify
- “Все слушатели должны быть синхронными” — @Async для тяжёлых операций (email, analytics)
Связанные темы:
- [[15. Что такое Iterator pattern]] — перебор коллекций
- [[2. Какие категории паттернов существуют]] — Behavioral паттерны
- [[14. Какие типы Proxy существуют]] — Proxy в Spring Events
- [[10. Когда использовать Strategy]] — замена алгоритмов
- [[13. В чём разница между State и Strategy]] — поведенческие паттерны