Сейчас делаю генератор 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=).