пятница, 31 октября 2008 г.

Event-Driven Development в Spring. Часть 1

Еще в Spring 1.2 была возможность посылать различные event-ы через весь контекст приложения. Я считаю этот вид коммуникации в некоторых местах приложения единственным верным решением.

Опишу кратко как работает этот механизм. Базовым классом для event-а должен быть org.springframework.context.ApplicationEvent, для рассылки которого используется метод org.springframework.context.ApplicationContext.publishEvent(event). Ну а получать event-ы будут те bean-ы, которые реализуют интерфейс org.springframework.context.ApplicationListener с единственным методом onApplicationEvent(event).

Допустим, что у нас есть логика, после выполнения которой другим сервисам необоходимо также сделать какие-то действия. Стандартный вариант решения такой задачи предполагает использование IoC:

public class BussinesServiceImpl implements BussinesService {

@Autowired
private EmailService emailService;
@Autowired
private LoggingService logService;

public void doAction() {

...

emailService.sendEmail(email, body);
logService.recordLog(data);
}
}

Также можно использовать и аспекты. Только вот получать параметры, которые пришли не через аргументы метода, advice-ам будет сложно. Реализовать такой случай с помощью event-ов можно так:

public class BussinesServiceImpl implements BussinesService, ApplicationContextAware {

private ApplicationContext context;

public void doAction() {

...

ActionEvent event = new ActionEvent();
event.setEmail(email);
event.setEmailBody(body);
event.setData(data);
context.publishEvent(event);
}

public void setApplicationContext(ApplicationContext value) {
context = value;
}
}

Соответственно сами сервисы будут выглядеть так:

public class EmailServiceImpl implements EmailService, ApplicationListener {

...

public void onApplicationEvent(ApplicationEvent event) {
if (!(event instanceof ActionEvent)) {
return;
}

ActionEvent actionEvent = (ActionEvent) event;
sendEmail(actionEvent.getEmail(), actionEvent.getEmailBody());
}
}

public class LoggingServiceImpl implements LoggingService, ApplicationListener {

...

public void onApplicationEvent(ApplicationEvent event) {
if (!(event instanceof ActionEvent)) {
return;
}

ActionEvent actionEvent = (ActionEvent) event;
recordLog(actionEvent.getData());
}
}

Какое преимущество дает такой подход? Изоляцию сервиса, от лишних зависимостей. Такая изоляция дает возможность выносить сервисы типа BussinesService в отдельные модули, которые не потребуют в дальнейшем модификации, если понадобится добавить вызов какого-либо сервиса.

10 комментариев:

Dmitry Utkin комментирует...

Спасибо за действительно полезные статьи :)

Анонимный комментирует...

Хм, а это не применение ли шаблона "Visitor"? Т.е. вместо добавления новых публичных методов в сервисы, добавляете наследников ActionEvent...

Никита Кокшаров комментирует...

По поводу паттерна Visitor Вы явно ошиблись, он здесь никак себя не проявляет.

Анонимный комментирует...

А помоему, это похоже на синглетон

Анонимный комментирует...

А как это будет работать в кластере серверов?

Никита Кокшаров комментирует...

В клястере это работать не будет, т.к. работает лишь в текущем контексте.

Анонимный комментирует...

Хотел сделать предложение рассказать о том, как заставить Spring работать в кластере серверов, но анонимам это нельзя. Так что пишу здесь.

Никита Кокшаров комментирует...

Что именно нельзя анонимам? Вы же оставили свой комментарий. Про клястера - я подумаю как это можно решить.

Анонимный комментирует...

Pretty interesting blog you've got here. Thank you for it. I like such themes and anything connected to this matter. I would like to read more on that blog soon.

Best wishes
Darek Wish

Анонимный комментирует...

Никита, как Вы ухитряетесь создавать свой ActionEvent таким способом

ActionEvent event = new ActionEvent();

?

Ведь метод context.publishEvent принимает ApplicationEvent, а класс, отнаследованный от ApplicationEvent должен обязательно иметь конструктор, принимающий Object.