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

суббота, 7 февраля 2009 г.

Инструмент для просмотра логов

В повседневной работе довольно часто приходится смотреть логи от log4j. Прежде пользовался лишь обычным view-ром, пока не нашел довольно полезный open-source инструмент MindTree Insight, предназначенный именно для этой задачи.

Из настроек отмечу лишь, то что поле Preferences -> Maintain Preferences -> Primary Pattern должно содержать ту же строку форматирования, которую вы указали в настройках log4j.

понедельник, 2 февраля 2009 г.

Проблема с SecurityContext-ом при invalidate http-сессии

В Spring Security есть возможность определения допустимого числа параллельных http-сессий от одного пользователя. Как только кол-во свободных сессий исчерпает себя, то по-умолчанию самая первая из них будет уничтожена через метод invalidate() и ее место займет новая. За настройку такой возможности отвечает тэг concurrent-session-control и его параметр max-sessions.

Проблема, которая была обнаружена мной буквально на днях, следующая - при уничтожении сессии, вместо объекта org.springframework.security.context.SecurityContext приходит null. Выявить эту ошибку может следующий код:

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

SecurityContext context = SecurityContextHolder.getContext();
Object principal = context.getAuthentication().getPrincipal();

...

}

Если посмотреть порядок следования фильтров в Spring Security:

  1. CHANNEL_FILTER

  2. CONCURRENT_SESSION_FILTER

  3. SESSION_CONTEXT_INTEGRATION_FILTER

  4. LOGOUT_FILTER

  5. ...

то мы увидим, что фильтр SESSION_CONTEXT_INTEGRATION_FILTER, который как раз и выставляет нужный нам объект SecurityContext, находится после CONCURRENT_SESSION_FILTER фильтра, управляющего параллельными сессиями. Такая "неверная", в данном случае, конфигурация фильтров и приводит к потере SecurityContext-а. Потому завел на это дело багу - SEC-1092. Решения я увидел два - поменять фильтры местами, с помощью конфигурации это делается довольно просто, и выставить объект SecurityContext самому. Второй вариант мне показался более верным. Менять фильтры местами, думаю не стоит, возможно есть причина по которой они расположены именно так.

Нужный нам экземпляр SecurityContext-а можно взять из текущий http-сессии, к которой он и привязан. Хранится он в сессии по имени HttpSessionContextIntegrationFilter.SPRING_SECURITY_CONTEXT_KEY. Объект класса HttpSessionContextIntegrationFilter как раз и находится на позиции SESSION_CONTEXT_INTEGRATION_FILTER, и выставляет текущий SecurityContext в SecurityContextHolder. Сам код решения взят из этого фильтра и выглядит так:

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

HttpSession session = event.getSession();
SecurityContext context = (SecurityContext) session.getAttribute(HttpSessionContextIntegrationFilter.SPRING_SECURITY_CONTEXT_KEY);
SecurityContextHolder.setContext(context);


SecurityContext context = SecurityContextHolder.getContext();
Object principal = context.getAuthentication().getPrincipal();

...

}