У автора ограниченные знания и понимание.Если есть какие-либо ошибки, пожалуйста, укажите и обсудите
Начните с болевых точек бизнеса
В системе, в которой участвует автор, необходим доступ к ресурсам большого количества ресурсных сторон. Для каждой стороны ресурса первоначальный подход заключается в том, что одна сторона ресурса соответствует одному модулю. Все модули опираются на общий SDK, предоставляющий шаблонные методы для получения различных данных о ресурсах. Что должен сделать ресурсный модуль, так это реализовать методы SDK и записать в метод логику для получения данных о ресурсах. Затем во время выполнения модуль динамически обнаруживается с помощью механизма SPI для получения соответствующих данных о ресурсах. Процесс показан на рисунке:
Во-первых, внешний интерфейс отправляет запрос, который будет содержать идентификатор ресурса, чтобы внутренняя служба разрешала идентификатор ресурса и вызывала соответствующий метод соответствующего модуля ресурса в соответствии с этим идентификатором для получения соответствующие данные ресурса.
Вначале эта реализация работает хорошо, и модулю ресурсов разработки нужно только вызвать интерфейс на стороне ресурсов в соответствующем методе, чтобы выполнить адаптацию разнородных данных.
Однако по мере того, как подключается все больше и больше ресурсных сторон, будут обнаруживаться следующие проблемы:
- Расширение кода проекта: модулю соответствует сторонний ресурс
- Невозможно динамически запускать новые ресурсы на стороне ресурсов: поскольку на основе SPI нам необходимо заранее разработать модули ресурсов, а также упаковать и развернуть модули ресурсов вместе, чтобы использовать ресурсы во время выполнения.
- Высокая стоимость совместной отладки: поскольку совместная отладка интерфейса выполняется удаленно, эффективность связи низкая, что приводит к высокой стоимости совместной отладки.
- Дублирование работы: работа по доступу к ресурсам на стороне ресурсов фактически повторяется, это не что иное, как вызов трехстороннего интерфейса ресурсов и адаптация данных.
Среди них основными проблемами являются расширение кода проекта, невозможность динамического запуска новых ресурсов и высокая стоимость совместной отладки.
Итак, как решить эти проблемы?
Для проблемы расширения кода проекта решение состоит в том, чтобы выделить ресурсный модуль в самостоятельный проект, а основной проект проекта не зависит от ресурсного модуля. Из-за высокой стоимости совместной отладки мы думаем о том, можем ли мы попросить поставщиков ресурсов написать для нас модули на основе предоставляемого нами SDK, и, наконец, упаковать их нам в соответствии с нашим согласованным форматом. Таким образом, будет сэкономлено время на ознакомление со структурой данных ресурсной стороны, а также снижены затраты на совместную отладку интерфейсов, что выгодно всем. Проблема дублирования работы, мы можем только сделать как можно больше, чтобы уменьшить дублирование работы. В рамках нашего предварительно заказанного SDK можем ли мы позволить небольшим партнерам, у которых есть дополнительное время на разработку, помочь в разработке, чтобы ускорить процесс разработки?
На самом деле использование модульной разработки может решить эти проблемы. В частности, как использовать модульную разработку для решения вышеуказанных проблем, пожалуйста, следите за официальными лицами и будьте спокойны, и слушайте меня медленно.
Три пути модульной разработки
Я считаю, что все знакомы с модульной разработкой, от seajs и requirejs во внешнем интерфейсе до JarsLink и OSGI во внутреннем. Оба являются платформами с открытым исходным кодом для модульного решения для разработки.
На самом деле, когда мы обычно разрабатываем back-end сервисы, мы тоже используем модульную разработку — на основе maven multi-module.
Что касается бэкэнд-инжиниринга, в этой статье в качестве примера используется проект Java maven, поскольку мы постоянно работаем с модульной разработкой. Давайте посмотрим на следующие три картинки:
Модуляризация на основе многомодульного подпроекта maven
В период разработки мы используем разные модули для разработки разных функций.В период компиляции все модули будут упакованы единообразно. Во время выполнения один и тот же загрузчик классов загружает пакеты jar каждого модуля. Поскольку используется один и тот же загрузчик классов, весь код модуля находится в одном пути к классам.
Модульность на основе изоляции Spring Context
В отличие от многомодульной разработки подпроектов на основе maven, модульной разработки на основе изоляции Spring Context, во время выполнения мы можем указать разные контексты Spring для загрузки различных пакетов jar модулей для достижения цели изоляции модулей во время выполнения. Опять же, для загрузки классов используется один и тот же загрузчик классов, поэтому весь код модуля по-прежнему находится в одном и том же пути к классам.
Модульность на основе изоляции загрузчика классов
Модульная разработка, основанная на изоляции загрузчика классов, отличается от двух предыдущих тем, что во время выполнения разные пакеты jar модулей загружаются разными загрузчиками классов. Мы знаем, что основанием для суждения о равенстве классов является:
- Загрузчик классов, который загружает класс, тот же
- Полное имя класса совпадает
Два класса равны только в том случае, если одновременно выполняются два вышеуказанных условия.
Самой существенной идеей модульной разработки, основанной на изоляции загрузчика классов, автор считает упомянутое выше условие для суждения о равенстве классов. Во время выполнения пакеты jar разных модулей загружаются разными классами.Мы знаем, что даже если полные имена разных классов модулей совпадают, они не равны.
Сравнение характеристик трех модульных методов разработки
Вот несколько ключевых особенностей, которые я считаю наиболее важными:
метод разработки | преимущество | недостаток |
---|---|---|
На основе мульти-подмодулей maven | Управлять зависимостями через maven, развертывание и упаковка очень удобно | Модули не могут быть изолированы во время выполнения.Если разные модули имеют один и тот же класс или версия класса несовместима, во время выполнения будет сообщено об ошибке, модули ресурсов не могут быть добавлены динамически; |
На основе весеннего контекста | Управление зависимостями через maven, развертывание и упаковка очень удобны, а реализация проста на основе интеграции Spring. | В сочетании с Spring Framework |
основанный на загрузчике классов | Модули среды выполнения полностью изолированы, не связаны с какой-либо структурой, очень легкие. | Сложность управления пакетом jar модуля станет выше. |
Благодаря приведенному выше анализу метод модульной разработки, основанный на изоляции загрузчика классов, является наиболее подходящим для наших существующих бизнес-потребностей, которые могут изолировать модули и динамически управлять модулями.
Поскольку каждый модуль не требует связи и полностью независим, преимущества Spring Context не могут быть отражены в нашем бизнесе, к тому же он сопряжен с фреймворком Spring, который немного тяжелее, чем метод, основанный на изоляции загрузчика классов.
Начните с простой версии модульной системы разработки от 0 до 1
Наша цель — динамически загружать пакет jar модуля и динамически управлять им (устанавливая пакет jar модуля, запуская модуль, приостанавливая работу модуля и переходя в автономный режим).Поэтому первая и основная проблема, которую мы решаем, заключается в том, как динамически загружать jar-пакет модуля ресурсов, при этом загрузка различных jar-пакетов модуля ресурсов не влияет друг на друга.Это включает в себя механизм загрузки классов. Теперь давайте начнем с механизма загрузки классов Java.
Начните с механизма загрузки классов
Мы знаем, что когда JVM загружает класс, по умолчанию она следует родительской модели делегирования класса. Что такое родительская модель делегирования класса и механизм загрузки класса, выходят за рамки подробного анализа этой статьи. Незнакомые наблюдатели могут искать множество связанных статей по определенному уровню или определенной песне.
Давайте сначала посмотрим на диаграмму модели родительского делегирования для класса.
В общем, если мы не настраиваем загрузчик классов для загрузки класса, мы используем загрузчик классов JDK по умолчанию для загрузки класса Процесс загрузки следует родительской модели делегирования, как показано на рисунке:
Если бы у нас был собственный загрузчик классов и мы следовали модели родительского делегирования, наша загрузка классов выглядела бы так:
Если мы используем механизм делегирования родительского класса JDK по умолчанию для загрузки различных пакетов jar модулей ресурсов, возникнут следующие проблемы:
- Различные пакеты jar модулей ресурсов зависят от разных версий одних и тех же зависимостей, например, один зависит от версии httpclient 3, а другой зависит от версии httpclient 4. Если разные версии трехточечной банки несовместимы, во время выполнения неизбежно возникнут ошибки, например NoClassDefError и MethodNotFound.
- Возможно, что разные модули ресурсов имеют классы с одинаковым полным именем, в этом случае возникает конфликт
- Сложно независимо управлять пакетами jar различных модулей ресурсов, и нет возможности запускать, приостанавливать и выходить из системы независимо друг от друга.
Итак, как это должно быть решено? Я верю, что у опытных судей уже есть свои ответы в сердце. Тссс.... все судьи, держитесь сами, сделайте вид, что вы сначала не знаете, а потом пусть автор продолжает пускать воду.
Мыслительный процесс гораздо важнее, чем окончательное решение проблемы.Теперь пусть автор расскажет о своем незрелом мыслительном процессе. Я надеюсь, что это вдохновит всех увидеть, это большое благословение для Hansheng!
Чтобы решить проблемы, вызванные родительской моделью делегирования классов, мы можем принять идею аналогии. Те, кто знаком с Tomcat, должны знать, что в Tomcat мы можем развернуть несколько веб-проектов. Итак, как Tomcat загружает эти разные проекты при запуске? Как изолировать проект? Как вы справляетесь с взаимозависимостью проекта? Подумайте об этом внимательно, если мы рассматриваем наш основной проект как Tomcat, то разве наши jar-пакеты модулей ресурсов не эквивалентны военным пакетам каждого проекта в Tomcat? Разве подход Tomcat к решению этих проблем не является именно тем, из чего мы можем извлечь уроки (плагиат)?
Далее давайте воспользуемся диаграммой, чтобы объяснить механизм загрузки классов Tomcat, как сломать модель родительского делегирования и как защитить различные проекты.
Вдохновлен механизмом загрузки классов Tomcat.
В Tomcat Shared ClassLoader, Catalina ClassLoader и Common ClassLoader настраиваются (по умолчанию эти три загрузчика классов являются Common ClassLoader) для загрузки пакетов jar и файлов классов в указанный каталог. С одной стороны, для запуска Tomcat необходимо ввести некоторые jar-пакеты, и эти jar-пакеты являются общими зависимостями всех проектов, поэтому для их загрузки используется унифицированный загрузчик классов. ClassLoader веб-приложения соответствует загрузчику классов, который загружает веб-проект. В разных веб-проектах будут разные загрузчики классов веб-приложений, но все они будут совместно использовать пакеты jar и файлы классов, загруженные общим загрузчиком классов. На самом деле это оптимизация: некоторые общие зависимости не нужно загружать каждым загрузчиком классов веб-приложения, что приведет к тому, что область методов в памяти jvm (jdk8 — это метапространство) будет занимать слишком много памяти.
Мы не изучаем подробно механизм загрузки классов Tomcat, с общей точки зрения механизма загрузки классов Tomcat на самом деле есть два принципа:
- Определенные общие зависимости загружаются с использованием одного и того же загрузчика классов, что ускоряет запуск Tomcat и экономит место в памяти.
- Каждый веб-проект загружается независимым загрузчиком классов, что гарантирует, что одни и те же зависимости разных веб-проектов не будут иметь проблем с совместимостью версий.
Используйте изображение, чтобы понять механизм загрузки классов модульной сервисной системы.
Выше и официальные лица вкратце упомянули о механизме загрузки классов Tomcat, по сути, идея может быть полностью позаимствована из него. Давайте проанализируем статус-кво ресурсного модуля в процессе разработки:
- Все модули ресурсов будут зависеть от одного и того же пакета SDK.
- Трехсторонние пакеты jar, от которых могут свободно зависеть разные модули ресурсов.
- Различные модули ресурсов могут свободно называть файлы классов и создавать пакеты.
Поэтому, ссылаясь на механизм загрузки классов Tomcat, автор реализует свой собственный механизм загрузки классов в своей собственной модульной системе, конкретный метод выглядит следующим образом:
- Для каждого пакета jar модуля ресурсов загрузчик классов будет настроен для загрузки. Все загрузчики классов имеют общий загрузчик родительских классов, который в системе, которую я реализовал, называется Application ClassLoader.
- Пакеты SDK, от которых зависит каждый ресурсный модуль, загружаются загрузчиком родительского класса пользовательского загрузчика классов модуля.
- Загрузчик классов пользовательского модуля не следует родительской модели делегирования.Конкретные правила таковы: прочтите конфигурацию и получите файл класса, соответствующий родительской модели делегирования. При загрузке класса, если класса нет в конфигурационном файле, он будет загружен загрузчиком пользовательского класса модуля, если он есть, то сначала он будет загружен загрузчиком родительского класса, полностью следуя модели родительского делегирования.
Объясните с помощью блок-схемы следующим образом:
Итак, конкретные идеи реализации были представлены судьям, и следующий шаг — показать вам, ребята, код.
Разработайте SDK, от которого зависит каждый модуль
Как показано на рисунке, мы определяем интерфейс IDemo, который предоставляет нам сервис sayHello:
Демонстрационный код ID:
package com.modularization.sdk
interface IDemo {
fun sayHello(): String
}
Разработать систему загрузки модулей
Мы используем подход, основанный на загрузчике классов, для достижения модульной разработки, а основной код предназначен для реализации пользовательской реализации загрузчика классов.
Там не так много основного кода, то есть написание собственного загрузчика классов и нарушение модели родительского делегирования в нужный момент.
Ниже приведен только код, загруженный пользовательским загрузчиком классов.Чтобы прочитать другие коды, загрузите код проекта в конце статьи.
Реализация основного кода:
class ModuleClassLoader : URLClassLoader {
private var excludePackages: List<String> = mutableListOf()
constructor(urls: Array<out URL>?, excludePackages: List<String>) : super(urls) {
this.excludePackages = excludePackages
}
override fun loadClass(name: String?): Class<*> {
// 需要遵循双亲委派模型的类的加载先交给父类加载
if (isExcludePackages(name)) return super.loadClass(name)
synchronized(getClassLoadingLock(name)) {
// 代码执行到这里,说明这个类是需要打破双亲委派模型的,先由子类的类加载器加载,加载不到再丢给父类加载
// 从已加载的缓存中寻找类
var c = findLoadedClass(name)
if (c == null) {
// c == null 表示子类类加载器还没有加载过此类
try {
// 寻找类
c = findClass(name)
} catch (e: Exception) {
// 因为如果类加载器找不到类会抛出一个异常,所以这里应该捕获异常,但是捕获了异常后什么都不用干
// do nothing
}
}
// c != null 表示子类类加载器加载到类,直接返回
if (c != null) return c
// 执行到这里,说明子类类加载器加载不到类,交给父类去加载
return super.loadClass(name)
}
}
/**
* 配置哪些包下面的类的加载不打破双亲委派模型
*/
private fun isExcludePackages(className: String?): Boolean {
if (className.isNullOrEmpty()) return false
excludePackages.forEach {
if (className!!.startsWith(it)) {
return true
}
}
return false
}
}
Остальной код можно посмотреть, загрузив инженерный код, приведенный в конце статьи.
Определить ограничения блока
Договариваемся о разработке модуля, и нам нужно настроить уникальный ID модуля, версию модуля и т.д. Файл конфигурации имеет одинаковое имя module.properties, а расположение одинаково размещено в src/main/resources/META-INF.Конечно, мы не можем использовать эту условную конфигурацию или можем использовать другие, в зависимости от бизнес-требований. , как показано на рисунке:
Информация о конфигурации подробно описана ниже:
# 模块唯一ID
module.appCode=fz
# 模块版本
module.jarVersion=1.0
# 模块提供服务的类路径,当然,我们可以根据约定配置更多服务的类路径
module.demo.location=com.modularization.fz.FzDemo
Определить правила упаковки модулей
Каждый модуль представляет собой проект maven, и вводится следующая конфигурация pom.xml:
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
модуль ресурса разработки
В этой статье автор готовит для чиновников три ресурсных модуля, каждый из которых реализует интерфейс IDemo и предоставляет свои данные. Для удобства тестирования автор напрямую написал код ресурсного модуля в maven-проекте, на самом деле для этого нам нужно использовать независимый проект.
Каталог проекта модуля показан на рисунке:На рисунке представлены три ресурса fz, tm и tw.
Каталог проекта ресурса fz показан на рисунке:
Для других модулей ресурсов реализация tm и tw такая же, как каталог проекта и fz, но возвращаемое значение метода sayHello другое.
Таким образом, мы смоделировали и разработали три ресурсных модуля.
Простая тестовая проверка
Скомпилируйте и упакуйте код ресурсного модуля tm, tw, fz и получите адрес пакета jar. Напишите тестовый класс следующим образом:
тестовый код
package com.modularization.server
import com.modularization.sdk.IDemo
import com.modularization.server.core.ModuleClassLoader
import com.modularization.server.core.exception.ModuleNotFoundException
import com.modularization.server.core.util.ResourceUtils
import com.modularization.server.proxy.DemoDynamicProxy
import org.junit.Before
import org.junit.Test
import java.net.URL
/**
* @author wu
* @since 2019/8/5
*/
class DemoTest {
val moduleCache = HashMap<String, ModuleJar>()
@Before
fun init() {
val excludePackages = arrayListOf("java", "com.modularization.sdk")
val moduleJarUrls = arrayListOf(
"file:/Users/admin/Desktop/work/modularization-demo/modularization-fz/target/modularization-fz-1.0-SNAPSHOT.jar",
"file:/Users/admin/Desktop/work/modularization-demo/modularization-tm/target/modularization-tm-1.0-SNAPSHOT.jar",
"file:/Users/admin/Desktop/work/modularization-demo/modularization-tw/target/modularization-tw-1.0-SNAPSHOT.jar")
moduleJarUrls.forEach {
val moduleJarUrl = URL(it)
val moduleClassLoader = ModuleClassLoader(arrayOf(moduleJarUrl), excludePackages)
val properties = ResourceUtils.getProperties(moduleClassLoader.getResourceAsStream("META-INF/module.properties"))
val moduleJar = ModuleJar()
moduleJar.appCode = properties.getProperty(ModuleSettingConstants.APP_CODE) ?: throw ModuleNotFoundException("${moduleJarUrl.path} ${ModuleSettingConstants.APP_CODE}没有配置")
moduleJar.jarVersion = properties.getProperty(ModuleSettingConstants.JAR_VERSION) ?: throw ModuleNotFoundException("${moduleJarUrl.path} ${ModuleSettingConstants.JAR_VERSION}没有配置")
moduleJar.moduleDemoLocation = properties.getProperty(ModuleSettingConstants.MODULE_DEMO_LOCATION) ?: ""
moduleJar.moduleClassLoader = moduleClassLoader
moduleJar.moduleJarUrl = moduleJarUrl.path
// 模拟缓存加载完成的模块
moduleCache[moduleJar.appCode] = moduleJar
println("模块:${moduleJar.appCode}启动完成")
}
}
@Test
fun sayHelloTest() {
val fzModuleJar = moduleCache["fz"]
val twModuleJar = moduleCache["tw"]
val tmModuleJar = moduleCache["tm"]
val fzDemo = DemoDynamicProxy().bind(fzModuleJar!!.moduleClassLoader!!
.loadClass(fzModuleJar.moduleDemoLocation).newInstance()) as IDemo
val twDemo = DemoDynamicProxy().bind(twModuleJar!!.moduleClassLoader!!
.loadClass(twModuleJar.moduleDemoLocation).newInstance()) as IDemo
val tmDemo = DemoDynamicProxy().bind(tmModuleJar!!.moduleClassLoader!!
.loadClass(tmModuleJar.moduleDemoLocation).newInstance()) as IDemo
println(tmDemo.sayHello())
println(twDemo.sayHello())
println(fzDemo.sayHello())
}
}
выходной результат
模块:fz启动完成
模块:tm启动完成
模块:tw启动完成
00:44:48.667 [main] INFO com.modularization.server.proxy.DemoDynamicProxy - 执行: class => com.modularization.tm.TmDemo, method => sayHello, 参数 => null, 耗时 => 0ms
执行结果 => I am Tm
I am Tm
00:44:48.678 [main] INFO com.modularization.server.proxy.DemoDynamicProxy - 执行: class => com.modularization.tw.TwDemo, method => sayHello, 参数 => null, 耗时 => 0ms
执行结果 => I am Tw
I am Tw
00:44:48.678 [main] INFO com.modularization.server.proxy.DemoDynamicProxy - 执行: class => com.modularization.fz.FzDemo, method => sayHello, 参数 => null, 耗时 => 0ms
执行结果 => I am Fz
I am Fz
Суммировать
К этому моменту я объективно проанализировал основные моменты модульной системы разработки, в которой участвовал автор, и реализовал простую версию, на самом деле ее нельзя использовать напрямую в продакшене, надо еще учитывать Up-down совместимость Версии SDK, совместимость версий JDK для разработки модулей, кэширование модулей, управление жизненным циклом и т. д. Однако, если вы понимаете все содержание этой статьи, вам не составит труда внедрить модульную систему обслуживания, которую можно использовать в производственной среде в соответствии с потребностями вашего бизнеса.
Адрес источника
Гитхаб автора слишком прост, а исходный код можно скачать по облачной ссылке Baidu. Судьи, не смейтесь.
Адрес ресурса: ссылка:disk.Baidu.com/Yes/1RF ищет y7UO T…Пароль: ztfn
Пример кода разработан с использованием kotlin, и вы можете снова написать его на java по сравнению с исходным кодом.