Показаны сообщения с ярлыком approach. Показать все сообщения
Показаны сообщения с ярлыком approach. Показать все сообщения

среда, 2 марта 2011 г.

Smooks: парсим xml с помощью ... xml

Smooks - отличный инструмент, который решает задачу парсинга xml используя xml-маппинг тэгов и аттрибутов на объекты и их свойства. Вообще продукт позиционируется как инструмент для трансформации, биндинга, валидации и обработки данных в различных форматах (CSV, XML, EDI). Я рассматриваю этот продукт как достойную альтернативу других механизмов парсинга xml - JAXB, JAXP, Digester и пр.

Предположим нам надо распарсить такой вот xml:

Xml необходимо распарсить в List из объектов типа PlayerRole:

Для начала создадим xml-маппинг необходимый для преобразования данных в объекты:

В Smooks все метаданные, необходимые для парсинга участков xml, представляются в виде бинов с сылками друг на друга. Адресация в xml осуществляется с помощью xpath-выражений. Если при парсинге необходима какая-то пост-обработка данных можно воспользоваться возможностью выполнения groovy-скриптов (тэг g:groovy), при этом код работы с xml будет более лаконичн чем на java. Из groovy также возможны обращение к бинам smooks.

Ну и наконец пример запуска механизма парсинга данных с приведенным конфигом:

Чтобы получить результат парсинга надо выбрать бин, обрабатывающий root-овый тэг, в нашем случае это бин с id="roles-bean".

Я выбраю этот движок для разбора xml т.к. он почти не требует кода и интегрирован с groovy. Использую его когда в проекте требуется распарсить различные конфигурационные ресурсы на этапе запуска сервера.

среда, 3 ноября 2010 г.

Поиск по ключу входящему в числовой диапазон.

Дана таблица по которой, в зависимости от опыта игрока, определяется его уровень:

  Диапазон опыта     Уровень

0 < exp <= 12 1
12 < exp <= 30 2
30 < exp <= 67 3
... ...
231 < exp <= 300 10

Задача: реализовать алгоритм поиска уровня.

Для решения задачи я задействую класс java.util.TreeMap. Почему этот класс? Все просто, он реализует важный интерфейс - java.util.NavigableMap, появившийся с версии Java 1.6. Его метод

Map.Entry<K,V> ceilingEntry(K key)

как раз позволяет найти запись в map-е удовлетворяющую условию K(n) < key <= K(n+1) и при этом будет содержать значение по ключу K(n+1). Таким образом, остается составить map-у из записей: ключ - макс. значение опыта на уровне, значение - уровень. Код решения:

Для наглядности я захардкодил map-у в коде, на деле же она создается из xml.

пятница, 29 октября 2010 г.

Обновление максимального значения без использования блокировок

При реализации грамотных многопоточных механизмов, порой используют только синхронизованные блоки кода. Но, не следует также забывать и о функции CAS (Compare and swap, представленной соответствующей процессорной командой). Данная функция присутствует во всех классах пакета java.util.concurrent.atomic.

Алгортимы с использованием CAS являются безблокировочными, ведь потокам не приходится ожидать блокировок для их исполнения. Операция CAS выполняется успешно или неуспешно, но независимо от исхода она завершается в предсказуемые сроки. В случае неуспешности выполнения программа может сделать попытку выполнения этой функции, либо выполнить другие операции.

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

вторник, 6 апреля 2010 г.

Транзакционные синхронизации в Spring Framework

Выполнение определенной логики по окончанию текущей транзакции в Spring Framework можно "подключать" динамически с помощью так называемых транзакционных синхронизаций.

Транзакционная синхронизация - это по сути callback-объект типа org.springframework.transaction.support.TransactionSynchronization, который содержит в себе определенные методы вызываемые из AbstractPlatformTransactionManager на соответствующие действия над транзакцией (afterCompletion, afterCommit, afterRollback, suspend и т.д.). org.springframework.transaction.support.TransactionSynchronizationManager - менеджер со статическими методами, используемый для регистрации и управления синхронизациями. При написании своей синхронизации в качестве базового класса удобно использовать org.springframework.transaction.support.TransactionSynchronizationAdapter. Вот пример:

суббота, 6 марта 2010 г.

Сериализация полей без set-методов и поддержка аннотаций в BlazeDS

Для передачи данных между BlazeDS 3.2 и клиентом рекомендуется использовать value object-ы. Эти объекты содержат только те данные, которые действительно необходимы клиенту.

К сожалению, в BlazeDS есть некоторые неудобства при сериализации объектов:

  • Обязательное наличие set-метода (можно и пустого), если вы хотите сериализовать поле у которого есть только get-метод

  • Сериализация enum-ов как объектов, вместо использования строчного значения, как заявлено в документации

Так же не очень удобно, на мой взгляд, является создание отдельных классов для каждого value object-a. Было бы удобнее помечать аннтоциями getter-ы или поля, которые будут учавствовать/отсутсвовать в value object-е, без создания нового класса.

Чтобы устранить вышеперечисленные недостатки придется разобраться с механизмом сериализации BlazeDS. Кратко опишу классы, которые задействованы при сериализации: flex.messaging.io.PropertyProxy - основной интерфейс, используемый для сериализации/десериализации объектов. Каждому классу объекта, передаваемому на клиента, соответствует определенная реализация PropertyProxy, в основном это flex.messaging.io.BeanProxy. Соответствие (Класс объекта - PropertyProxy-объект) держится в классе flex.messaging.io.PropertyProxyRegistry, при сериализации/десериализации BlazeDS получает PropertyProxy для какого-либо объекта используя методы: getProxy и getProxyAndRegister.

Т.к. PropertyProxyRegistry является singleton-ом, то замену реализации нам придется сделать через смену значения поля registry, которое спрятано внутри класса. Реализация, в которой убраны перечисленные выше недостатки, выглядит так:

среда, 28 октября 2009 г.

Динамическая модификация контекста в Spring

Существует механизм изменения контекста Spring, который запускается после сбора всей информации о нем (из xml и аннотаций) и срабатывает до создания самих bean-ов. Я говорю об интерфейсе org.springframework.beans.factory.config.BeanFactoryPostProcessor, реализуя который вы получаете именно такую возможность. Используя данный механизм очень удобно менять параметры уже объявленных где-то в контексте bean-ов. Объект реализующий этот интерфейс можно зарегистрировать как обычный bean в контексте, либо добавить его на этапе создания контекста, например так:

Только не забудьте указать в конструкторе контекста аргументу refresh значение false, иначе наш механизм будет вызываться лишь для новых bean-ов добавляемых уж после создания контекста (как это делать я писал тут). При вызове метода refresh произойдет инициализация контекста и наш обработчик будет вызван.

Описанный выше прием оказывается очень полезен в использовании при тестировании, когда нужно изменить какие-то из параметров bean-ов или просто выставить свои значения.

понедельник, 17 августа 2009 г.

Управление версиями в Maven

После очередного релиза проекта приходится менять его версию, проходясь при этом по всем pom-файлам. Процедура эта довольно утомительная и избавить от нее поможет versions-maven-plugin.

Достаточно выставить в головном pom-файле новую версию проекта и затем выполнить команду:

mvn versions:update-child-modules

Как вы наверное догадались, команда выполнит обновление всех версий проекта в pom-файлах дочерних модулей. Если же, по какой-то причине, вы остались недовольны проделанной работой плагина, то содержимое pom-файлов можно легко вернуть в исходное состояние командой:

mvn versions:revert

Чтобы удалить backup-ы pom-файлов выполните:

mvn versions:commit

Помимо выполнения обновления версии проекта, есть еще несколько полезных возможностей. Вывод списка новых версий зависимостей проекта, доступных из репозитария:

mvn versions:display-dependency-updates

Список новых версий плагинов используемых в проекте:

mvn versions:display-plugin-updates

понедельник, 3 августа 2009 г.

Использование LiquiBase с Hibernate и Maven. Часть 2

Теперь о том, что касается изменений которые вносит hibernate в структуру БД, при добавлении/изменении сущностей, если в настройках указан параметр hibernate.hbm2ddl.auto=update. Как продолжить пользоваться этой возможностью, если теперь для всех изменений структуры БД мы должны использовать только liquibase? Было бы не плохо получать sql генерируемый hibernate, который можно легко поместить в changeset liquibase. Для этого необходимо подключить hibernate3-maven-plugin к модулю, в котором используется hibernate:

Необоходимым условием для работы плагина является наличие файла hibernate.hbm.xml, в нем должны содержаться следующие настройки:

Не забудьте также и о том, что теперь все маппинги классов тоже нужно указывать в этом файле, а не в каком-нибудь SessionFactoryBean, если используете Spring. Настройка SessionFactoryBean будет выглядеть примерно так:

Стоит отметить, что параметру hibernate.hbm2ddl.auto, в вашем проекте, нужно присвоить значение validate. Теперь при каждом выполнении команды:

mvn hibernate3:hbm2ddl -Dhibernate.create=true

вам будет выводится sql генерируемый hibernate, который можно вставить в changeset. Параметр -Dhibernate.create=true следует указывать, в том случае, когда вам требуется SQL всей схемы БД. В случае, когда требуется получить SQL-команды, выполняемые hibernate для обновления схемы БД, то вместо первого параметра используйте -Dhibernate.update=true.

И помните, что в случае обновления БД вам нужно будет следить SQL, генерируемым hibernate, т.к. не все изменения параметров объектов входят в SQL (к примеру, hibernate не будет создавать некоторые constraint-ы).

четверг, 9 июля 2009 г.

Использование LiquiBase с Hibernate и Maven. Часть 1

Когда-то я представлял вашему вниманию инструмент для управления изменениями в БД - liquibase. Теперь я решил описать как его можно использовать в проектах с maven-сборкой, и hibernate.

Для начала необходимо создать отдельный maven-артефакт, который будет содержать только ресурсы - xml-файлы с changeSet-ами от liquibase. Структура файлов в папке resources нашего артефакта следующая:

    /changelogs/версия_проекта/*-changelog.xml
/changelogs/версия_проекта/release-changelog.xml
/project-changelogs.xml

Файл project-changesets.xml включает в себя набор файлов release-changelog.xml всех версий проекта, вот пример его содержимого:

Таким же образом файлы release-changelog.xml включают в себя список файлов с измененями, находящихся в той же папке и касающихся лишь данной версии проекта. Сами изменения я предпочитаю описывать не пользуясь при это тэгами liquibase, а использую привычный SQL.

Благодаря наличию в liquibase такого понятия как "контекст", можно разделить все операции над БД на три контекста:

  • schemedata - контекст в котором будут отражены изменения структруры базы;

  • initdata - контекст определяющий данные для инициализации БД, например добавление новых пользовательских ролей;

  • migratedata - контекст используемый при изменении "живых" данных, т.е данных появляющихся в системе при ее эксплуатации.

Такая концепция позволит вам четко разделять виды изменений в БД. А также "накатывать" изменения лишь нужных контекстов. Пример:

Если в проекте предполагается использовать две базы - БД разработчика/продакшн и БД для тестов, то pom-файл, созданного нами артефакта, будет содержать следующие строки:

Параметры app.database.xxx и test.database.xxx можно определить в головном pom-файле проекта. Теперь накатывать изменения на обе БД можно одной командой:

mvn process-resources либо mvn clean install

Чтобы сделать rollback обеих баз на несколько changeset-ов назад к команде следует добавить параметры:

-Dliquibase.goal=rollback -Dliquibase.rollbackCount=1

liquibase.rollbackCount - определят кол-во changeset-ов которые следует откатить.

Порой может возникнуть ситуация, когда требуется внести изменение в существующий changeset и заново накатить все changeset-и. Здесь поможет параметр:

-Dliquibase.dropFirst=true

Если потребуется работать с каждой БД отдельно, то необходимо использовать уже команды самого плагина liquibase:

mvn liquibase:update либо mvn liquibase:rollback

Для тестовой БД добавляем параметр -Ptestdb в командной строке.

четверг, 2 апреля 2009 г.

Переключение из HTTPS-соединения в HTTP (в Tomcat)

Для наиболее безопасной передачи конфиденциальных данных по сети, в веб-приложениях, используется HTTPS-соединение. HTTPS-соединение лучше использовать не на весь пользовательский сеанс, а лишь на определенных этапах где защищенность канала действительно очень важна.

К примеру, если сделать на HTTPS-соединении только логин пользователя, а после его входа в систему снова использовать обычный HTTP, то на web-сервере Tomcat придется реализовать поддержку корректного переключения с HTTPS на HTTP.

Конфигурация Spring Security для такого случая:

<security:http auto-config="true" >
<security:intercept-url pattern="/login.html" access="IS_AUTHENTICATED_ANONYMOUSLY"
requires-channel="https"/>
<security:intercept-url pattern="/billing.html" access="ROLE_USER"
requires-channel="http"/>
</security:http>

Возможны два варианта переключения соединений:


1.    2.

В первом случае все будет работать, т.к. Tomcat создаст cookie сперва для HTTP сессии, которая будет действительна и для HTTPS, а следовательно может быть аутентифицирована Spring Security и использована дальше в работе с обычным HTTP.

Во втором случае Tomcat создаст cookie для HTTPS с параметром secure, а это значит что аутентификация такой сессии не позволит использовать в дальнейшем обычную HTTP сессию. Сookie с параметром secure привязана лишь к HTTPS-сессиям. Для пользователя же это обернется редиректом Spring Security на страницу логина, но с уже созданной cookie, и только второй логин уже пустит пользователя в систему. Чтобы решить эту проблему необходимо, находясь в HTTPS сессии, создать cookie для обычной HTTP сессии без параметра secure. С помощью фильтра это реализовать довольно просто:

package com.blogspot.nkoksharov.filter;

import java.io.IOException;

import javax.servlet.*;
import javax.servlet.http.*;

import org.apache.commons.lang.StringUtils;

public class HttpsCookieFilter implements Filter {

@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
final HttpServletRequest httpRequest = (HttpServletRequest) request;
final HttpServletResponse httpResponse = (HttpServletResponse) response;
final HttpSession session = httpRequest.getSession(false);

if (request.isSecure() && session != null) {
final Cookie sessionCookie = new Cookie("JSESSIONID", session.getId());
sessionCookie.setMaxAge(-1);
sessionCookie.setSecure(false);
String contextPath = httpRequest.getContextPath();
if (StringUtils.isNotBlank(contextPath)) {
sessionCookie.setPath(contextPath);
} else {
sessionCookie.setPath("/");
}
httpResponse.addCookie(sessionCookie);
}

chain.doFilter(request, response);
}

...

}

Теперь переход с https-соединения будет выглядеть так:

Фильтр лучше всего поместить в самом начале цепочки фильтров, важно чтобы он был перед springSecurityFilterChain. Следует отметить, что при таком решении http-cookie будет приходить пользователю на каждый https-запрос. Т.к. сложно опеределить при https-соединении создана ли cookie для http-канала.

понедельник, 30 марта 2009 г.

Замена механизма отображения ошибок при валидации в Tapestry 5.1

При унифицированном подходе к отображению ошибок на форме, обычно для каждого поля ставится соответствующий html-элемент, либо элементы в которых пользователю сообщается об ошибке в конкретном поле. К примеру, в Spring-mvc в таком случае вам придется ставить тэг form:error для каждого поля.

Механизм отображения ошибок в Tapestry 5 реализован с помощью специального декоратора, который призван избавить разработчика от расставления подобных элементов на своей странице. Реализация этого механизма находится в классе org.apache.tapestry5.internal.DefaultValidationDecorator. Результат его работы выглядит так:

Согласитесь, не очень красиво вставлять красный крест после каждого поля с ошибкой да еще и подсвечивать его содержимое. Во всяком случае меня это не устроило. Хотелось бы, чтобы ошибки выглядели примерно так:

Для этого, в модуле tapestry вашего приложения необходимо подменить markup-фильтр DefaultValidationDecorator совоей реализацией:

package com.blogspot.nkoksharov.tapestry;

import org.apache.tapestry5.*;
import org.apache.tapestry5.ioc.*;
import org.apache.tapestry5.services.*;

public class AppModule {

public void contributeMarkupRenderer(OrderedConfiguration configuration,
final Environment environment) {

MarkupRendererFilter defaultValidationDecorator = new MarkupRendererFilter() {
public void renderMarkup(MarkupWriter writer, MarkupRenderer renderer) {
ValidationDecorator decorator = new ExValidationDecorator(environment, writer);
environment.push(ValidationDecorator.class, decorator);
renderer.renderMarkup(writer);
environment.pop(ValidationDecorator.class);
}
};

configuration.override("DefaultValidationDecorator", defaultValidationDecorator);
}
}

Логика декоратора выглядит так:

package com.blogspot.nkoksharov.tapestry;

import org.apache.tapestry5.*;
import org.apache.tapestry5.services.Environment;

public class ExValidationDecorator extends BaseValidationDecorator {

private static final String ERROR_ELEMENT_CLASS = "error_element";

private final Environment environment;
private final MarkupWriter markupWriter;

public ExValidationDecorator(Environment environment, MarkupWriter markupWriter) {
this.environment = environment;
this.markupWriter = markupWriter;
}

@Override
public void afterField(Field field) {
if (inError(field)) {
String errorId = field.getClientId() + "Error";
markupWriter.element("div",
"class", ERROR_ELEMENT_CLASS,
"id", errorId);
markupWriter.write(getErrorMsg(field));
markupWriter.end();
}
}

private String getErrorMsg(Field field) {
ValidationTracker tracker = environment.peekRequired(ValidationTracker.class);
return tracker.getError(field);
}

private boolean inError(Field field) {
ValidationTracker tracker = environment.peekRequired(ValidationTracker.class);
return tracker.inError(field);
}
}

Данный декоратор после каждого поля ставит такой вот элемент:

<div class="error_element" id="someElementError">
Сообщение об ошибке для данного поля
</div>

Стоит отметить, что возможность переопределения элементов в списке конфигурации сервисов, через метод override стала доступна лишь начиная с версии Tapestry 5.1.0.0

воскресенье, 22 февраля 2009 г.

Динамическое добавление bean-ов в контекст Spring

Хорошо известно, что Spring способен создавать bean-ы из статитческого контекста. Однако, имеется возможность динамического создания и регистрации bean-ов, которая в некоторых случаях может оказаться весьма полезной. Такую функциональность предоставляет интерфейс org.springframework.beans.factory.support.BeanDefinitionRegistry метод registerBeanDefinition. Для регистрации вам понадобится объект реализующий интерфейс org.springframework.beans.factory.config.BeanDefinition. Созданием такого объекта занимается класс org.springframework.beans.factory.support.BeanDefinitionBuilder, предоставляющий практически все те же возможности конфигурации bean-а, что и в статическом xml. При регистрации bean-а, если bean c таким id уже существует, то он будет заменен.

package com.blogspot.nkoksharov;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.testng.annotations.Test;

public class SpringTest {

@Test
public void testDynamicCreation() {
ConfigurableApplicationContext context =
new ClassPathXmlApplicationContext("/spring/test/application-context.xml");
BeanDefinitionRegistry factory = (BeanDefinitionRegistry) context.getBeanFactory();

BeanDefinitionBuilder builder =
BeanDefinitionBuilder.rootBeanDefinition(com.blogspot.nkoksharov.TestBeanImpl.class);
builder.addPropertyValue("property", "someValue");
BeanDefinition definition = builder.getBeanDefinition();
factory.registerBeanDefinition("testBean", definition);


...

TestBean bean = (TestBean) context.getBean("testBean");

...

}

}

Также можно и удалять bean-ы методом removeBeanDefinition.

четверг, 11 декабря 2008 г.

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

Механизм рассылки event-ов в контексте Spring реализован одним интерфейсом org.springframework.context.event.ApplicationEventMulticaster. Именно ему и будут делегироваться все добавления, удаления listener-ов, а также публикация event-ов для них. По-умолчанию, в качестве реализации такого интерфейса будет использоваться объект класса org.springframework.context.event.SimpleApplicationEventMulticaster. В самом же контексте данный bean регистрируется под именем applicationEventMulticaster. Однако, если под таким именем bean уже существует и реализует нужный интерфейс, то будет использоваться именно он.

Необоходимость создания своего ApplicationEventMulticaster-а может возникнуть, например, чтобы рассылать event-ы по spring-контекстам целого кластера. Именно этот случай и реализован в моем примере. Сам механизм коммуникации между нодами обеспечивается фреймворком JGroups, который рассылает сообщения по сети multicast-ом через UDP-протокол. Объект com.blogspot.nkoksharov.springevents.jgroups.JGroupsMulticaster переопределяет метод void multicastEvent(ApplicationEvent event) и рассылает по сети лишь те экземпляры event-ов, классы которых являются подклассами com.blogspot.nkoksharov.springevents.jgroups.JGroupsEvent. А вот и сам код JGroupsMulticaster-а:

package com.blogspot.nkoksharov.springevents.jgroups;

import org.jgroups.*;
import org.slf4j.*;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.SimpleApplicationEventMulticaster;

/**
* Alternative multicaster to org.springframework.context.event.SimpleApplicationEventMulticaster
*
* @author nkoksharov
*
*/
public class JGroupsMulticaster extends SimpleApplicationEventMulticaster implements InitializingBean, DisposableBean, LocalEventMulticaster {

private final Logger logger = LoggerFactory.getLogger(getClass());
private JChannel jchannel;
private JGroupsReceiver receiver = new JGroupsReceiver();
private String configFile;
private String clusterName;

public void multicastEvent(ApplicationEvent event) {
if (event instanceof JGroupsEvent) {
try {
jchannel.send(new Message(null, null, event));
} catch (ChannelNotConnectedException e) {
logger.error("channel not connected", e);
} catch (ChannelClosedException e) {
logger.error("channel closed", e);
}
} else {
super.multicastEvent(event);
}
}

public void localMulticastEvent(JGroupsEvent event) {
super.multicastEvent(event);
}

public void afterPropertiesSet() throws Exception {
jchannel = new JChannel(configFile);
receiver.setLocalMulticater(this);
jchannel.setReceiver(receiver);
jchannel.connect(clusterName);
}

public void destroy() throws Exception {
jchannel.close();
}
public void setConfigFile(String configFile) {
this.configFile = configFile;
}
public void setClusterName(String clusterName) {
this.clusterName = clusterName;
}
}

Рассылка же абсолютно всех event-ов, в частности системных, может привести к некорректной работе всего spring-контекста.

Проект собирается с помощью Maven-а и запускается командой:

mvn compile exec:java

Каждый запуск - это новый контекст, т.е. можно запустить приложение на разных компьютерах в локальной сети и наблюдать появление сообщений в консоли о получении event-ов. Также можно запустить несколько экземпляров приложения на одном компьютере, все должно работать.

Скачать исходники
Event-Driven Development в Spring. Часть 1

пятница, 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 в отдельные модули, которые не потребуют в дальнейшем модификации, если понадобится добавить вызов какого-либо сервиса.