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

вторник, 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. Вот пример:

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

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

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

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

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

воскресенье, 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.

четверг, 19 февраля 2009 г.

Сборка приложения используя maven-assembly-plugin с модулями Spring

Столкнулся с проблемой сборки приложения при помощи maven-assembly-plugin. Напомню, что этот плагин предназначен для создания архива, включающего в себя в распакованном виде какие-либо ресурсы, а также классы из зависимостей проекта. То есть, если вам нужно поместить приложение со всеми его зависимыми классами из других библиотек в один jar-файл, то этот инструмент может вам помочь.

Однако по причине того, что плагин распаковывает все зависимости в один каталог, могу возникнуть проблемы с библиотеками, имеющими файлы с одинаковыми названиями. Именно такая ситуация и происходит, когда пытаешься собрать приложение в один jar имеющее в зависимостях spring библиотеки. После запуска приложения возникает такая ошибка:

Exception in thread "main" java.lang.IllegalStateException: 
org.springframework.beans.factory.parsing.BeanDefinitionParsingException:
Configuration problem: Failed to import bean definitions
from URL location [classpath:/spring/dao-context.xml]
Caused by:
org.springframework.beans.factory.parsing.BeanDefinitionParsingException:
Configuration problem: Unable to locate Spring NamespaceHandler for
XML schema namespace [http://www.springframework.org/schema/tx]
Offending resource: class path resource [spring/dao-context.xml]

at org.springframework.beans.factory.parsing.
FailFastProblemReporter.error(FailFastProblemReporter.java:68)

Возникает она из-за того, что spring не может найти соответствующий NamespaceHandler предназначенный для обработки xml-схемы http://www.springframework.org/schema/tx. Соответствие между NamespaceHandler-ми и xml-схемами описывается в файле spring.handlers. Файл spring.handlers имеется в нескольких библиотеках spring (spring-aop.jar, spring-beans.jar...) в папке META-INF, таким образом в наш jar попадает лишь какой-то определенный экземпляр.При дублировании файлов maven-assembly-plugin в результате оставляет первый из них, merge-ить их он не умеет. Та же ситуация происходит и с файлом spring.schemas, в котором описываются соотвествия путей к xsd описаниям xml-схем и их реальным положением внутри библиотеки spring.

Решить возникшую проблему можно создав файлы spring.schemas и spring.handlers в папке /src/main/resources/META-INF вашего maven-проекта, агрегирующих в себе содержимое одноименных файлов из библиотек. В файле настройки maven-assembly-plugin-а необходимо указать папку /src/main/resources/META-INF которую мы будем копировать в архив, с помощью тэгов fileSets:

<assembly>
<id>with-dependencies</id>
<formats>
<format>jar</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>${basedir}/src/main/resources/META-INF</directory>
<outputDirectory>META-INF</outputDirectory>
</fileSet>
</fileSets>

<dependencySets>
<dependencySet>
<outputDirectory/>
<outputFileNameMapping/>
<unpack>true</unpack>
</dependencySet>
</dependencySets>
</assembly>

Таким образом наши файлы попадут в архив первыми и уже не будут перезаписаны другими вариантами из библиотек Spring.

Кстати, версию maven-assembly-plugin-а следует использовать 2.2-beta-3 и старше. Предыдующая версия будет копировать все одинаковые файлы в ваш jar, и в одной папке будет несколько файлов с одним именем.

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