Как стать автором
Обновить

Комментарии 49

Меня одного бесит бессмысленное использование буквы `I` в начале имени интерфейса? Почему нельзя интерфейс сервиса назвать просто `Service`?
Хороший вопрос. Я считаю это очень хорошей практикой, потому что:
  • По имени типа сразу понятно, что это интерфейс
  • Если у интерфейса есть реализация, то её можно назвать просто Service, а не придумывать всякие ServiceImpl и т.д.

Также это стандартная схема именования интерфейсов в коде Eclipse.
Я считаю это очень хорошей практикой, потому что:

Потому что писали когда-то на C#?
Вы так говорите, как будто это что-то плохое
Ну а чего не пошли дальше и не назвали методы с заглавной буквы, чего плохого-то? Есть общепринятые практики и решения, ничего против C# не имею, в рамках него есть свои правила написания кода и это замечательно. Раздражает, когда идут в чужой монастырь со своим уставом.
Напомните, в какой официальной конвенции именования запрещены префиксы I? Требуется лишь только, чтобы имена типов были camel case и начинались с заглавной буквы. Про I там ничего не сказано.
Вообще-то, это шарп взял это из джавы)

Кто вам такое сказал?

По имени типа сразу понятно, что это интерфейс


И что это даёт?
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».
Да, схема именования везде разная. В OpenJDK не используется I*, а вот в коде Eclipse используется:

image
Очень часто у интерфейса есть только один наследник, которому придётся придумывать новое имя, если бы префикс I не использовался. Вот какое новое имя вы бы дали Workbench или ViewSite?
DefaultWorkbench, DefaultViewSite?
DefaultWorkbench или даже SWTWorkbench потому, что конкретная реализация использует SWT для org.eclipse.ui.Workbench.

Аналогично для ViewSite, почему бы не SWTViewSite или DefaultViewSite? То, что класс называется Workbench не говорит ровным счётом ничего. А SWTWorkbench подсказывает, что это реализация на базе SWT. А потом появится SwingWorkbench. И понятно в общих чертах, что там.
НЛО прилетело и опубликовало эту надпись здесь
Я не убегаю от Eclipse никуда, с чего вы это вообще взяли?
НЛО прилетело и опубликовало эту надпись здесь
Eclipse != OSGi. Мы много чего используем готового из Eclipse, чему просто не существует альтернатив. А OSGi — лишь крошечный кусочек.

Нет, не одного :) Но всё же это не лишено смысла, ведь это венгерская нотация, которая появилась не на пустом месте. Вы не поймёте что за класс внутри файла, пока не откроете его, если имя файла не начинается с I.

Все enum начинать с E?
НЛО прилетело и опубликовало эту надпись здесь
Хорошее обсуждение, которое полностью основано на личных мнениях людей. «Я предпочитаю», «Я думаю» и всё в таком духе. Совершенно правильно, что вопрос закрыли.
Мы не будем использовать парсинг байткода с помощью ASM и подобных библиотек.

А нужно ли при загрузки плагинов читать байткод? Это используется в каких нибудь инструментах?
Ну, понятно что личное мнение тут не показатель, но за последние 10 лет я такого ни разу не видел. Для загрузки плагина ничего из того, от чего автор отказался, по большей части не нужно — а нужен стандартный класслоадер, например URLClassLoader, и все. А уж все остальное — исключительно для других целей (например, тот же OSGI, загрузив бандл, сделает много чего полезного — например, создаст и инициализирует сервисы, описанные в бандле, передаст ссылки на них в существующие бандлы, которые подписаны на такие сервисы, и сообщит им об этом). То есть такого, что в описанном тут методе либо не предусмотрено, либо даже не реализуемо. А при помощи ASM, например, что-то в загруженных классах поменяет. Это не всегда нужно — но если нужно, то обычно без манипуляции байткодом не обойтись.
Вот мне тоже показалось что работа с байткодом тут преувеличена. Но если есть такие примеры, то было бы интересно взглянуть
>Вот мне тоже показалось что работа с байткодом тут преувеличена.
Ну я могу представить, для чего она может быть нужна. Скажем в OSGI, если вы импортируете чужой сервис, то контейнер вам подсунет на самом деле proxy класс, и если сервиса реально нет — то этот proxy кинет исключение. И прокси эти с некоторой вероятностью генерируются путем манипуляции с байткодом. Но это не точно.
Не совсем понимаю, как URLClassLoader поможет вам найти нужные реализации сервисов. Загрузчик классов может только загружать классы и всё. Будете грузить вообще все классы, которые есть, а потом через reflection искать нужные? У вас приложение будет стартовать по 10 минут.
>Будете грузить вообще все классы, которые есть
В случае плагина в отдельном jar все равно все так и будет. А иначе зачем нужно все, что вы туда пакуете?

>загрузчик классов может только загружать классы и всё.
Не все. Еще он может вернуть нам список пакетов, например.

В целом, я так скажу — соглашения об именах вполне могут эту проблему решить. Ведь если у вас плагин == класслоадер, все классы в нем вполне могут называться точно так же, как в ядре. Или в другом плагине. Так что вы просто создаете класслоадер на jar, и грузите из него класс с известным именем. Я совершенно не утверждаю при этом, что это удобно или эффективно. Скорее всего — ни то, ни другое.

А практически же — достаточно метаданных в хорошо всем известном месте — и получаете широко и давно известное решение а-ля ServiceLoader. Либо ровно его же, либо что-то свое.
В случае плагина в отдельном jar все равно все так и будет
Почему? ClassLoader в джаве грузит классы лениво, в момент первого обращения к ним. В джарке могут быть сотни классов и далеко не все из них понадобятся. Например, пользователь вообще не будет трогать те или иные кнопки, и тогда получается, что вы зря грузили все эти лишние классы.
Используется, и ни в каких-нибудь, а в Спринге! А как вы думаете там работают всякие автофигурации, когда вам достаточно просто навесить аннотации на типы, а Спринг сам просканирует classpath и найдёт их? Как вы это предлагаете делать без парсинга байткода? Если у вас нет XML-конфигурации, то список классов заранее неизвестен, и Спрингу придётся пропарсить классы, чтобы понять, какие именно нужны.

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

Всю жизнь для подобного использовали:


java -cp "core.jar;plugins/*" Main

а для lookup-а сервисов — ServiceLoader.


Мне кажется, это еще одна попытка оправдать бесполезность и рудиментарность модульной системы в Java 9. Самый большой ее недостаток — это привязка модулей к бинарным бандлам (jar) и соответвтенно лейауту проекта. Идея classpath проще и универсальней.

Идея иерархии класслоадеров еще универсальнее. А classpath в чистом виде я наверное уже лет 10 как почти не вижу. А везде где вижу — стараюсь отказываться.
а для lookup-а сервисов — ServiceLoader.
Вот только вам придётся для этого прописывать все свои реализации сервисов в `META-INF/services/`. Это большой геморрой, особенно когда точек расширений у вас очень много. Спасибо, но это слишком неудобно. Использовать module-info.java гораздо проще и надёжнее.

Идея classpath проще и универсальней.
Проще, но с ней вы теряете инкапсуляцию и надёжность графа зависимостей. Если у вас есть какая-нибудь ошибка вроде цикла или split-пакетов, то classpath вам ничего об этом не скажет.
Ну выж понимаете, что у этого решения зато есть другие преимущества — оно работает не только в Java 9 :)
Вот только вам придётся для этого прописывать все свои реализации сервисов в META-INF/services/.

Для особо ленивых как-то так: http://metainf-services.kohsuke.org/
Ну или как альтернатива для рантайма: https://github.com/ronmamo/reflections


Проще, но с ней вы теряете инкапсуляцию и надёжность графа зависимостей

Люди не вчера зависимости придумали. Maven, Gradle генерят стабильный classpath, плюс позволяют разруливать конфликты. Никаких циклов не может быть — мавен юзает либу наибольшей версии, градл — ту, которая ближе к руту. Для особо дотошных есть dependency tree. Java modules:


  1. привязаны к бинарным артефактам, что делает ваш проект зависимым от способа пакетации ваших классов
  2. не решает проблем изолирования транзитивных зависимостей, как osgi
  3. шатают и вертят всю существовавшую до этого экосистему
  4. ничего реально полезного не привносят, кроме дополнительных ограничений и абстракций
Это же не мне ответ был?

Нее, пардон. Уровнем выше (автору).

Очень много мануалов как загрузить класс.
А вот как можно выгрузить класс (чтоб загрузить его изменённый вариант). Например замена плагинов без рестарта всего ПО?
Или это в корне неправильно?
Выгрузить класс может только сборщик мусора, если класс станет недостижимым. Это значит, что не должно быть ни одной цепочки сильных ссылок от GC roots до любого объекта этого класса + не должно быть цепочек ссылок на сам этот класс. Но даже если вы всё это проконтролируете, то всё равно не сможете положиться на немедленную выгрузку классов, так как сборщик мусора собирает мусор непредсказуемо. Это значит, что какое-то время эти классы будут висеть в metaspace и занимать место.
Загружать новые версии плагинов можно, и я даже встречал людей, которые утверждали, что это делают, но я считаю, что геморрой от этого превышает пользу.
(Это всё моё личное мнение)
А какие тогда есть решения задачи «заменить jar-файл без рестарта „родительского“ приложения?
(Естественно, интерфейсы в классах этого плагина не меняются, только логика).

Apache Tomcat ведь без рестарта загружает/выгружает war-ники?
Строго говоря — для этого нужно прибить именно класслоадер. У OSGI это получается, но это не слишком простое решение (не для пользователя, а скорее внутри).

Из нынешних решений только два пути:


  • неэффективный — один ClassLoader на группу классов, которая может быть отгружена. В случае с классами плагина это не столь критично, поскольку если и есть необходимость их отгрузки, то, скорее всего, всех вместе.
  • Unsafe-way: Unsafe#defineAnonymousClass(..) — способ, для которого, очевидно, тем более никто не даёт гарантий работоспособности и безопасности, но который более подходит для варианта, когда нужно загружать класс и отгружать, по возможности, как только у него нет экземпляров. Как пример, он используется для сгенерированный LambdaMetafactory и StringConcatFactory классов лямбд и соединителей строк, соответственно (актуально для текущей OpenJDK), а также в Nashorn для загрузки скомпилированных скриптов.

На моём опыте, первый случай, как и было сказано, уместен для случаев, когда группа классов загружается и может быть в каком-то случае отгружена (eg всё те же плагины), а второй — для динамически генерируемых классов с жизненным циклом таким же, как и жизненный цикл их экземпляров (eg, я в одном из проектов для генерации динамических строк, на основе шаблонов, в рантайме генерирую класс с агрессивным инлайнингом, который, однако, нужен ровно столько, сколько существует его экземпляр).

Кстати, в Java 15 наконец-то можно будет нормально создавать временные классы и выгружать их с помощью метода Lookup.defineHiddenClass. Долгожданная фича.
НЛО прилетело и опубликовало эту надпись здесь
ServiceLoader до Java 9 — это совсем не то же самое.
В вашей ссылке нет ничего про запрет использования префикса I.
Хорошая публикация. Теперь понял как работают такие вещи.

Добавил в закладки.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации