Перейти к содержимому

Как написать свою библиотеку java

  • автор:

Java-библиотека: создание, обработка, работа с файлами

Java-библиотека: создание, обработка, работа с файлами

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

Ключевые принципы дизайна

Повторное использование кода является одним из священных Граалей компьютерного программирования. Написание кода, который можно легко повторно применять, требует обладания сложных навыков, но, безусловно, его может освоить каждый желающий. Язык Java может помочь создать хорошую многоразовую платформу. Для этого разработчику необходимо иметь базовые знания по программированию, включая умение создавать, компилировать и выполнять простые программы из командной строки.

Несколько ключевых принципов

Несколько ключевых принципов хорошего дизайна Java-библиотеки:

  1. Она четко должна передавать свою внутреннюю структуру таким образом, что пользователь знает, как ее использовать, даже если не понимает, как она работает изнутри.
  2. Инкапсуляция — ужесточение и уточнение интерфейса кода, что делает его более надежным и понятным.
  3. Расширяемость — создание Java-библиотеки, которая хорошо выполняет свою задачу и предоставляет возможность заполнить недостающие части для удовлетворения потребностей конкретного разработчика.
  4. Наследование — обеспечивает способ настройки кода без необходимости знать все детали кода, который настраивается.
  5. Предоставление способа получения отладочной информации из кода — помогает пользователям находить собственные ошибки.

Границы функциональности: вход и выход

Проблема в том, что не все дизайнеры Java-библиотек думают о своих пользователях, о том, как API будет применяться на практике и как будет выглядеть и тестироваться код.

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

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

Также разработчики всегда должны предлагать пользователям более одного конструктора. Клиенты должны работать со String и Integer, чтобы они могли передавать ByteArrayInputStream при тестировании модулей.

Например, несколько способов создания точки входа API Github с помощью jcabi-github:

  • Github noauth = new RtGithub();
  • Github basicauth = new RtGithub(«username», «password»);
  • Github oauth = new RtGithub(«token»).

В качестве второго примера, как работать с charles, Java-библиотекой профессионала для веб-сканирования, можно прописать следующее.

Сканирование библиотеки

По умолчанию шаблоны не должны игнорироваться. Переменная indexPage — это URL-адрес, с которого должен начаться обход контента, драйвер — это используемый браузер. По умолчанию он ничего не может сделать, поскольку неизвестно, какой браузер установлен на работающей машине. Выход нужен в основном для Library, которая общается с внешним миром. Здесь нужно ответить на вопрос, как он будет обрабатываться.

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

Для этого нужно прописать что-то вроде этого:

WebCrawl graph = new GraphCrawl(. );List pages = graph.crawl();

Интерфейс и третьи лица

Разработчик всегда должен использовать интерфейсы, а пользователь обязан взаимодействовать с кодом только через строгие контракты. Например, в библиотеке jcabi-github класс RtGithub si единственный, который он видит.

Интерфейс и третьи лица

Приведенный выше фрагмент создает заявку в репозитории eugenp/tutorials. Экземпляры Repo и Issue применяются, но фактические типы никогда не раскрываются. Сценарий, приведенный выше, может быть разрешен, но тогда разработанный алгоритм будет загрязнен большим количеством стандартного кода.

Интерфейсы также обеспечивают простоту расширения и обратной совместимости. С одной стороны разработчики обязаны соблюдать уже выпущенные контракты, а с другой — пользователь расширяет предлагаемые интерфейсы: он может их украшать или писать альтернативные реализации.

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

Проект должен быть максимально прозрачным. Лучший пример — использовать SLF4J с API для ведения журнала. Не стоит применять log4j напрямую, возможно, разработчик захочет применить другие средства ведения журнала.

Изучение программирования — сложная задача для людей, отдаленных от.

Подключение Java-библиотек документов, которые переходят через проект транзитивно, выполняют, чтобы не включались опасные зависимости, такие как xalan или xml-apis.

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

Библиотека модульного тестирования

Библиотека модульного тестирования

У разработчиков очень распространены модули логов, они нужны в каждом проекте. Особенно важны для серверных приложений, потому что журналы размещаются только там, где можно видеть, что происходит в них. Несмотря на то что JDK поставляется с собственной библиотекой журналирования, существуют лучшие альтернативы, например, Log4j, SLF4j и LogBack. Разработчик Java должен быть знаком с плюсами и минусами библиотек журналов и знать, почему использование SLF4j лучше, чем простой Log4j.

В современном мире веб-сервисов и IoT JSON стал протоколом перехода для передачи информации от клиента к серверу. Он заменил XML, как наиболее предпочтительный способ передачи информации независимо от платформы. К сожалению, JDK не имеет Library JSON. Но есть много хороших сторонних модулей, которые позволяют анализировать и создавать сообщения, например, Джексон. Веб-разработчик должен быть знаком хотя бы с одной из этих библиотек.

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

Разработчикам Java доступно несколько хороших сторонних библиотек общего назначения, таких как Apache Commons и Google Guava. Они упрощают множество задач. Как справедливо сказал Джошуа Блох в «Эффективной Яве», нет смысла заново изобретать колесо, лучше использовать проверенные и протестированные модули, вместо того чтобы писать свои собственные подпрограммы. Разработчику Java полезно ознакомиться с Google Guava и библиотекой Apache Commons.

Платформа с HTTP-поддержкой

Многим разработчикам не нравится в JDK отсутствие поддержки HTTP. Можно установить HTTP-соединение, используя классы в java.net пакет сторонних библиотек с открытым исходным кодом, такие как Apache HttpClient и HttpCore.

Хотя в JDK 9 реализована поддержка HTTP 2.0 и улучшена поддержка HTTP, специалисты рекомендуют разработчикам ознакомиться с популярными клиентскими библиотеками HTTP, включая HttpClient и HttpCore.

Существуют библиотеки разбора

Существуют библиотеки разбора XML, включая Xerces, JAXB, JAXP, Dom4j и Xstream. Xerces2 — это новое поколение высокопроизводительных, полностью совместимых анализаторов XML в семействе Apache Xerces. Новая версия Xerces представляет собственный интерфейс Xerces (XNI), полную платформу для создания компонентов и конфигураций синтаксического анализатора, которая является чрезвычайно модульной и простой в программировании.

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

Парсер Apache Xerces2 является эталонной реализацией XNI, но другие компоненты конфигурации могут быть написаны с использованием собственного интерфейса Xerces. Dom4j — еще одна гибкая структура XML для создания библиотеки Java.

Программы реального мира должны взаимодействовать с Microsoft Office в той или иной форме. Многие из них должны предоставлять функциональные возможности для экспорта данных в Excel, и если это нужно сделать из Java-приложения, потребуется API-интерфейс Apache POI. Это очень богатая библиотека, которая позволяет читать и записывать файлы XLS из Java-программы.

Если программист пишет каркас, который генерирует код или взаимодействует с байт-кодами, то ему нужна библиотека байт-кодов. Она позволяет читать и изменять байт-код, сгенерированный приложением. Некоторые из популярных Library байт — это javassist и Cglib Nodep. Помощник Java-программирования делает манипулирование байт-кодом очень простым. ASM — еще один полезный модуль для его редактирования.

Повсеместный формат PDF

Повсеместный формат PDF

Подобно Microsoft Excel, библиотеки PDF являются еще одним повсеместным форматом. Если необходимо поддерживать функциональность PDF в приложении, например, экспортировать данные в файлы PDF, то можно применить модули iText и Apache FOP. Оба предоставляют полезную функциональность, связанную с PDF, но iText лучше.

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

Несмотря на то, что JDK обладает богатой коллекцией, есть сторонние модули, предоставляющие больше возможностей, такие как коллекции Apache Commons, Goldman Sachs, Google и Trove. Library Trove особенно полезна, поскольку обеспечивает высокоскоростные регулярные и примитивные коллекции для Java.

Javax.mail и Apache Commons Email предоставляют API для отправки электронной почты с Java. Он построен поверх API JavaMail, который создан для упрощения работ.

Криптографический пакет кодеков

Криптографический пакет кодеков

Apache Commons содержит простой кодер и декодеры для различных форматов, таких как Base64 и Hexadecimal. В дополнение к этим широко используемым кодерам и декодерам, он поддерживает набор утилит фонетического кодирования. Библиотеки встроенных баз данных SQL, такие как H2, которые можно встроить в Java-приложении, отлично подходят для тестирования SQL-скриптов и запуска модульных тестов, которым нужна база данных. Однако H2 — не единственная БД, также существует на выбор Apache Derby и HSQL.

Библиотеки устранения неполадок JDBC призваны облегчить отладку, например, P6spy. Это модуль, который позволяет легко перехватывать данные БД и регистрировать их без изменения кода программы. Можно использовать их для регистрации запросов SQL и их времени. Например, если применяют PreparedStatment и CallableStatement в коде, эти Library могут регистрировать точный вызов с параметрами и с определением времени необходимого для выполнения.

Буферы протокола Google — это способ кодирования структурированных данных в эффективном, но расширяемом формате. Это более богатая и лучшая альтернатива сериализации Java. Некоторые из полезных сетевых библиотек — Netty и Apache MINA. Если разработчик пишет программу, в которой нужно выполнить низкоуровневую сетевую задачу, то он использует эти модули.

Java Json библиотеки

Есть надежные библиотеки JSON для сериализации/десериализации. Можно найти детали о Джексоне в репозитории GitHub.

Java Json библиотеки

Она предоставлена Дугом Крокфордом и была введена в платформу JavaEE 7, как часть инициативы 353 «Запрос спецификации Java» (JSR) и является автономной реализацией, которую можно интегрировать с SE, начиная с версии 8.

Девятая модификация обеспечивает собственную поддержку JSON в рамках инициативы Enhancement Proposal (JEP) 198.

  1. Широко используется, особенно сообществом Spring.
  2. Обеспечивает отличную функциональность.
  3. Хорошо работает в течение длительного времени.
  4. Хорошо поддерживается с активным сообществом развития.
  5. Имеет хорошую документацию.
  6. Поддерживает фокус, придерживаясь одной библиотеки Java/JSON.

Вот самые важные классы и методы Джексона, которые сериализуют/десериализуют в JSON:

  1. ObjectMapper — преобразует конструкции между Java и JSON.
  2. ObjectMapper.writeValue — преобразует тип данных Java в JSON и в этом случае выводит в Writer.
  3. ObjectMapper.readValue — преобразует JSON в тип данных Java.

Запускают одиночный модульный тест из командной строки следующим образом:

+gradle test —tests org.jsonatwork.ch4.BasicJsonTypesTest+

Одиночный модульный тест

Обучающие книги Horstmann

Обучающие книги Horstmann

Многообразие библиотек заставляет программистов находить исчерпывающее, но сжатое руководство по всем новым функциям Java SE 9. В этом им помогают книги Хорстманна, особенно интересно начинающим пользователям десятое издание книги «Java-библиотека профессионала».

Кей С. Хорстманн — профессор компьютерных наук государственного университета Сан-Хосе. Он является опытным профессиональным программистом и консультантом для крупных корпораций, университетов и организаций по Java, C ++, Windows и интернет-программирования. Хорстманн является автором многих успешных профессиональных и академических книг, включая «Big C ++», «C ++ для всех», «Big Java», «Объектно-ориентированный дизайн и шаблоны».

Книга Корнелла Хорстманн «Java-библиотека профессионала» охватывает все важные аспекты языка, которые должен знать современный разработчик, включая модульность, а также мощные лямбда-выражения, представленные в 8-й версии. В них изложены основы программирования с использованием Java в более доступном и менее строгом подходе.

Книга предоставляет более удобный интенсивный графический дизайн, который передает сложные концепции просто и эффективно. Она отличается от других книг на рынке тем, что фокусируется на таких темах, как циклы и графические интерфейсы, при этом избегая ориентации на объектную ориентацию. Акцент делается на простые программы, которые используют предварительно созданные структуры данных и алгоритмы.

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

Способы улучшение производительности Library

Способы улучшение производительности

С JSON легко работать и он стал стандартным форматом данных практически для всего. В Stackify широко применяют JSON для API-интерфейсов REST, сериализации сообщений в очереди и многого другого.

Некоторые общие советы по производительности JSON:

  1. Использовать несколько библиотек JSON для оптимальной производительности и функций.
  2. Использовать потоки, когда это возможно.
  3. Сжимать пользовательский JSON. Поскольку это просто текст, можно рассчитывать на сжатие до 90%. Поэтому применяют «zip» везде, где это возможно.
  4. Избегать анализа, если это не нужно.
  5. Выполнять сериализацию/десериализацию больших и меньших объектов JSON. В некоторых случаях, когда нужно получить массив больших объектов, его разбивают на более мелкие части.
  6. Настроить анализатор JSON Web API.
  7. Не сериализовать все поля, нулевые значения или значения по умолчанию.
  8. Использовать более короткие имена полей.
  9. Ручная сериализация или разбор могут быть быстрее.

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

Java: работа с файлами - запись, чтение, удаление

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

Первая программа на Java - Hello World

Как написать свою первую программу на Java. Пример исходного кода простейшего Hello World на Java. Описание методов и класса.

Какие наиболее лучшие книги по Java. Лучший самоучитель по Java для начинающих

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

Язык программирования Java: с чего начать изучение. Где применяется Java

«Джава» — это язык программирования широкого назначения. С его помощью разрабатываются программы на смартфоны и планшеты, компьютеры. Особенность таких приложений — кроссплатформенность. Существует несколько семей технологии Java: SE, EE, .

Java: исключения и их обработка

Как работают исключения в Java. Каким образом происходит их отлов и обработка, наиболее часто встречающиеся варианты и описание к ним.

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

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

Отслеживать
Anton Sorokin
задан 4 авг 2017 в 5:49
Anton Sorokin Anton Sorokin
7,008 6 6 золотых знаков 37 37 серебряных знаков 65 65 бронзовых знаков

1 ответ 1

Сортировка: Сброс на вариант по умолчанию

Все эти блоки кода надо положить в один проект.
Затем создать jar-файл.
После этого импортируем этот файл в [другой] проект.

Отслеживать
ответ дан 4 авг 2017 в 7:30
Anton Sorokin Anton Sorokin
7,008 6 6 золотых знаков 37 37 серебряных знаков 65 65 бронзовых знаков

  • java
  • netbeans
  • библиотеки
  • jar
    Важное на Мете
Похожие

Подписаться на ленту

Лента вопроса

Для подписки на ленту скопируйте и вставьте эту ссылку в вашу программу для чтения RSS.

Дизайн сайта / логотип © 2024 Stack Exchange Inc; пользовательские материалы лицензированы в соответствии с CC BY-SA . rev 2024.1.3.2953

Нажимая «Принять все файлы cookie» вы соглашаетесь, что Stack Exchange может хранить файлы cookie на вашем устройстве и раскрывать информацию в соответствии с нашей Политикой в отношении файлов cookie.

Как создать свою библиотеку java

Чтобы создать свою библиотеку Java с использованием Gradle , следуйте этим шагам:

  • Установите Gradle на свой компьютер, если еще не установлен.
  • Создайте новый проект Gradle . Для этого запустите команду в командной строке или терминале в нужной вам директории:

Это создаст новый проект с заданной структурой каталогов и файлов для библиотеки Java

  • Откройте файл build.gradle в своем любимом редакторе и добавьте зависимости, если это необходимо. Вы можете добавлять зависимости на другие библиотеки Java , которые вы используете в своей библиотеке, например:
dependencies  implementation 'com.google.guava:guava:30.0-jre' testImplementation 'junit:junit:4.13.2' > 

Это добавляет зависимости на библиотеки Google Guava и JUnit для реализации и тестирования вашей библиотеки соответственно.

  • Создайте классы, интерфейсы и другие ресурсы для вашей библиотеки в директории src/main/java . Например, вы можете создать класс MyLibrary в пакете com.example.mylibrary следующим образом:
package com.example.mylibrary; public class MyLibrary  public static String getMessage()  return "Hello, World!"; > > 
  • Соберите свою библиотеку, запустив команду gradle build в командной строке или терминале. Это создаст JAR-файл вашей библиотеки в директории build/libs
  • Используйте свою библиотеку в других проектах Java , добавив зависимость на JAR-файл вашей библиотеки в файл build.gradle проекта, например:
dependencies  implementation files('libs/mylibrary.jar') > 

Это добавляет зависимость на JAR-файл mylibrary.jar , который вы создали в предыдущем шаге.

Вот пример кода для использования библиотеки в другом проекте Java :

import com.example.mylibrary.MyLibrary; public class Main  public static void main(String[] args)  String message = MyLibrary.getMessage(); System.out.println(message); // => "Hello, World!" > > 

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

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

@Service public interface GoogleSearchApi < /** * @return http status code for Google main page */ @Uri("https://www.google.com") int mainPageStatus(); >

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

@SpringBootApplication public class App implements CommandLineRunner < private static final Logger LOG = LoggerFactory.getLogger(App.class); private final GoogleSearchApi api; public App(GoogleSearchApi api) < this.api = api; >@Override public void run(String. args) < LOG.info("Main page status: " + api.mainPageStatus()); >public static void main(String[] args) < SpringApplication.run(App.class, args); >>

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

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

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

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

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

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

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

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

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

/** * Some Google requests */ @Service public interface GoogleSearchApi < /** * @return http status code for Google main page */ @Uri("https://www.google.com") int mainPageStatus(); /** * @return request object for Google main page */ @Uri("https://www.google.com") HttpGet mainPageRequest(); /** * @param query search query * @return result of search request execution */ @Uri("https://www.google.com/search?q=") CloseableHttpResponse searchSomething(String query); /** * @param query doodle search query * @param language doodle search language * @return http status code for doodle search result */ @Uri("https://www.google.com/doodles/?q=&hl=") int searchDoodleStatus(String query, String language); >

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

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

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

@SpringBootApplication public class App implements CommandLineRunner < private static final Logger LOG = LoggerFactory.getLogger(App.class); private final GoogleSearchApi api; public App(GoogleSearchApi api) < this.api = api; >@Override @SneakyThrows public void run(String. args) < LOG.info("Main page status: " + api.mainPageStatus()); LOG.info("Main page request: " + api.mainPageRequest()); LOG.info("Doodle search status: " + api.searchDoodleStatus("tesla", "en")); try (CloseableHttpResponse response = api.searchSomething("qweqwe")) < LOG.info("Search result " + response); >> public static void main(String[] args) < SpringApplication.run(App.class, args); >>

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

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

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

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

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

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

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

DynamicProxyBeanDefinitionRegistryPostProcessor

@Component public class DynamicProxyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor < //корневые пакеты, которые мы будем сканировать private static final String[] SCAN_PACKAGES = ; private final InterfaceScanner classpathScanner; public DynamicProxyBeanDefinitionRegistryPostProcessor() < classpathScanner = new InterfaceScanner(); //настраиваем фильтры для сканера. В данном примере достаточно аннотации Service classpathScanner.addIncludeFilter(new AnnotationTypeFilter(Service.class)); >@Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException < for (String basePackage : SCAN_PACKAGES) < createRepositoryProxies(basePackage, registry); >> @SneakyThrows private void createRepositoryProxies(String basePackage, BeanDefinitionRegistry registry) < for (BeanDefinition beanDefinition : classpathScanner.findCandidateComponents(basePackage)) < Classclazz = Class.forName(beanDefinition.getBeanClassName()); //для каждого найденного класса создаём кастомный bean definition BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz); builder.addConstructorArgValue(clazz); //указываем, какой метод будет использоваться для создания инстансов наших интерфейсов builder.setFactoryMethodOnBean( "createDynamicProxyBean", DynamicProxyBeanFactory.DYNAMIC_PROXY_BEAN_FACTORY ); registry.registerBeanDefinition(ClassUtils.getShortNameAsProperty(clazz), builder.getBeanDefinition()); > > @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException < >private static class InterfaceScanner extends ClassPathScanningCandidateComponentProvider < InterfaceScanner() < super(false); >@Override protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) < return beanDefinition.getMetadata().isInterface(); >> >

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

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

@Component(DYNAMIC_PROXY_BEAN_FACTORY) public class DynamicProxyBeanFactory < public static final String DYNAMIC_PROXY_BEAN_FACTORY = "repositoryProxyBeanFactory"; private final DynamicProxyInvocationHandlerDispatcher proxy; public DynamicProxyBeanFactory(DynamicProxyInvocationHandlerDispatcher proxy) < this.proxy = proxy; >@SuppressWarnings("unused") public T createDynamicProxyBean(Class beanClass) < //noinspection unchecked return (T) Proxy.newProxyInstance(beanClass.getClassLoader(), new Class[], proxy); > >

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

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

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

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

public interface HandlerMatcher < /** * @return if handler is able to handle given method, othervise */ boolean canHandle(Method method); > public interface ProxyInvocationHandler extends InvocationHandler, HandlerMatcher

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

DynamicProxyInvocationHandlerDispatcher

package com.bachkovsky.dynproxy.lib.proxy; import lombok.SneakyThrows; import org.springframework.stereotype.Component; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.List; /** * Top level dynamic proxy invocation handler, which finds correct implementation based and uses it for method * invocation */ @Component public class DynamicProxyInvocationHandlerDispatcher implements InvocationHandler < private final ListproxyHandlers; /** * @param proxyHandlers all dynamic proxy handlers found in app context */ public DynamicProxyInvocationHandlerDispatcher(List proxyHandlers) < this.proxyHandlers = proxyHandlers; >@Override public Object invoke(Object proxy, Method method, Object[] args) < switch (method.getName()) < // three Object class methods don't have default implementation after creation with Proxy::newProxyInstance case "hashCode": return System.identityHashCode(proxy); case "toString": return proxy.getClass() + "@" + System.identityHashCode(proxy); case "equals": return proxy == args[0]; default: return doInvoke(proxy, method, args); >> @SneakyThrows private Object doInvoke(Object proxy, Method method, Object[] args) < return findHandler(method).invoke(proxy, method, args); >private ProxyInvocationHandler findHandler(Method method) < return proxyHandlers.stream() .filter(h ->h.canHandle(method)) .findAny() .orElseThrow(() -> new IllegalStateException("No handler was found for method: " + method)); > >

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

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

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

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

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

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

public abstract class HttpInvocationHandler implements ProxyInvocationHandler < final HttpClient client; private final UriHandler uriHandler; HttpInvocationHandler(HttpClient client, UriHandler uriHandler) < this.client = client; this.uriHandler = uriHandler; >@Override public boolean canHandle(Method method) < return uriHandler.canHandle(method); >final String getUri(Method method, Object[] args) < return uriHandler.getUriString(method, args); >>

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

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

@Component public class HttpCodeInvocationHandler extends HttpInvocationHandler < public HttpCodeInvocationHandler(HttpClient client, UriHandler uriHandler) < super(client, uriHandler); >@Override @SneakyThrows public Integer invoke(Object proxy, Method method, Object[] args) < try (CloseableHttpResponse resp = client.execute(new HttpGet(getUri(method, args)))) < return resp.getStatusLine().getStatusCode(); >> @Override public boolean canHandle(Method method) < return super.canHandle(method) && method.getReturnType().equals(int.class); >>

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

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

Заключение

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

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

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

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

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

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