Java как создать библиотеку
Перейти к содержимому

Java как создать библиотеку

Создание библиотеки в стиле Spring Data Repository своими руками при помощи Dynamic Proxy и Spring IoC

А что если бы можно было создать интерфейс, например, такой:

А затем просто внедрять его и вызывать его методы:

Такое вполне возможно реализовать (и не очень то и сложно). Дальше я покажу, как и зачем это делать.

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

Свойства, которых хотелось добиться от такого решения:

  • декларативное описание желаемого действия
  • минимальное необходимое количество кода
  • интеграция с используемым фреймворком внедрения зависимостей (в нашем случае Spring)

Подобное реализовано в библиотеках Spring Data Repository и Retrofit. В них пользователь описывает желаемое взаимодействие в виде java интерфейса, дополненного аннотациями. Пользователю не нужно самому писать реализацию — её генерирует библиотека в рантайме на основе сигнатур методов, аннотаций и типов.

Когда я изучал тему, у меня возникало много вопросов, ответы на которые были разбросаны по всему интернету. Мне бы в тот момент не помешала статья, подобная этой. Потому здесь я постарался собрать всю информацию и мой опыт в одном месте.

В данном посте я покажу, как можно реализовать данную идею, на примере обёртки для http-клиента. Пример игрушечный, предназначенный не для реального использования, а для демонстрации подхода. Исходники проекта можно изучить на bitbucket.

Как это выглядит для пользователя

Пользователь описывает необходимый ему сервис в виде интерфейса. Например, для выполнения http запросов в google:

Что в конечном итоге будет делать реализация данного интерфейса, определяется по сигнатуре. Если возвращаемый тип int — будет выполняться http запрос и возвращаться статус код результата. Если возвращаемый тип CloseableHttpResponse, то возвращаться будет ответ на запрос целиком, и так далее. Куда будет делаться запрос — будем брать из аннотации Uri, подставляя в её содержимое вместо плейсхолдеров одноимённые переданные значения.

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

Когда пользователь хочет использовать данный интерфейс, он внедряет его в свой код используя Spring:

Интеграция со Spring нужна была в моём рабочем проекте, но она, разумеется, не единственно возможная. Если вы не используете внедрение зависимостей, получение реализации можно сделать, например, через static factory method. Но я в данной статье буду рассматривать именно Spring.

Данный подход очень удобен: достаточно пометить свой интерфейс как компонент Spring (аннотация Service в данном случае), и он готов к внедрению и использованию.

Как заставить Spring поддерживать эту магию

Типичное Spring приложение сканирует classpath на старте и ищет все компоненты, помеченные специальными аннотациями. Для них оно регистрирует BeanDefinition’ы — рецепты, по которым будут создаваться данные компоненты. Но если в случае конкретных классов Spring знает, как их создать, какие вызвать конструкторы и что в них передать, то для абстрактных классов и интерфейсов у него такой информации нет. Поэтому для нашего GoogleSearchApi Spring не будет создавать BeanDefinition. В этом ему потребуется помощь от нас.

Для того, чтобы допилить логику обработку BeanDefinition’ов, в спринге существует интерфейс BeanDefinitionRegistryPostProcessor. С помощью него мы можем добавить в BeanDefinitionRegistry какие нам угодно определения бинов.

К сожалению, я не нашёл способа встроиться в логику Spring сканирования classpath, чтобы обработать и обычные бины и наши интерфейсы за один проход. Поэтому я создал и использовал наследника класса ClassPathScanningCandidateComponentProvider для того, чтобы найти все интерфейсы, помеченные аннотацией Service:

Полный код сканирования пакетов и регистрации BeanDefinition’ов:

Готово! На старте приложения Spring выполнит данный код и зарегистрирует все необходимые интерфейсы, как бины.

Создание реализации найденных бинов делегируется отдельному компоненту DynamicProxyBeanFactory:

Для создания реализации используется старый добрый механизм Dynamic Proxy. Реализация создаётся на лету при помощи метода Proxy.newProxyInstance. О нём уже много написано статей, поэтому останавливаться здесь подробно я не буду.

Поиск нужного обработчика и обработка вызова

Как можно увидеть, DynamicProxyBeanFactory перенаправляет обработку метода в DynamicProxyInvocationHandlerDispatcher. Так как у нас существует потенциально много реализаций обработчиков (на каждую аннотацию, на каждый возвращаемый тип, и т.д.), то логично завести какое-то центральное место их хранения и поиска.

Для того, чтобы определять, подходит ли обработчик для обработки вызванного метода, я расширил стандартный интерфейс InvocationHandler новым методом

В результате получился интерфейс ProxyInvocationHandler, реализации которого и будут нашими обработчиками. Также реализации обработчиков будут помечены как Component, чтобы Spring мог соберать их для нас в один большой список внутри DynamicProxyInvocationHandlerDispatcher:

В методе findHandler мы проходимся по всем обработчикам и возвращем первый попавшийся, способный обработать переданный метод. Данный механизм поиска может быть не очень эффективен, когда реализаций обработчиков станет много. Возможно, тогда нужно будет задуматься о какой-то более подходящей структуре для их хранения, чем список.

Реализация обработчиков

В задачи обработчиков входит считывание информации о вызванном методе интерфейса и обработка самого вызова.

Что должен сделать обработчик в данном случае:

  1. Считать аннотацию Uri, достать её содержимое
  2. Заменить в строке Uri плейсхолдеры на реальные значения
  3. Считать возвращаемый тип метода
  4. Если возвращаемый тип подходит, выполнить обработку метода и вернуть результат.

Первые три пункта нужны для всех возвращаемых типов, поэтому общий код я вынес в абстрактный суперкласс
HttpInvocationHandler:

Во вспомогательном классе UriHandler реализована работа с аннотацией Uri: считывание значения, замена плейсхолдеров. Код его я тут приводить не буду, т.к. он довольно утилитный.
Но стоит отметить, что для считывания имён параметров из сигнатуры метода java, нужно при компиляции добавить опцию «-parameters».
HttpClient — обёртка над апачевским CloseableHttpClient, является бэкэндом для данной библиотеки.

В качестве примера конкретного обработчика приведу обработчик, возвращающий статус код ответа:

Остальные обработчики сделаны аналогично. Добавление новых обработчиков выполняется просто и не требует модификации существующего кода — просто создаём новый обработчик и помечаем его как компонент Spring.

Вот и всё. Код написан и готов к работе.

Заключение

Чем больше я думаю о подобном дизайне, тем больше вижу в нём недостатков. Слабые стороны, которые я вижу:

  • Type Safety, которой нет. Неправильно поставил аннотацию — до встречи с RuntimeException. Использовал неправильную комбинацию возвращаемого типа и аннотации — то же самое.
  • Слабая поддержка от IDE. Отсутствие автодополнения. Пользователь не можжет посмотреть, какие действия доступны ему в его ситуации (как если бы он поставил «точку» после объекта и увидел список доступных методов)
  • Мало возможностей для применения. Мне приходят на ум уже упомянутые http клиент, и клиент к базе данных. Но для чего ещё это можно применить?

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

А что вы думаете про такой подход? Стоит ли оно стараний? Какие вы видите в данном подходе проблемы? Пока я его всё ещё стараюсь осмыслить, пока он обкатывается в нашем продакшене, хотелось бы услышать что думают о нём другие люди. Надеюсь, данный материал был полезен кому-то.

Как создать библиотеку Java: С нуля до Maven Central

Как создать библиотеку Java: С нуля до введения в Maven Central Если ты… С тегами java, maven, библиотека, учебник.

  • Автор записи

Вступление

Если вам обычно нужно переписать или скопировать похожий код между разными проектами, возможно, пришло время прекратить его копирование и создать библиотеку.

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

Чтобы использовать вашу библиотеку в разных проектах, вы должны опубликовать ее в репозитории, например Центральное хранилище Maven .

Итак, давайте пройдем весь процесс и опубликуем библиотеку для заполнения строк. Мы начнем с создания нашего проекта с нуля.

Если вы хотите пропустить создание проекта:

    Вы можете использовать свой собственный проект и перейти к Подготовка pom.xml для развертывания на Maven Central ; или

Загрузите мой проект с GitHub и перейдите к Запрашиваю доступ к Maven Central .

⚠️ ЕСЛИ ВЫ ИСПОЛЬЗУЕТЕ МОЙ ПРОЕКТ, НЕ ЗАБУДЬТЕ ИЗМЕНИТЬ ИДЕНТИФИКАТОР ЕГО ГРУППЫ.

Создание проекта

Выполните следующую команду на своем терминале:

Используйте свой собственный идентификатор группы в команде. Если вы используете библиотеку com.the great.api.demo вы не сможете опубликовать в Maven Central.

Если вы не уверены, какой идентификатор группы использовать, посмотрите эту статью .

Эта команда создаст проект со следующими pom.xml :

Давайте изменим его, чтобы использовать Java 11 вместо 1.7.

А затем мы создаем файл ЛИЦЕНЗИЯ . Я буду использовать лицензию Apache 2, но вы можете использовать любую лицензию, какую захотите. Чтобы использовать лицензию Apache 2, вам необходимо скопировать содержимое из http://www.apache.org/licenses/LICENSE-2.0.txt и вставьте его в свой ЛИЦЕНЗИОННЫЙ файл.

Реализация библиотеки

Теперь давайте создадим интерфейс Заполнитель строк в упаковке com.великий api.демонстрационная библиотека.заполнитель строк . Это будет интерфейс, который клиенты будут использовать для заполнения строк.

Теперь давайте создадим реализацию интерфейса String Padder .

Обратите внимание, что класс является закрытым для пакетов, поэтому клиенты не могут использовать его в своем коде.

Теперь мы собираемся создать фабрику для клиентов, чтобы создавать экземпляры String Padder .

Создание Тестов

Давайте заменим JUnit 4 на JUnit 5 и добавим зависимость AssertJ в ваш pom.xml .

Теперь мы готовы провести наши тесты.

Если мы запустим mvn verify , мы увидим вывод, подобный следующему:

Наша реализация готова. Следующим шагом будет развертывание нашей библиотеки в Maven Central.

Подготовка pom.xml для развертывания в Maven Central

Есть некоторые требования, которые мы должны выполнить, чтобы загрузить нашу библиотеку в Maven Central. Мы можем найти эти требования на https://central.sonatype.org/pages/requirements.html .

Первое, что мы должны изменить в вашем pom.xml заключается в определении версии без МОМЕНТАЛЬНОГО СНИМКА для нашей библиотеки. Для этого нам просто нужно удалить суффикс -SNAPSHOT . Давайте определим нашу версию как 0.0.1 .

Затем мы должны добавить описание и URL-адрес в наш проект. В моем случае URL-адрес должен быть http://thegreatapi.com . Ваш URL-адрес должен отличаться от моего, потому что вы должны владеть доменом. Вы также можете использовать URL-адрес GitHub, если хотите.

После этого мы добавляем информацию о лицензии. Обратите внимание, что я использую лицензию Apache 2. Используйте ту же лицензию, которую вы определили в своем файле ЛИЦЕНЗИЯ .

Следующий шаг – добавить информацию о разработчиках.

А затем добавьте информацию об управлении исходным кодом (SCM). Следующая информация предназначена для моего проекта. Замените его информацией о вашем проекте.

Maven Central требует, чтобы вы отправили Javadoc и исходный код. Поэтому вы должны создать его при создании своих артефактов. Вы также должны подписать артефакты, которые отправляете в Maven Central.

Поскольку нам нужно делать это только при развертывании в Maven Central, было бы неплохо создать профиль для этого.

Вам также необходимо установить клиент GPG и поместить его в путь командной строки. Следуйте инструкциям на https://central.sonatype.org/pages/working-with-pgp-signatures.html для установки клиента GPG.

Следующий шаг – добавить управление распространением в ваш pom.xml .

Кроме того, добавьте nexus-staging-maven-плагин.

Ваша библиотека готова к публикации в Maven Central.

Запрашиваю доступ к Maven Central

Использование хостинга репозитория OSS (OSSRH), предоставляемого Sonatype для любого проекта с открытым исходным кодом, является самым простым способом публикации вашего проекта.

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

Официально они просят у вас 2 рабочих дня для завершения процесса, но обычно это занимает один или два часа.

Используйте свой собственный идентификатор группы. Если вы используете библиотеку com.the great.api.demo вы не сможете опубликовать в Maven Central.

Они спросят, являетесь ли вы владельцем домена, указанного в groupId, и если да, вам придется подтвердить право собственности. На моем билете они прокомментировали:

В моем случае я добавил текстовую запись в свой DNS. Когда я это сделал, я прокомментировал билет, и они установили для билета значение “Решено”.

Освобождение библиотеки

После подтверждения права собственности на домен вы готовы загрузить свои артефакты. Для этого вы сначала выпускаете его на стадию.

выпуск mvn clean deploy-P для чистого развертывания

На данный момент ваши артефакты хранятся в частном хранилище, поэтому вы можете проверить их перед выпуском. Так что войдите в https://s01.oss.sonatype.org/ используя ваши учетные данные JIRA.

В меню слева нажмите “Промежуточные хранилища”, и вы увидите свою библиотеку.

Если с вашей библиотекой все в порядке, вы можете закрыть ее, в противном случае вы можете удалить ее.

Когда вы закроете его, в нижней части вы можете проверить, успешно ли он был закрыт, нажав на вкладку “Активность”. Вы должны увидеть что-то похожее на это:

Если репозиторий был успешно закрыт, теперь вы можете продвинуть его до выпуска.

Выполните следующую команду, используя свой идентификатор хранилища. На изображении выше это com отличная демонстрационная библиотека api-1005 . Не забудьте заменить его своим собственным идентификатором .

mvn nexus-постановка: выпуск – 1005

Используйте свой собственный идентификатор группы. Если вы используете библиотеку com.the great.api.demo вы не сможете опубликовать в Maven Central.

Вы должны увидеть результат, похожий на:

Теперь ваша библиотека опубликована. В этом самом первом развертывании вы должны прокомментировать билет JIRA, чтобы они могли активировать синхронизацию с Maven Central.

После того, как Sonatype активирует центральную синхронизацию Maven для вашей библиотеки, когда вы успешно выпустите новые компоненты, они будут опубликованы в Central https://repo1.maven.org/maven2/ , как правило, в течение 10 минут, хотя обновления https://search.maven.org может занять до двух часов.

Начните Свою Библиотеку Прямо Сейчас

Теперь вы готовы создать свою собственную библиотеку. Это будет здорово для вас и сообщества Java.

Если вы все еще не чувствуете себя готовым, что мешает вам сделать следующий шаг? Прокомментируйте здесь или отправьте мне сообщение в мои социальные сети . Я буду рад вам помочь.

Как создать библиотеку?

У меня есть три «куска» кода — интерфейс, абстрактный класс и класс. Класс наследует абстрактный класс, а абстрактный класс наследует интерфейс. Класс переопределяет и реализует все методы из интерфейса и Абстрактного Класса. Я хочу сделать из них библиотеку, а точнее пока только jar файл, чтобы импортировать их в свой код. Нужно ли мне делать их в разных проектах, но в одной раскладке(package)? Если да, то как мне их из разных проектов запилить в один jar-файл?

Или вообще можно не писать интерфейс и абстрактный класс, а оставить просто класс?

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *