Как Расширить Spring Собственным Типом Репозитория На Примере Infinispan



Зачем об этом писать? Это моя первая статья, в которой я попытаюсь описать практический опыт, полученный мной при работе с 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.
  
  
  
  
  
  
  
  
   

@SpringBootApplication @EnableMapRepositories("my.person.package.for.entities") public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }

Мы можем практически полностью скопировать содержимое кода этой аннотации и создать собственные EnableInfinispanRepositories. Чтобы не писать это каждый раз, скажу, что мы всегда заменяем карту слов на infinispan, в подобных реализациях скрытых спойлерами.

Код аннотации 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

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.