Зачем об этом писать? Это моя первая статья, в которой я попытаюсь описать практический опыт, полученный мной при работе с Spring Repository под капотом фреймворка.
Готовых статей на эту тему в Интернете мне не удалось найти ни на русском, ни на английском языке, на github было всего несколько репозиториев с исходным кодом, да и исходный код самого Spring. Поэтому я решил, а почему бы и не написать, вдруг тема написания своих типов репозиториев для Spring будет актуальна для кого-то еще.
Я не буду подробно рассматривать программирование для Infinispan; Подробности реализации всегда можно узнать в источниках, указанных в конце статьи.
Основной упор делается на сопряжение механизма Spring Boot Repository с новым типом репозитория.
Как это все началось
Во время работы над одним из проектов у одного из архитекторов возникла идея, что можно по аналогии писать свои типы репозиториев, как это сделано в разных модулях Spring (например, JPARepository, KeyValueRepository, CassandraRepository и т. д.).В качестве пробной реализации мы решили выбрать работу с данными через Инфиниспан .
Естественно, архитекторы — люди занятые, поэтому реализацию идеи доверили Java-разработчику, т.е.
мне.
Когда я начал исследовать тему в Интернете, Google упорно не выдавал почти ничего, кроме статей о том, как здорово использовать JPARepository во всех видах с тривиальными примерами.
О KeyValueRepository информации было еще меньше.
На StackOverFlow есть грустные вопросы без ответа на аналогичную тему.
Делать было нечего, пришлось лезть в исходники Spring.
Инфиниспан
Если коротко говорить об Infinispan, то это просто распределенное хранилище данных в виде ключ-значение, и все это постоянно кэшируется в памяти.Перезагружаем Infinispan, данные все сбрасываются.
Было решено, что наиболее подходящим кандидатом для исследования является KeyValueRepository, так как он наиболее близок к этому направлению, уже реализованный в Spring. Разница лишь в том, что вместо Infinispan (на его месте мог бы быть Hazelcast, например) в качестве хранилища данных KeyValueRepository имеет обычный ConcurrentHashMap.
Выполнение
Чтобы включить возможность использования репозитория для хранилища значений ключа в проекте Spring, используйте аннотацию EnableMapRepositories.Мы можем практически полностью скопировать содержимое кода этой аннотации и создать собственные EnableInfinispanRepositories. Чтобы не писать это каждый раз, скажу, что мы всегда заменяем карту слов на infinispan, в подобных реализациях скрытых спойлерами.@SpringBootApplication @EnableMapRepositories("my.person.package.for.entities") public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Код аннотации EnableInfinispanRepositories @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(InfinispanRepositoriesRegistrar.class)
public @interface EnableInfinispanRepositories {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
ComponentScan.Filter[] excludeFilters() default {};
ComponentScan.Filter[] includeFilters() default {};
String repositoryImplementationPostfix() default "Impl";
String namedQueriesLocation() default "";
QueryLookupStrategy.Key queryLookupStrategy() default
QueryLookupStrategy.Key.CREATE_IF_NOT_FOUND;
Class<?> repositoryFactoryBeanClass() default
KeyValueRepositoryFactoryBean.class;
Class<?> repositoryBaseClass() default
DefaultRepositoryBaseClass.class;
String keyValueTemplateRef() default "infinispanKeyValueTemplate";
boolean considerNestedRepositories() default false;
}
Если мы посмотрим, что происходит в коде аннотации EnableMapRepositories, то увидим, что туда импортируется класс, который и делает всю магию по активации этого типа репозитория.
@Import(MapRepositoriesRegistrar.class)
public @interface EnableMapRepositories {
}
Ниже приведен код MapRepositoriesRegistar. public class MapRepositoriesRegistrar extends
RepositoryBeanDefinitionRegistrarSupport {
@Override
protected Class<? extends Annotation> getAnnotation() {
return EnableMapRepositories.class;
}
@Override
protected RepositoryConfigurationExtension getExtension() {
return new MapRepositoryConfigurationExtension();
}
}
Код перегружает два метода.
В одном регистратор связывается со своей активирующей аннотацией, чтобы затем получить от него заполненные атрибуты конфигурации.
Другой содержит реализацию хранилища данных, специфичного для этого типа хранилища.
Давайте по аналогии сделаем свой InfinispaRepositoriesRegistar. @NoArgsConstructor
public class InfinispanRepositoriesRegistrar extends
RepositoryBeanDefinitionRegistrarSupport {
@Override
protected Class<? extends Annotation> getAnnotation() {
return EnableInfinispanRepositories.class;
}
@Override
protected RepositoryConfigurationExtension getExtension() {
return new InfinispanRepositoryConfigurationExtension();
}
}
Теперь посмотрим, как выглядит сама реализация хранилища.
public class MapRepositoryConfigurationExtension extends
KeyValueRepositoryConfigurationExtension {
@Override
public String getModuleName() {
return "Map";
}
@Override
protected String getModulePrefix() {
return "map";
}
@Override
protected String getDefaultKeyValueTemplateRef() {
return "mapKeyValueTemplate";
}
@Override
protected AbstractBeanDefinition getDefaultKeyValueTemplateBeanDefinition(RepositoryConfigurationSource configurationSource) {
BeanDefinitionBuilder adapterBuilder = BeanDefinitionBuilder
.
rootBeanDefinition(MapKeyValueAdapter.class); adapterBuilder.addConstructorArgValue( getMapTypeToUse(configurationSource)); BeanDefinitionBuilder builder = BeanDefinitionBuilder .
rootBeanDefinition(KeyValueTemplate.class); .
} .
}
MapKeyValueAdapter будет реализовывать наиболее специфичную часть, характерную специально для хранения локального кэша в HashMap. Но KeyValueTemplate оборачивает методы адаптера в довольно общий код. Поэтому, чтобы выполнить задачу и заменить хранилище данных из локального кэша на распределенное хранилище Infinispan, нужно сделать аналогичное ConfigurationExtension, но заменить его на конкретный адаптер, где будет реализована вся логика чтения/записи/поиска данных, характерная для Infinispan будет внедрен.
Реализация InfinispanRepositoriesConfigurationExtension @NoArgsConstructor
public class InfinispanRepositoryConfigurationExtension
extends KeyValueRepositoryConfigurationExtension {
@Override
public String getModuleName() {
return "Infinispan";
}
@Override
protected String getModulePrefix() {
return "infinispan";
}
@Override
protected String getDefaultKeyValueTemplateRef() {
return "infinispanKeyValueTemplate";
}
@Override
protected Collection<Class<?>> getIdentifyingTypes() {
return Collections.singleton(InfinispanRepository.class);
}
@Override
protected AbstractBeanDefinition getDefaultKeyValueTemplateBeanDefinition(RepositoryConfigurationSource configurationSource) {
RootBeanDefinition infinispanKeyValueAdapterDefinition =
new RootBeanDefinition(InfinispanKeyValueAdapter.class);
RootBeanDefinition keyValueTemplateDefinition =
new RootBeanDefinition(KeyValueTemplate.class);
ConstructorArgumentValues constructorArgumentValuesForKeyValueTemplate = new ConstructorArgumentValues();
constructorArgumentValuesForKeyValueTemplate
.
addGenericArgumentValue(infinispanKeyValueAdapterDefinition);
keyValueTemplateDefinition.setConstructorArgumentValues(
constructorArgumentValuesForKeyValueTemplate);
return keyValueTemplateDefinition;
}
}
Также необходимо перегрузить метод getIdentifyingTypes() в нашем ConfigurationExtension, чтобы ссылаться на наш новый тип репозитория (см.
реализацию выше).
@NoRepositoryBean
public interface InfinispanRepository <T, ID> extends
PagingAndSortingRepository<T, ID> {
}
Чтобы все наконец заработало, нам нужно настроить KeyValueTemplate, передав ему наш адаптер.
@Configuration
public class InfinispanConfiguration extends CachingConfigurerSupport {
@Autowired
private ApplicationContext applicationContext;
@Bean
public InfinispanKeyValueAdapter getInfinispanAdapter() {
return new InfinispanKeyValueAdapter(
applicationContext.getBean(CacheManager.class)
);
}
@Bean("infinispanKeyValueTemplate")
public KeyValueTemplate getInfinispanKeyValueTemplate() {
return new KeyValueTemplate(getInfinispanAdapter());
}
}
Вот и все.
Можно, конечно, копнуть глубже и не использовать готовые реализации Spring для репозиториев, а наследовать исключительно от их абстрактных классов и интерфейсов, но объем работы будет гораздо больше, чем в этой статье.
Краткое содержание
Написав всего 6 наших классов, мы получили репозиторий нового типа, который может работать с Infinispan как с хранилищем данных.И этот новый тип репозитория работает очень похоже на стандартные репозитории Spring. Полный набор исходников можно найти на моем github .
Источники Spring Data KeyValue также можно увидеть по адресу github .
Если у вас есть конструктивные замечания по данной реализации, пишите в комментариях, либо можете сделать пул-реквест на исходный проект. Теги: #backend #java #spring boot #spring #spring framework #infinispan #adapter #repository
-
Ибп Для Банковских И Финансовых Учреждений
19 Oct, 24 -
#9 Джокаст
19 Oct, 24 -
Мониторинг Технологий Рунета За 2013 Год
19 Oct, 24