Комментарии 49
- По имени типа сразу понятно, что это интерфейс
- Если у интерфейса есть реализация, то её можно назвать просто Service, а не придумывать всякие ServiceImpl и т.д.
Также это стандартная схема именования интерфейсов в коде Eclipse.
Я считаю это очень хорошей практикой, потому что:
Потому что писали когда-то на C#?
По имени типа сразу понятно, что это интерфейс
И что это даёт?
java.util.Map, java.util.List и java.util.Collection интерфейсы, там нет никаких IMap, IList, ICollection, читаемость от этого не хуже, при этом понимание, что это, есть. Создаётся впечатление, что здесь обозначаем, что это интерфейс ради того, чтобы обозначить, что тут интерфейс.
Если у интерфейса есть реализация, то её можно назвать просто Service, а не придумывать всякие ServiceImpl и т.д.
ИМХО, реализацию называть Service и/или ServiceImpl — плохая идея. Это как ArrayList обозвать ListImpl, т. е. имя класса ни о чём не говорит. А по ArrayList понятно, какая это реализация. Соответственно, реализации можно назвать, чтобы они отображали основной мотив реализации. Например, какие названия лучше выглядят:
com.app.plugin1.ServiceImpl, com.app.plugin2.ServiceImpl, com.app.plugin3.ServiceImpl
или
com.app.plugin.KafkaService, com.app.plugin2.StubService, com.app.plugin2.MQService
?
Также это стандартная схема именования интерфейсов в коде Eclipse.
Поправьте меня, пожалуйста, но я не видел, чтобы в коде OpenJDK или Spring-а было обозначение интерфейса через начальную букву «I».
Очень часто у интерфейса есть только один наследник, которому придётся придумывать новое имя, если бы префикс I не использовался. Вот какое новое имя вы бы дали Workbench или ViewSite?
Аналогично для ViewSite, почему бы не SWTViewSite или DefaultViewSite? То, что класс называется Workbench не говорит ровным счётом ничего. А SWTWorkbench подсказывает, что это реализация на базе SWT. А потом появится SwingWorkbench. И понятно в общих чертах, что там.
Нет, не одного :) Но всё же это не лишено смысла, ведь это венгерская нотация, которая появилась не на пустом месте. Вы не поймёте что за класс внутри файла, пока не откроете его, если имя файла не начинается с I
.
На тему именования хорошее обсуждение на SO: https://stackoverflow.com/questions/2814805/java-interfaces-implementation-naming-convention
del
Мы не будем использовать парсинг байткода с помощью ASM и подобных библиотек.
А нужно ли при загрузки плагинов читать байткод? Это используется в каких нибудь инструментах?
Ну я могу представить, для чего она может быть нужна. Скажем в OSGI, если вы импортируете чужой сервис, то контейнер вам подсунет на самом деле proxy класс, и если сервиса реально нет — то этот proxy кинет исключение. И прокси эти с некоторой вероятностью генерируются путем манипуляции с байткодом. Но это не точно.
В случае плагина в отдельном jar все равно все так и будет. А иначе зачем нужно все, что вы туда пакуете?
>загрузчик классов может только загружать классы и всё.
Не все. Еще он может вернуть нам список пакетов, например.
В целом, я так скажу — соглашения об именах вполне могут эту проблему решить. Ведь если у вас плагин == класслоадер, все классы в нем вполне могут называться точно так же, как в ядре. Или в другом плагине. Так что вы просто создаете класслоадер на jar, и грузите из него класс с известным именем. Я совершенно не утверждаю при этом, что это удобно или эффективно. Скорее всего — ни то, ни другое.
А практически же — достаточно метаданных в хорошо всем известном месте — и получаете широко и давно известное решение а-ля ServiceLoader. Либо ровно его же, либо что-то свое.
В случае плагина в отдельном jar все равно все так и будетПочему? ClassLoader в джаве грузит классы лениво, в момент первого обращения к ним. В джарке могут быть сотни классов и далеко не все из них понадобятся. Например, пользователь вообще не будет трогать те или иные кнопки, и тогда получается, что вы зря грузили все эти лишние классы.
Порой приходится, если описание конфигурации плагина, необходимой для его загрузки, записано, допустим, в аннотациях в некоторых классах плагина, однако при этом нужно избегать их загрузки.
Например, некая аннотация, указываются на то, какую зависимость нужно загрузить до данной или, например, просто указывающая на entry-point плагина.
Всю жизнь для подобного использовали:
java -cp "core.jar;plugins/*" Main
а для lookup-а сервисов — ServiceLoader.
Мне кажется, это еще одна попытка оправдать бесполезность и рудиментарность модульной системы в Java 9. Самый большой ее недостаток — это привязка модулей к бинарным бандлам (jar) и соответвтенно лейауту проекта. Идея classpath проще и универсальней.
а для lookup-а сервисов — ServiceLoader.Вот только вам придётся для этого прописывать все свои реализации сервисов в `META-INF/services/`. Это большой геморрой, особенно когда точек расширений у вас очень много. Спасибо, но это слишком неудобно. Использовать module-info.java гораздо проще и надёжнее.
Идея classpath проще и универсальней.Проще, но с ней вы теряете инкапсуляцию и надёжность графа зависимостей. Если у вас есть какая-нибудь ошибка вроде цикла или split-пакетов, то classpath вам ничего об этом не скажет.
Вот только вам придётся для этого прописывать все свои реализации сервисов в META-INF/services/
.
Для особо ленивых как-то так: http://metainf-services.kohsuke.org/
Ну или как альтернатива для рантайма: https://github.com/ronmamo/reflections
Проще, но с ней вы теряете инкапсуляцию и надёжность графа зависимостей
Люди не вчера зависимости придумали. Maven, Gradle генерят стабильный classpath, плюс позволяют разруливать конфликты. Никаких циклов не может быть — мавен юзает либу наибольшей версии, градл — ту, которая ближе к руту. Для особо дотошных есть dependency tree. Java modules:
- привязаны к бинарным артефактам, что делает ваш проект зависимым от способа пакетации ваших классов
- не решает проблем изолирования транзитивных зависимостей, как osgi
- шатают и вертят всю существовавшую до этого экосистему
- ничего реально полезного не привносят, кроме дополнительных ограничений и абстракций
А вот как можно выгрузить класс (чтоб загрузить его изменённый вариант). Например замена плагинов без рестарта всего ПО?
Или это в корне неправильно?
Загружать новые версии плагинов можно, и я даже встречал людей, которые утверждали, что это делают, но я считаю, что геморрой от этого превышает пользу.
(Это всё моё личное мнение)
(Естественно, интерфейсы в классах этого плагина не меняются, только логика).
Apache Tomcat ведь без рестарта загружает/выгружает war-ники?
Из нынешних решений только два пути:
- неэффективный — один ClassLoader на группу классов, которая может быть отгружена. В случае с классами плагина это не столь критично, поскольку если и есть необходимость их отгрузки, то, скорее всего, всех вместе.
- Unsafe-way:
Unsafe#defineAnonymousClass(..)
— способ, для которого, очевидно, тем более никто не даёт гарантий работоспособности и безопасности, но который более подходит для варианта, когда нужно загружать класс и отгружать, по возможности, как только у него нет экземпляров. Как пример, он используется для сгенерированныйLambdaMetafactory
иStringConcatFactory
классов лямбд и соединителей строк, соответственно (актуально для текущей OpenJDK), а также в Nashorn для загрузки скомпилированных скриптов.
На моём опыте, первый случай, как и было сказано, уместен для случаев, когда группа классов загружается и может быть в каком-то случае отгружена (eg всё те же плагины), а второй — для динамически генерируемых классов с жизненным циклом таким же, как и жизненный цикл их экземпляров (eg, я в одном из проектов для генерации динамических строк, на основе шаблонов, в рантайме генерирую класс с агрессивным инлайнингом, который, однако, нужен ровно столько, сколько существует его экземпляр).
Добавил в закладки.
Плагинное приложение на Java без боли