{"id":18446576,"url":"https://github.com/esastack/esa-cabin","last_synced_at":"2025-04-08T00:31:59.058Z","repository":{"id":45582420,"uuid":"365467442","full_name":"esastack/esa-cabin","owner":"esastack","description":"A lightweight class isolation container for Java applications.","archived":false,"fork":false,"pushed_at":"2023-12-11T09:59:02.000Z","size":7164,"stargazers_count":25,"open_issues_count":3,"forks_count":8,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-23T03:11:41.028Z","etag":null,"topics":["class-isolation","classloader","maven-plugin"],"latest_commit_sha":null,"homepage":"https://www.esastack.io","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/esastack.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-05-08T09:01:21.000Z","updated_at":"2024-08-02T01:12:24.000Z","dependencies_parsed_at":"2022-07-26T11:47:08.181Z","dependency_job_id":null,"html_url":"https://github.com/esastack/esa-cabin","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esastack%2Fesa-cabin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esastack%2Fesa-cabin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esastack%2Fesa-cabin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esastack%2Fesa-cabin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/esastack","download_url":"https://codeload.github.com/esastack/esa-cabin/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247755389,"owners_count":20990616,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["class-isolation","classloader","maven-plugin"],"created_at":"2024-11-06T07:09:43.508Z","updated_at":"2025-04-08T00:31:54.047Z","avatar_url":"https://github.com/esastack.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Cabin\n![Build](https://github.com/esastack/esa-cabin/workflows/Build/badge.svg?branch=main)\n[![codecov](https://codecov.io/gh/esastack/esa-cabin/branch/main/graph/badge.svg?token=CCQBCBQJP6)](https://codecov.io/gh/esastack/esa-cabin)\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.esastack/esa-cabin/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.esastack/esa-cabin/)\n[![GitHub license](https://img.shields.io/github/license/esastack/esa-cabin)](https://github.com/esastack/esa-cabin/blob/main/LICENSE)\n\nCabin是一款Java类隔离框架，用于隔离各个模块之间、模块和业务之间使用的第三方依赖库。使用Cabin后，模块SDK将只是用自己指定\n版本的三方依赖，不会被业务引入的依赖所影响，保证业务正常运行，提高业务的开发效率，减少中间件同学此类重复的支持工作。\n\n## 概述\nESA Cabin用于解决模块间类冲突。要解决的问题是，当一个应用项目中使用到了多个框架或者SDK(后续称为LibModule)时，\n各个LibModule之间、LibModule和应用业务代码(后续称为BizModule)之间可能存在包冲突，即“使用了三方依赖的不兼容的不同版本”。\n业务遇到包冲突的时候，需要进行进行复杂的排包工作和兼容性实验，以期待找到一个能兼容各个模块的依赖版本；当LibModule使用\n了不兼容的三方依赖版本发布的时候，还需要多方协调。一旦遇到依赖冲突，将严重影响开发效率和体验。  \n\n举例说明依赖冲突。假设如下场景，如果工程需要引入两个框架：FrameworkA 和 FrameworkB，其中 FrameworkA 需要依赖版本号为\n 1.0.0 的 DependencyC， FrameworkB 需要依赖版本号为 2.0.0 的 DependencyC，而这两个版本无法兼容(例如类名、方法名、参数有\n 变化或者增删)：\n \n![image.png](src/main/resources/1.png)\n\n为了彻底解决包冲突的问题，需要借助类隔离机制，将各个Framework打包为一个LibModule，使用各自的 ClassLoader 加载，业务代码\n使用单独的ClassLoader加载，达到相互隔离的目的:\n\n![image.png](src/main/resources/2.png)\n\n## Quick Start\n## LibModule开发者\n框架、中间件、SDK可以打包为特定的格式，使用Cabin容器来和业务代码进行类隔离。\n\n### 打包\n\u003e 目前只支持maven打包。\n\n#### 打包插件\n+ 使用cabin-module-maven-plugin插件打包\n具体使用方式参考下文举例。\n\n#### 单module项目打包\n将一个module及其依赖打包。此种打包方式需要指定classifier(例子里有说明)，用户根据classifier来区分使用fatjar和普通jar。\n此种打包方式会将module的所有maven依赖传递给使用者，使用者可以任意update/exclude这些依赖，不会影响module中的代码。\n\u003cbr\u003e\n用esa-rpc-filter这个module举例：\n```xml\n...\n\u003cartifactId\u003eesa-rpc-cluster\u003c/artifactId\u003e\n...\n\u003cdependencies\u003e\n    ...\n\u003c/dependencies\u003e\n\n\u003cbuild\u003e\n    \u003cplugins\u003e\n        \u003cplugin\u003e\n            \u003cgroupId\u003eio.esastack\u003c/groupId\u003e\n            \u003cartifactId\u003ecabin-module-maven-plugin\u003c/artifactId\u003e\n            \u003cversion\u003e${cabin.version}\u003c/version\u003e\n            \u003cexecutions\u003e\n                \u003cexecution\u003e\n                    \u003cid\u003edefault-cli\u003c/id\u003e\n                    \u003cgoals\u003e\n                        \u003cgoal\u003emodule-repackage\u003c/goal\u003e\n                    \u003c/goals\u003e\n                    \u003cconfiguration\u003e\n                        ## 一定要指定，否则会覆盖原有的标准jar包\n\t\t\t\u003cclassifier\u003ecabin-module\u003c/classifier\u003e\n\t\t\t## 需要导出的类、包、资源，一般使用package即可，资源一般是SPI的配置文件，Spring的xsd和spring.fatories文件等。\n                        \u003cexported\u003e\n                            \u003cpackages\u003e\n                                \u003cpackage\u003eesa.rpc.cluster\u003c/package\u003e\n                                \u003cpackage\u003eesa.commons.serialize\u003c/package\u003e\n                            \u003c/packages\u003e\n                            \u003cresources\u003e\n                                \u003cresource\u003eMETA-INF/services/\u003c/resource\u003e\n                            \u003c/resources\u003e\n                        \u003c/exported\u003e\n\n                        ##imported的类是指被打包的模块需要优先从业务classpath中加载的类，比如Spring一般是使用业务的依赖；如果模块需要通过方法的参数和返回值传递三方依赖的类和对象，也建议使用import的方式从业务classpath加载这些类(例如需要传递netty的ChannelHandlerContext)；提供了SPI接口的三方包，也建议从biz中import。\n                        \u003cimported\u003e\n                            \u003cpackages\u003e\n                                \u003cpackage\u003ejavassist\u003c/package\u003e\n                                \u003cpackage\u003eorg.springframework\u003c/package\u003e\n                            \u003c/packages\u003e\n                        \u003c/imported\u003e\n\t\t\t##不影响使用的SPI，可以不import，在此处排除\n \t\t\t\u003cexcludeCheckedSPIs\u003e\n\t\t\t \t\u003cexcludeCheckedSPI\u003ecom.fasterxml.jackson.core.JsonFactory\u003c/excludeCheckedSPI\u003e\n                                \u003cexcludeCheckedSPI\u003eio.grpc\u003c/excludeCheckedSPI\u003e\n                        \u003c/excludeCheckedSPIs\u003e\n                    \u003c/configuration\u003e\n                \u003c/execution\u003e\n            \u003c/executions\u003e\n        \u003c/plugin\u003e\n    \u003c/plugins\u003e\n\u003c/build\u003e\n```\n\n#### 多module项目合并打包成All-In-One的FatJar\n如果要将多个module项目合并打包成一个FatJar, 具体步骤如下:\n+ 创建一个新module来依赖需要合并在一起的module，如xxx-cabin，在其pom中依赖此项目的所有其他module和额外需要的三方依赖；\n+ 在xxx-cabin的pom中加入cabin-module-maven-plugin插件，并配置相关信息\n\u003cbr\u003e\n下面用ESA RPC举例讲解如何打包：\n\n+ esa-rpc-cabin (会传递三方依赖)\n\n```xml\n\u003cdependencies\u003e\n    ...加入需要合并打包的module和依赖\n\u003c/dependencies\u003e\n\n\u003cbuild\u003e\n    \u003cplugins\u003e\n        \u003cplugin\u003e\n            \u003cgroupId\u003eio.esastack\u003c/groupId\u003e\n            \u003cartifactId\u003ecabin-module-maven-plugin\u003c/artifactId\u003e\n            \u003cversion\u003e${cabin.version}\u003c/version\u003e\n            \u003cexecutions\u003e\n                \u003cexecution\u003e\n                    \u003cid\u003edefault-cli\u003c/id\u003e\n                    \u003cgoals\u003e\n                        \u003cgoal\u003emodule-repackage\u003c/goal\u003e\n                    \u003c/goals\u003e\n                    \u003cconfiguration\u003e\n\t\t\t### 需要导出的类、包、资源，一般使用package即可，资源一般是SPI的配置文件，Spring的xsd和spring.fatories文件等。\n                        \u003cexported\u003e\n                            \u003cpackages\u003e\n                                \u003cpackage\u003eesa.rpc\u003c/package\u003e\n                                \u003cpackage\u003eesa.commons.serialize\u003c/package\u003e\n                            \u003c/packages\u003e\n                            \u003cclasses\u003e\n\t\t\t\t\u003cclass\u003eesa.rpc.filter.consumer.Async2SyncFlter\u003c/class\u003e\n                            \u003c/classes\u003e\n\t\t\t    #\n                            \u003cresources\u003e\n                                \u003cresource\u003eMETA-INF/services/\u003c/resource\u003e\n                                \u003cresource\u003eMETA-INF/esa/\u003c/resource\u003e\n                                \u003cresource\u003eMETA-INF/esa-rpc.xsd\u003c/resource\u003e\n                                \u003cresource\u003eMETA-INF/spring.*\u003c/resource\u003e\n                                \u003cresource\u003eesa/rpc/spring/boot/autoconfigure/ESABeanConfiguration.class\u003c/resource\u003e\n                                \u003cresource\u003eesa/rpc/spring/boot/autoconfigure/banner/ESAWelcomeLogoApplicationListener.class\u003c/resource\u003e\n                            \u003c/resources\u003e\n                        \u003c/exported\u003e\n\n                        #imported的类是指被打包的模块需要优先从业务classpath中加载的类，比如Spring一般是使用业务的依赖；如果模块需要通过方法的参数和返回值传递三方依赖的类和对象，也建议使用import的方式从业务classpath加载这些类(例如需要传递netty的ChannelHandlerContext)；提供了SPI接口的三方包，也建议从biz中import。\n                        \u003cimported\u003e\n                            \u003cpackages\u003e\n                                \u003cpackage\u003ejavassist\u003c/package\u003e\n                                \u003cpackage\u003eorg.springframework\u003c/package\u003e\n                            \u003c/packages\u003e\n                        \u003c/imported\u003e\n                        ##开启检查SPI类的功能\n                        \u003ccheckSPI\u003etrue\u003c/checkSPI\u003e\n\t\t\t##在此处排除排除掉不必import的SPI类\n \t\t\t\u003cexcludeCheckedSPIs\u003e\n\t\t\t \t\u003cexcludeCheckedSPI\u003ecom.fasterxml.jackson.core.JsonFactory\u003c/excludeCheckedSPI\u003e\n                                \u003cexcludeCheckedSPI\u003eio.grpc\u003c/excludeCheckedSPI\u003e\n                        \u003c/excludeCheckedSPIs\u003e\n                    \u003c/configuration\u003e\n                \u003c/execution\u003e\n            \u003c/executions\u003e\n        \u003c/plugin\u003e\n    \u003c/plugins\u003e\n\u003c/build\u003e\n```\n\n#### SPI资源文件和相关类的处理\n+ 如果SDK自定义了SPI接口或者实现了其他的SPI接口，这些类都需要配置为exported(一般在exported/packages/package中已经包含，不用额外配置)；\n+ 如果SDK中引用了定义定实现了SPI接口的三方依赖，在启动的时候可能出现SPI接口和实现类不是一个ClassLoader加载的错误，此时建议import此三方依赖的类；\n+ 可以开启checkSPI配置：会扫描所有依赖的SPI文件，如果SPI接口和实现类没被imported/exported则报错；如果确定不影响SDK使用的SPI，可以使用excludeCheckedSPIs/excludeCheckedSPI来跳过某个包或类的检查，具体的配置见上面的配置示例。\n\n#### 多module项目，其中任意个数的modules组合打包\n当一个项目中有多个modules，业务使用方可以根据需要引用其中的一个或者一些module的情况下，按照下面的原则配置\n\n## 业务开发者\n### 引入依赖\n+ 使用Cabin启动项目，应用需要在maven依赖中加入Cabin的依赖：\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eio.esastack\u003c/groupId\u003e\n    \u003cartifactId\u003ecabin-core\u003c/artifactId\u003e\n    \u003cversion\u003e${cabin.version}\u003c/version\u003e\n\u003c/dependency\u003e\n```\n+ 引用框架、中间件、SDK的支持类隔离的maven版本(使用Cabin插件打包的版本)\n以ESA RPC(多模块打包)和ESA RPC Auth(单模块打包)为例:\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eesa\u003c/groupId\u003e\n    \u003cartifactId\u003eesa-rpc-cabin\u003c/artifactId\u003e\n    \u003cversion\u003e${esa.rpc.version}\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eesa\u003c/groupId\u003e\n    \u003cartifactId\u003eesa-rpc-auth\u003c/artifactId\u003e\n    \u003cclassifier\u003e${cabin-classifier}\u003c/classifer\u003e\n    \u003cversion\u003e${esa.rpc.version}\u003c/version\u003e\n\u003c/dependency\u003e\n```\n### 开发\n***在main函数中添加一行代码，这行代码必须在main函数的第一行，在其前面不可以有任何代码。***\n```java\npublic static void main(String[] args) {\n    //此行代码必须添加，且必须在main函数内的第一行，\n    //任何代码、包括打日志均不能在此之前。\n    CabinAppBootstrap.run(args);\n\n    //应用本来需要执行的业务代码，无论是普通应用、Spring、Springboot应用，均如此。\n    ...\n}\n```\n\n### 单元测试\n单元测试时如果包冲突问题也可以用类隔离功能解决，目前只支持junit4.\n\n+ 普通单元测试\n在单元测试用例类上添加@Runwith注解，如下所示：\n```java\n@RunWith(CabinJUnit4Runner.class)\npublic class UnitTest {\n\n    @Test\n    public void curatorTest() {\n       //do test logic\n    }\n}\n```\n\n+ 使用spring-test进行单测\n在单元测试用例类上添加@Runwith注解，如下所示：\n```java\n@RunWith(CabinSpringRunner.class)\n@SpringBootTest\npublic class UnitTest {\n\n    @Test\n    public void curatorTest() {\n       //do test logic\n    }\n}\n```\n\n### 打包发布\n#### zip包发布\n如果用户使用java -classpath xxxApplicaion的方式启动，那么按照以前的打包方式即可。\n\n#### jar包发布\n如果要使用java -jar aaaApp.jar的方式启动，打包发布时需要使用Cabin的打包插件(目前只支持Maven)。\n+ 添加Cabin Maven Plugin\n不能再用springboot的打包插件(spring-boot-maven-plugin)打包，要用下面这个：\n```xml\n\u003cplugin\u003e\n    \u003cgroupId\u003eio.esastack\u003c/groupId\u003e\n    \u003cartifactId\u003ecabin-maven-plugin\u003c/artifactId\u003e\n    \u003cversion\u003e${cabin.version}\u003c/version\u003e\n    \u003cexecutions\u003e\n        \u003cexecution\u003e\n            \u003cid\u003edefault-cli\u003c/id\u003e\n            \u003cgoals\u003e\n                \u003cgoal\u003erepackage\u003c/goal\u003e\n            \u003c/goals\u003e\n\n            \u003cconfiguration\u003e\n\t\t\u003cmainClass\u003e应用主类全限定名\u003c/mainClass\u003e\n            \u003c/configuration\u003e\n        \u003c/execution\u003e\n    \u003c/executions\u003e\n\u003c/plugin\u003e\n```\n\n+ 使用java -jar启动进程\n应用打包后会在target目录下生成*.jar的文件，只要使用java -jar运行此文件即可。\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fesastack%2Fesa-cabin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fesastack%2Fesa-cabin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fesastack%2Fesa-cabin/lists"}