Поиск по блогу

Monday, July 19, 2010

Spring hints #2. Используем Spring Context Events в задаче e-mail нотификации пользователей.

     В этом посте я расскажу о реализации собственного шаблона применительно к задаче отправки  пользовательских уведомлений. В примере я буду использовать следующие компоненты:
  • Объект, читающий данные из базы. Назовем его DataBaseReader. В упрощенной форме этот объект ничего из реальной базы читать не будет. Его задача имитировать выполнение какой-либо бизнес логики и генерировать ApplicationEvent.
  • Объект-событие NotificationEvent. Этот объект будет содержать в себе необходимую информацию для отправки сообщения.
  • Обработчик событий NotificationEventHandler. Обработчик получает все события, происходящие в Spring контексте, анализирует их и при необходимости обрабатывает.
Итак, приступим.
Необходимо реализовать следующую схему:



На этой схеме присутствуют компоненты, о которых я уже упоминал выше. Давайте познакомимся с остальными:
  • Интерфейс org.springframework.context.ApplicationContextAware.  Данный интерфейс определяет всего один метод - setApplicationContext(ApplicationContext appContext); Все Spring beans, реализующие данных интерфейс будут иметь возможность обращаться к контексту, к которому они принадлежат. В нашей задаче это нужно, потому что все контекстные события публикуются через ApplicationContext.
  • Абстрактный класс ApplicationEvent реализует логику объектов-событий.
  • Интерфейс ApplicationListener определяет всего один метод - onApplicationEvent(ApplicationEvent applicationEvent). Все объекты из Spring контекста, реализующие данных интерфейс будут получать объекты типа ApplicationEvent при возникновении любого контекстного события.
Есть так же вспомогательные компоненты. Абстрактный класс TimerTask служит для создания объектов, запускаемых по временному графику. Я решил немного усложнить задачу, добавив в нее Spring Scheduling. Т.о. DataBaseReader будет выполняться по графику. А если быть точнее, то каждые 3 секунды.


Итак, переходим к реализации. Создадим Maven проект со следующими зависимостями:


   
      org.springframework
      spring
      2.5.6
   


Реализуем NotificationEvent класс. Напомню, что этот класс представляет собой объект-событие, содержащий в себе необходимые данные для нотификации пользователей по e-mail.

import org.springframework.context.ApplicationEvent;

/**
 * @author lazy-codder
 */
public class NotificationEvent extends ApplicationEvent {

    private int eventType;
    private Object message;
    private Exception e;

    public NotificationEvent(Object source) {
        super(source);
    }

    public NotificationEvent(Object source, int eventType, Object message, Exception e) {
        super(source);
        this.eventType = eventType;
        this.message = message;
        this.e = e;
    }

    public int getEventType() {
        return eventType;
    }

    public void setEventType(int eventType) {
        this.eventType = eventType;
    }

    public Object getMessage() {
        return message;
    }

    public void setMessage(Object message) {
        this.message = message;
    }

    public Exception getException() {
        return e;
    }
}

Теперь реализуем DataBaseReader с Scheduling частью.

import codder.notification.NotificationEvent;
import codder.notification.NotificationEventType;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import java.util.TimerTask;

/**
 * @author lazy-codder
 */
public class DataBaseReader extends TimerTask implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void run() {
        applicationContext.publishEvent(new NotificationEvent(this, 
                                        NotificationEventType.INFO, 
                                        "Data base was read!", null));
    }

    public void setApplicationContext(ApplicationContext applicationContext) 
                                                              throws BeansException {
        this.applicationContext= applicationContext;
    }
}

Обратите внимание на то как реализован интерфейс ApplicationContextAware. Теперь DataBaseReader имеет возможность обращаться к Spring контексту и публиковать события так, как это реализовано в методе run(). Объект DataBaseReader по своему смыслу должен реализовывать некую бизнес логику.

Далее создадим файл spring-context.xml и сконфигурим в нем наш DataBaseReader.



    
        
        
        
    

    
        
            
                
            
        
    

Отлично, теперь у нас определен бизнес объект, который будет запускаться каждые 3 секунды автоматически сразу после создания контекста.

Теперь создадим интерфейс NotificationManagerIF и его реализацию.
/**
 * @author lazy-codder
 */
public interface NotificationManagerIF {

    void sendEmail(String infoSubject, Object message);
    void sendEmail(String errorSubject, Object message, Exception exception);
}
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @author lazy-codder
 */
public class NotificationManagerImpl implements NotificationManagerIF {
    private static final Log log = LogFactory.getLog(NotificationManagerImpl.class);

    public void sendEmail(String infoSubject, Object message) {
        log.info("Just print message in log instead of sending it by e-mail.");
        log.info("Subject: " + infoSubject + "; Message: " + message);
    }

    public void sendEmail(String errorSubject, Object message, Exception exception) {
        log.info("Just print message in log instead of sending it by e-mail.");
        log.info("Subject: " + errorSubject + "; Message: " + message);
        log.info(exception.getMessage(), exception);
    }
}

Как видите в этом примере все упрощено. Реализация несет только основную концепцию, поэтому NotificationManagerImpl выводит полученные сообщения в лог, имитируя отправку сообщения. Сейчас осталось реализовать обработчик событий:

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;

/**
 * @author lazy-codder
 */
public class NotificationEventHandler implements ApplicationListener {
    private Log log = LogFactory.getLog(NotificationEventHandler.class);

    private NotificationManagerIF notificationManager;

    private static final String INFO_SUBJECT = 
                             "Info subject (please do not reply this e-mail)";
    private static final String ERROR_SUBJECT = 
                             "Error subject (please do not reply this e-mail)";

    public void onApplicationEvent(ApplicationEvent applicationEvent) {
        if(applicationEvent instanceof NotificationEvent) {
            NotificationEvent notificationEvent= (NotificationEvent) applicationEvent;
            switch (notificationEvent.getEventType()) {
                case NotificationEventType.INFO:
                    log.info("Sending info message...");
                    notificationManager.sendEmail(INFO_SUBJECT, 
                                                  notificationEvent.getMessage());
                    break;
                case NotificationEventType.ERROR:
                    log.info("Sending error message...");
                    notificationManager.sendEmail(ERROR_SUBJECT, 
                                                  notificationEvent.getMessage(),
                                                  notificationEvent.getException());
                    break;
                default:
                    log.info("Ignoring event...");
                    break;
            }
        }
    }

    public void setNotificationManager(NotificationManagerIF notificationManager) {
        this.notificationManager = notificationManager;
    }
}

Последнее, что нужно определить - это набор статических констант:

/**
 * @author lazy-codder
 */
public class NotificationEventType {
    public static final int INFO = 1;
    public static final int ERROR = 2;
}

Теперь добавляем NotificationEventHandler и NotificationManagerImpl в Spring контекст. В конечном результате получаем это:



    
        
        
        
    

    
        
            
                
            
        
    

    
        
    

    

Запускаем, и наслаждаемся результатом...

/**
 * @author lazy-codder
 */
public class Main {

    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-context.xml");   
    }
}

На это все!

4 comments:

  1. Неплохой пример. Давно порываюсь вынести нотификацию из бизнес методов при помощи событий, только руки никак не доходят. Для полноты картины можно рассказать как сделать нотификацию через аспекты.

    ReplyDelete
  2. Да, у тебя feed не работает. Сделай фикс.

    ReplyDelete
  3. Где не работает? У меня работает. Могу кстати зазипованный проект с исходниками сюда выложить.

    ReplyDelete
  4. Вчера за полтора часа в спешке опубликовал пост перед сном. Мог где-то ляпнуть.

    ReplyDelete