Русский
Русский
English
Статистика
Реклама

Как расширить Spring своим типом Repository на примере Infinispan

Зачем об этом писать?

Это моя первая статья, в ней я попытаюсь описать полученный мною практический опыт работы со 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 {@Overrideprotected Class<? extends Annotation> getAnnotation() {return EnableMapRepositories.class;}@Overrideprotected RepositoryConfigurationExtension getExtension() {return new MapRepositoryConfigurationExtension();}}

В коде перегружаются два метода. В одном связывается Registar со своей активирующей аннотацией, чтобы потом из неё получить заполненные атрибуты конфигурации. В другом находится реализация хранилища данных, специфичных для данного типа репозитория.

Сделаем по аналогии свой InfinispaRepositoriesRegistar.
@NoArgsConstructorpublic 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 {@Overridepublic String getModuleName() {return "Map";}@Overrideprotected String getModulePrefix() {return "map";}@Overrideprotected String getDefaultKeyValueTemplateRef() {return "mapKeyValueTemplate";}@Overrideprotected 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
@NoArgsConstructorpublic 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(), чтобы в нём сослаться на наш новый тип репозитория (см. реализацию выше).

@NoRepositoryBeanpublic interface InfinispanRepository <T, ID> extends PagingAndSortingRepository<T, ID> {}

Чтобы окончательно всё заработало, нужно сконфигурировать KeyValueTemplate, подсунув ему наш адаптер.

@Configurationpublic 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
К списку статей
Опубликовано: 27.12.2020 16:14:22
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Java

Infinispan

Spring framework

Spring boot

Spring

Repository

Adapter

Backend

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru