[Java] Как расширить Spring своим типом Repository на примере Infinispan
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Зачем об этом писать?Это моя первая статья, в ней я попытаюсь описать полученный мною практический опыт работы со Spring Repository под капотом фреймворка. Готовых статей про эту тему я в интернете не нашёл ни на русском, ни на английском, были только несколько репозиториев исходников на github, ну и исходники самого Spring. Поэтому и решил, почему бы не написать, вдруг тема написания своих типов репозиториев для Spring для кого-то ещё актуальна.Программирование для Infinispan я не буду рассматривать подробно, детали реализации всегда можно посмотреть в исходниках, указанных в конце статьи. Основной упор сделан именно на сопряжение механизма Spring Boot Repository и нового типа репозитория.С чего всё начиналосьВ ходе работы на одном из проектов у одного из архитектора возникла идея, что можно написать свои типы репозиториев по аналогии, как это сделано в разных модулях Spring (например, JPARepository, KeyValueRepository, CassandraRepository и т.п.). В качестве пробной реализации решили выбрать работу с данными через Infinispan. Естественно, что архитекторы - люди занятые, поэтому реализовывать идею поручили Java разработчику, т.е. мне.Когда я начал прорабатывать тему в интернете, то Google упорно выдавал почти одни статьи про то, как замечательно использовать JPARepository во всех видах на тривиальных примерах. По KeyValueRepository информации было ещё меньше. На StackOverFlow печальные никем не отвеченные вопросы по подобной теме. Делать нечего, пришлось лезть в исходники Spring.InfinispanЕсли говорить кратко про 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.Чтобы каждый раз это не писать, скажу, что слово map мы всегда заменяем на 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();
}
}
В коде перегружаются два метода. В одном связывается Registar со своей активирующей аннотацией, чтобы потом из неё получить заполненные атрибуты конфигурации. В другом находится реализация хранилища данных, специфичных для данного типа репозитория.Сделаем по аналогии свой 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;
}
}
Нужно обязательно в нашем ConfigurationExtension ещё перегрузить метод getIdentifyingTypes(), чтобы в нём сослаться на наш новый тип репозитория (см. реализацию выше).
@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.Если у вас есть конструктивные замечания к данной реализации, то пишите в комментариях, либо можете сделать pull request в исходном проекте.
===========
Источник:
habr.com
===========
Похожие новости:
- [Разработка веб-сайтов, Open source, JavaScript, ReactJS, TypeScript] Что выбрать в качестве библиотеки компонентов для React-проекта
- [Хостинг, Программирование, DevOps, Микросервисы] Технология Serverless: снова привет, 1970-е (перевод)
- [Ruby, Oracle, Python, JavaScript, Java] Опыт сопряжения Java, JavaScript, Ruby и Python в одном проекте посредством GraalVM
- [Java, Git, DevOps] Trunk Based Development и Spring Boot, или ветвись оно все по абстракции
- [Open source, Виртуализация, Kubernetes, Openshift] 7 вещей, которые нужно проработать, прежде чем запускать OpenShift в продакшн
- [Тестирование IT-систем, Программирование, Java, IT-стандарты, Промышленное программирование] Принцип слоеного теста
- [JavaScript, Google Chrome, Расширения для браузеров, Хранение данных, Хранилища данных] Использование Redux в MV3 расширениях Chrome (перевод)
- [Разработка веб-сайтов, JavaScript, Программирование, ReactJS] Вопросы для собеседования по хукам React (перевод)
- [Поисковые технологии, Python, Разработка мобильных приложений, Kotlin] Не баян: ищем дубликаты изображений на основе Milvus с индексом FAISS внутри
- [Разработка веб-сайтов, JavaScript, Программирование, ReactJS] Заметка о том, как React обновляет состояние (перевод)
Теги для поиска: #_java, #_java, #_infinispan, #_spring_framework, #_spring_boot, #_spring, #_repository, #_adapter, #_backend, #_java
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 00:17
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Зачем об этом писать?Это моя первая статья, в ней я попытаюсь описать полученный мною практический опыт работы со Spring Repository под капотом фреймворка. Готовых статей про эту тему я в интернете не нашёл ни на русском, ни на английском, были только несколько репозиториев исходников на github, ну и исходники самого Spring. Поэтому и решил, почему бы не написать, вдруг тема написания своих типов репозиториев для Spring для кого-то ещё актуальна.Программирование для Infinispan я не буду рассматривать подробно, детали реализации всегда можно посмотреть в исходниках, указанных в конце статьи. Основной упор сделан именно на сопряжение механизма Spring Boot Repository и нового типа репозитория.С чего всё начиналосьВ ходе работы на одном из проектов у одного из архитектора возникла идея, что можно написать свои типы репозиториев по аналогии, как это сделано в разных модулях Spring (например, JPARepository, KeyValueRepository, CassandraRepository и т.п.). В качестве пробной реализации решили выбрать работу с данными через Infinispan. Естественно, что архитекторы - люди занятые, поэтому реализовывать идею поручили Java разработчику, т.е. мне.Когда я начал прорабатывать тему в интернете, то Google упорно выдавал почти одни статьи про то, как замечательно использовать JPARepository во всех видах на тривиальных примерах. По KeyValueRepository информации было ещё меньше. На StackOverFlow печальные никем не отвеченные вопросы по подобной теме. Делать нечего, пришлось лезть в исходники Spring.InfinispanЕсли говорить кратко про 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); } } @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; } @Import(MapRepositoriesRegistrar.class)
public @interface EnableMapRepositories { } public class MapRepositoriesRegistrar extends RepositoryBeanDefinitionRegistrarSupport {
@Override protected Class<? extends Annotation> getAnnotation() { return EnableMapRepositories.class; } @Override protected RepositoryConfigurationExtension getExtension() { return new MapRepositoryConfigurationExtension(); } } @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); ... } ... } @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; } } @NoRepositoryBean
public interface InfinispanRepository <T, ID> extends PagingAndSortingRepository<T, ID> { } @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()); } } =========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 00:17
Часовой пояс: UTC + 5