Вопрос 11 · Раздел 2

Как реализован Observer в Java?

4. @Async для тяжёлых операций 5. WeakReference для предотвращения утечек 6. Backpressure в Reactive Streams 7. Spring Events для enterprise приложений

Версии по языкам: English Russian Ukrainian

🟢 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.Observer deprecated с 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!");

Типичные проблемы

  1. Исключение в слушателе останавливает всех
    // ❌ Если один слушатель кидает 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);
            // Продолжаем уведомлять остальных
        }
    }
    
  2. ConcurrentModificationException
    // ❌ Слушатель удаляет себя во время уведомления
    for (NewsListener listener : listeners) {
        listener.onNewVideo(title);  // Внутри: listeners.remove(this) → Exception!
    }
       
    // ✅ Копия списка или CopyOnWriteArrayList
    private final List<NewsListener> listeners = new CopyOnWriteArrayList<>();
    
  3. Утечка памяти
    // ❌ Слушатель не удалён → утечка
    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

  1. Изолируйте слушателей — try/catch вокруг каждого
  2. CopyOnWriteArrayList для thread-safety
  3. Удаляйте слушателей при уничтожении
  4. @Async для тяжёлых операций
  5. WeakReference для предотвращения утечек
  6. Backpressure в Reactive Streams
  7. 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]] — поведенческие паттерны