Сейчас делаю генератор Kotlin-клиентов по swagger-спецификации. Естественным образом возник вопрос как писать библиотеку для Spring.
Почему библиотека должна зависеть от Spring?
На практике, у меня все приложения на Spring Boot. Поэтому даже не просто от Spring, но и от Spring Boot. Если будет случай, что надо что-то другое, то можно и другим генератором воспользоваться.
Какие версии выбирать?
Нужно выбирать достаточно низкие версии, чтобы приложение их обновляло в вверх, а не библиотека обновляла приложение. Естественно, мажорные версии должны быть одинаковы, иначе нет совместимости.
Как логировать?
Начиная с версии Java 9 есть такой класс для логирования: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/System.Logger.html
Как вообще правильно создать библиотеку и чем она отличается от приложения?
- Зависимости как в приложении
- Сборка в jar
- Публикация в maven-репозиторий
- Нет @Application класса
- Бины можно просто создавать, а можно с условиями
Автоконфигурирование бинов
Чтобы это все работало, нужно создать класс(ы) с аннотацией @Configuration и прописать его полное имя в resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
: тогда Spring Boot найдет его и использует для автоконфигурирования нашей библиотеки.
Приложение может отключить класс автоконфигурирования через аннотацию @EnableAutoConfiguration(exclude=)
или через свойство spring.autoconfigure.exclude
Далее: какие условия можно использовать. Условия могут быть к отдельным методам @Bean внутри класса, так и ко всему классу.
Компонент создается, если есть другой компонент
Например, если мы находим драйвера какой-нибудь БД, то конфигурируем клиентскую библиотеку для нее.
Создаем компонент, если OtherService уже существует (например, в проекте):
@Component
@ConditionalOnBean(OtherService::class)
@RegisterReflectionForBinding(SomeRequest::class, SomeResponse::class)
Так же регистрируются SomeRequest и SomeResponse для корректной сериализации (это для случая нативных сборок, без нативных сборок это не нужно, но лучше, если библиотека будет поддерживать нативные сборки).
Компонент создается, если указано в конфигурации
Например, если не сказано не создавать. Это наиболее наш случай: по умолчанию создаем, но даем возможность отключить, если от нас будут нужны, например, только классы моделей (или какое-то более сложно конфигурирование).
Есть и другие варианты условий (https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/autoconfigure/condition/package-summary.html). Самый интересный – ConditionalOnProperty, т.е. включается через конфигурацию.
Компонент создается, если такого бина еще нет
Это удобно для случаев каких-то базовых бинов. Например, мы можем создать WebClient, если его не создают в приложении. Аннотация @ConditionalOnMissingBean
.
Компонент создается, если использована аннотация
Пример аннотации:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyApiClientConfiguration.class)
public @interface EnableMyApiClient {}
По сути, это ссылка на наш конфигурационный класс, просто через аннотацию.
Мне такой способ не нравится: если уж библиотека включается в зависимости, то пусть настраивается, а если не надо в редких случаях, то отключается через @EnableAutoConfiguration(exclude=)
.