Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Спасение от Jar Hell с помощью слоев Jigsaw

Спасение от Jar Hell с помощью слоев Jigsaw

Модульную систему Java aka JPMS или Jigsaw часто критикуют из-за отсутствия версионирования, которое есть в альтернативных модульных системах для Java, таких как OSGi. Одна из главных целей версионирования — это решать проблему Jar Hell, которая возникает, когда приложение зависит от двух разных версий одной и той же библиотеки.

Jigsaw может детектировать такую конфликтную ситуацию через детектирование так называемых split-пакетов, но не позволяет загрузить две версии одного модуля, присутствующих в module path. Однако не всегда возможно избавиться от конфликта версий в большом приложении, потому что конфликтующие версии могут появляться в приложении через транзитивные зависимости, которые не всегда под контролем разработчика. К счастью, в Jigsaw предусмотрели возможность бороться с этой проблемой с помощью так называемых слоев Jigsaw (Jigsaw Layers).

В этом докладе мы разберемся, какие проблемы могли бы возникнуть, если бы в Jigsaw были явные версии для модулей, и как слои Jigsaw вместе с Jigsaw-сервисами могут помочь безопасно решать проблему Jar Hell.

Nikita Lipsky

October 21, 2018
Tweet

More Decks by Nikita Lipsky

Other Decks in Programming

Transcript

  1. Про себя ? •  Инициатор проекта Excelsior JET –  работал

    над проектом более 18 лет –  как идейный вдохновитель –  компиляторный инженер –  руководитель –  и много в каких еще ролях •  twitter: @pjBooms •  team blog: https://www.excelsiorjet.com/blog 3
  2. План доклада •  Очень краткое введение в Jigsaw •  Проблема

    Jar Hell •  Версионирование как способ решить Jar Hell •  Проблемы связанные с версионированием •  Jigsaw Layers •  Как решать Jar Hell с помощью Jigsaw Layers •  Демо 4
  3. Пример модуля // src/java.sql/module-info.java module java.sql { requires transitive java.logging;

    requires transitive java.xml; exports java.sql; exports javax.sql; exports javax.transaction.xa; uses java.sql.Driver; } 6
  4. Модульная система Jigsaw Jigsaw модуль •  импортирует: –  модули: requires

    –  сервисы: uses •  экспортирует: –  пакеты: exports –  сервисы: provides SomeService with ServiceImpl 7
  5. Модульная система Jigsaw Module A Class A Module B Class

    B Module C Class C exports packageC; requires: A exports packageA; I requires A; requires C; 8
  6. Версионирование •  Для решения проблемы JAR hell в первых драфтах

    Jigsaw модуль мог иметь версию – соответственно импорт/экспорт квалифицировался версией – если два модуля требовали библиотеку разных версий, грузились обе версии библиотеки 16
  7. Версионирование Вопрос: Как реализовать версионирование? Задача: Имеем модуль A, использующий

    библиотеку Lib (v1), и модуль B, использующий Lib (v2). Требуется, чтобы обе версии Lib работали и не конфликтовали. Решение: грузить обе версии библиотеки разными загрузчиками классов. 19
  8. Версионирование Вопрос: Как реализовать версионирование? Задача: Имеем модуль A, использующий

    библиотеку Lib (v1), и модуль B, использующий Lib (v2). Требуется, чтобы обе версии Lib работали и не конфликтовали. Решение: грузить обе версии библиотеки разными загрузчиками классов. 20
  9. Версионирование Вопрос: Как реализовать версионирование? Задача: Имеем модуль A, использующий

    библиотеку Lib (v1), и модуль B, использующий Lib (v2). Требуется, чтобы обе версии Lib работали и не конфликтовали. Решение: грузить обе версии библиотеки разными загрузчиками классов. 21
  10. Версионирование •  ClassLoader образует уникальное пространство имен классов •  Если

    каждый модуль грузить своим загрузчиком классов, то нет конфликтов с классами других модулей 22
  11. Версионирование com.foo App 1.0 com.foo parse-api 2.0 com.foo persist-api 3.0

    org.apache commons 2.1 org.apache commons 3.1 CL1 CL2 CL3 CL4 CL5 Разные версии apache commons могут одновременно жить в JVM 23
  12. Jigsaw и загрузчики Jigsaw – это не только модули, но

    и разбиение Java платформы на модули. 27
  13. Jigsaw и загрузчики Проблемы обратной совместимости: По спецификации getClassloader() ==

    null для всех классов платформы. Что противоречит разбиению платформы на модули, где каждый модуль грузится своим загрузчиком 28
  14. Jigsaw и загрузчики Проблемы обратной совместимости: По спецификации getClassloader() ==

    null для всех классов платформы. Что противоречит разбиению платформы на модули, где каждый модуль грузится своим загрузчиком 29
  15. Jigsaw и загрузчики Проблемы обратной совместимости: Даже то, что классы

    вашего предложения будут грузится многими загрузчиками может вызывать проблемы Ф 30
  16. Jigsaw и загрузчики Проблемы обратной совместимости: И даже то, что

    Application Classloader перестал наследовать URLClassloader уже вызывает проблемы Ф 31
  17. 32

  18. Версионирование? App 1.0 Foo 2.0 Bar 3.0 Baz 2.1 Baz

    3.1 A Нет, это я “A”! A Я - “A”! 41
  19. Loading constraints •  Loading constraints запрещают двум разным классам с

    одинаковыми именами (fully qualified) появиться в области видимости одного класса 44
  20. Loading constraints B.java: class B { T1 f1 = A.f;

    int f2 = A.m(t2); } A.java: class A { static T1 f; static int m(T2 t) Если B грузится загрузчиком L1, а A грузится L2, то JVM проверит, что (T1, L1) = (T1, L2) и (T2, L1) = (T2, L2) == 45
  21. Версионирование Еще деталь: импорт в ранних версиях Jigsaw специфицировался не

    просто версией, а диапазоном версий: –  Модуль мог декларировать, что может работать с зависимостями версий от и до 47
  22. Версионирование … после чего версионирование в модульной системе Java приказало

    долго жить. Нет версионирования – не нужны загрузчики классов для модулей. 51
  23. Jar Hell Stop! Но ведь в Jigsaw нет версий! У

    двух версий одной и той же библиотеки скорей всего есть одинаковые пакеты 55
  24. Jar Hell Stop! Но ведь в Jigsaw нет версий! У

    двух версий одной и той же библиотеки скорей всего есть одинаковые пакеты Jigsaw запрещает двум модулям иметь одинаковые пакеты (split packages) 56
  25. Jar Hell Но как же все таки решать проблему Jar

    Hell? App 1.0 Foo 2.0 Bar 3.0 Baz 2.1 Baz 3.1 57
  26. 59

  27. Jar Hell Другой вариант – разложить разные версии библиотеки по

    разным пакетам (Maven Shade плагин) Не всегда работает из-за reflection. 62
  28. Jigsaw Layers * Jigsaw layer: •  Локальная модульная система • 

    Внутри одного слоя запрещены split пакеты •  Разные модули с одинаковыми пакетами должны принадлежать разным слоям 65
  29. Jigsaw Layers * •  Слои Jigsaw реализованы …через загрузчики классов.

    •  У каждого слоя по крайней мере один свой загрузчик (может быть несколько) •  Но при этом невозможно по построению нарушение classloader constraints! 67
  30. Jigsaw Layers * •  Слои Jigsaw реализованы через загрузчики классов!

    •  У каждого слоя по крайней мере один свой загрузчик (может быть несколько) •  Но при этом невозможно по построению нарушение classloader constraints! 68
  31. Jigsaw Layers * •  Слои Jigsaw реализованы через загрузчики классов!

    •  У каждого слоя по крайней мере один свой загрузчик (может быть несколько) •  Но при этом невозможно по построению нарушение classloader constraints! 69
  32. Jigsaw Layers * •  Слои Jigsaw реализованы через загрузчики классов!

    •  У каждого слоя по крайней мере один свой загрузчик (может быть несколько) •  Но при этом невозможно по построению нарушение classloader constraints! 70
  33. Jigsaw Layers Jar Hell Resolution App 1.0 Library Foo 2.0

    Library Bar 3.0 Library Baz 2.1 Library Baz 3.1 71
  34. Jigsaw Layers Jar Hell Resolution App 1.0 Library Foo 2.0

    Library Bar 3.0 Library Baz 2.1 Library Baz 3.1 Boot Layer 72
  35. Jigsaw Layers Jar Hell Resolution App 1.0 Library Foo 2.0

    Library Bar 3.0 Library Baz 2.1 Library Baz 3.1 Layer 2 Boot Layer 73
  36. Layer 3 Jigsaw Layers Jar Hell Resolution App 1.0 Library

    Foo 2.0 Library Bar 3.0 Library Baz 2.1 Library Baz 3.1 Layer 2 Boot Layer 74
  37. Jigsaw Layers Jar Hell Resolution App 1.0 Library Foo 2.0

    Library Bar 3.0 Library Baz 2.1 Library Baz 3.1 75
  38. Layer 3 Jigsaw Layers Jar Hell Resolution App 1.0 Library

    Foo 2.0 Library Bar 3.0 Library Baz 2.1 Library Baz 3.1 Layer 2 Boot Layer 76
  39. Layer 3 Jigsaw Layers Jar Hell Resolution App 1.0 Library

    Foo 2.0 Library Bar 3.0 Library Baz 2.1 Library Baz 3.1 Layer 2 Boot Layer 77
  40. Jigsaw Layers •  У каждого слоя есть родительский слой • 

    Модули дочернего слоя могут импортировать модули родительского слоя •  Но модули родительского слоя не могут импортировать модули дочернего! •  Именно поэтому не возможны нарушения classloaders constraints! 78
  41. Jigsaw Layers •  У каждого слоя есть родительский слой • 

    Модули дочернего слоя могут импортировать модули родительского слоя •  Но модули родительского слоя не могут импортировать модули дочернего! •  Именно поэтому не возможны нарушения classloaders constraints! 79
  42. Jigsaw Layers •  У каждого слоя есть родительский слой • 

    Модули дочернего слоя могут импортировать модули родительского слоя •  Но модули родительского слоя не могут импортировать модули дочернего! •  Именно поэтому не возможны нарушения classloaders constraints! 80
  43. Jigsaw Layers •  У каждого слоя есть родительский слой • 

    Модули дочернего слоя могут импортировать модули родительского слоя •  Но модули родительского слоя не могут импортировать модули дочернего! •  Именно поэтому невозможны нарушения classloaders constraints! 81
  44. Layer 3 Jigsaw Layers Jar Hell Resolution App 1.0 Library

    Foo 2.0 Library Bar 3.0 Library Baz 2.1 Library Baz 3.1 Layer 2 Boot Layer A A 82
  45. Layer 3 Jigsaw Layers Jar Hell Resolution App 1.0 Library

    Foo 2.0 Library Bar 3.0 Library Baz 2.1 Library Baz 3.1 Layer 2 A A Boot Layer 83
  46. Layer 3 Jigsaw Layers Jar Hell Resolution App 1.0 Library

    Foo 2.0 Library Bar 3.0 Library Baz 2.1 Library Baz 3.1 Layer 2 A A Boot Layer 84
  47. Layer 3 Jigsaw Layers Jar Hell Resolution App 1.0 Library

    Foo 2.0 Library Bar 3.0 Library Baz 2.1 Library Baz 3.1 Layer 2 A A Boot Layer Я живу на одном Ну а ты на другом На высоком берегу на крутом 85
  48. Jigsaw Layers Вопрос: если модули основного приложения не могут импортировать

    модули дочерних слоев, как тогда использовать их функциональность? 86
  49. 87

  50. Jigsaw Layers Вопрос: если модули основного приложения не могут импортировать

    модули дочерних слоев, как тогда использовать их функциональность? Ответ: Использовать шаблон IoC через Jigsaw Services: cлои могут предоставлять сервисы в модули родительского слоя 88
  51. Jigsaw Layers Вопрос: если модули основного приложения не могут импортировать

    модули дочерних слоев, как тогда использовать их функциональность? Ответ: Использовать шаблон IoC через Jigsaw Services: cлои могут предоставлять сервисы в модули родительского слоя 89
  52. Jigsaw Layers Вопрос: если модули основного приложения не могут импортировать

    модули дочерних слоев, как тогда использовать их функциональность? Ответ: Использовать шаблон IoC через Jigsaw Services: cлои могут предоставлять сервисы в модули родительского слоя 90
  53. Layer 3 Jigsaw Layers Jar Hell Resolution App 1.0 Library

    Foo 2.0 Library Bar 3.0 Library Baz 2.1 Library Baz 3.1 Layer 2 Boot Layer 91
  54. Layer 3 Jigsaw Layers Jar Hell Resolution App 1.0 Foo

    2.0 Bar 3.0 Baz 2.1 Baz 3.1 Layer 2 App Service Provider 1 App Service Provider 2 Boot Layer 92
  55. 93

  56. Как использовать var finder = ModuleFinder.of(Paths.get(“MyLayer”)); var parent = ModuleLayer.boot();

    Configuration cf = parent.configuration().resolve( finder, ModuleFinder.of(), Set.of(“myRootMod")); ModuleLayer myL = parent.defineModulesWithOneLoader( cf, ClassLoader.getSystemClassLoader()); 94
  57. Как использовать var finder = ModuleFinder.of(Paths.get(“MyLayer”)); var parent = ModuleLayer.boot();

    Configuration cf = parent.configuration().resolve( finder, ModuleFinder.of(), Set.of(“myRootMod")); ModuleLayer myL = parent.defineModulesWithOneLoader( cf, ClassLoader.getSystemClassLoader()); 95
  58. Как использовать var finder = ModuleFinder.of(Paths.get(“MyLayer”)); var parent = ModuleLayer.boot();

    Configuration cf = parent.configuration().resolve( finder, ModuleFinder.of(), Set.of(“myRootMod")); ModuleLayer myL = parent.defineModulesWithOneLoader( cf, ClassLoader.getSystemClassLoader()); 96
  59. Как использовать var finder = ModuleFinder.of(Paths.get(“MyLayer”)); var parent = ModuleLayer.boot();

    Configuration cf = parent.configuration().resolve( finder, ModuleFinder.of(), Set.of(“myRootMod")); ModuleLayer myL = parent.defineModulesWithOneLoader( cf, ClassLoader.getSystemClassLoader()); 97
  60. Как использовать var finder = ModuleFinder.of(Paths.get(“MyLayer”)); var parent = ModuleLayer.boot();

    Configuration cf = parent.configuration().resolve( finder, ModuleFinder.of(), Set.of(“myRootMod")); ModuleLayer myL = parent.defineModulesWithOneLoader( cf, ClassLoader.getSystemClassLoader()); var services = ServiceLoader.load(myL, MySrv.class); 98
  61. Где еще могут пригодиться слои Проблема: стандарт servlet контейнеров не

    знает пока про модули –  Зависимости в war файлах – это по сути тот же самый classpath! Решение: можно форкнуть ваш любимый app server, добавив модульный слой в его загрузчик приложений –  пример: 100
  62. Где еще могут пригодиться слои Проблема: стандарт servlet контейнеров не

    знает пока про модули –  Зависимости в war файлах – это по сути тот же самый classpath! Решение: можно форкнуть ваш любимый app server, добавив модульный слой в его загрузчик приложений –  пример: https://github.com/pjBooms/tomcat 101
  63. Tomcat •  В classpath лежит bootstrap.jar •  Сам Томкат грузится

    отдельным загрузчиком (org.apache.catalina.startup.ClassLoaderFactory) •  Каждое веб-приложение грузится своим загрузчиком (org.apache.catalina.loader.WebappClassLoaderBase) 102
  64. Tomcat •  У каждого загрузчика есть свой Unnamed модуль • 

    Если не предпринимать специальных усилий, то классы загружаемые загрузчиком попадут в Unnamed модуль – даже если они формально лежат в именованном модуле 103
  65. Modular War Example Пример (https://github.com/pjBooms/modular-war-example): –  2 модуля: helloworld-provider, helloworld-web-app

    –  helloworld-web-app выводит на страницу имена модулей, загруженных классов: "<H3>HelloServlet module: " + HelloServlet.class.getModule().getName() + "</H3>\n" + "<H3>HelloWorldProvider module: " + HelloWorldProvider.class.getModule().getName() + "</H3>\n" + 104
  66. Tomcat fork Модификация ClassLoaderFactory.createClassLoader: вместо return new URLClassLoader(array); создание модульного

    слоя: ModuleFinder finder = ModuleFinder.of(array); … ModuleLayer moduleLayer = parentLayer.defineModulesWithOneLoader(cf, parent); return moduleLayer.findLoader(moduleNames.get(0)); 107
  67. Tomcat fork Модификация WebAppClassloaderBase.start() cоздается модульный слой: WebResource modules =

    resources.getResource("/WEB-INF/modules"); if (modules.exists()) { ModuleFinder finder = ModuleFinder.of(Paths.get(modules)); ModuleLayer parent = Catalina.class.getModule().getLayer(); … ModuleLayer moduleLayer = parent.defineModulesWithOneLoader(cf, getParent()); 108
  68. Tomcat fork Модификация WebAppClassloaderBase.loadClass() слой используется для загрузки классов веб-

    приложения: if (moduleLayerLoader != null) { try { return moduleLayerLoader.loadClass(name); } catch (ClassNotFoundException e) { // Ignore } } 109
  69. Как насчет Spring Boot? •  Классы Spring Boot executable Jar

    грузятся Spring Boot загрузчиком •  Этот загрузчик не знает про модули •  Соответственно у Spring Boot нет модулей в рантайме 111
  70. Как насчет Spring Boot? •  Классы Spring Boot executable Jar

    грузятся Spring Boot загрузчиком •  Этот загрузчик не знает про модули •  Соответственно у Spring Boot нет модулей в рантайме 112
  71. Как насчет Spring Boot? •  Классы Spring Boot executable Jar

    грузятся Spring Boot загрузчиком •  Этот загрузчик не знает про модули •  Соответственно у Spring Boot нет модулей в рантайме 113
  72. Как насчет Spring Boot? •  Классы Spring Boot executable Jar

    грузятся Spring Boot загрузчиком •  Этот загрузчик не знает про модули •  Соответственно у Spring Boot нет модулей в рантайме 114
  73. Как насчет Spring Boot? Хорошая новость: Spring Boot приложения можно

    запускать без Spring Boot загрузчика – сложив все его зависимости в classpath 115
  74. Как насчет Spring Boot? Хорошая новость: Spring Boot приложения можно

    запускать без Spring Boot загрузчика – сложив все его зависимости в classpath Соответственно их можно модуляризировать, используя стандартный способ! 116
  75. Заключение Версии для модулей кажутся хорошей идеей, но на деле

    с ними много проблем: – обратная совместимость – небезопасность (classloaders constraints) – NP-полнота на разрешение зависимостей 118
  76. Заключение Jigsaw Layers позволяют решать проблему Jar Hell безопасно: – невозможно

    нарушение classloaders constraints по построению – Jigsaw services помогают использовать функциональность реализованную в дочернем слое из родительского 119