Skip to content

ESA Cabin is a lightweight class isolation container for Java applications.

License

Notifications You must be signed in to change notification settings

Mkabaka/esa-cabin

This branch is 74 commits behind esastack/esa-cabin:main.

Folders and files

NameName
Last commit message
Last commit date
May 14, 2021
May 8, 2021
May 8, 2021
May 8, 2021
May 8, 2021
May 8, 2021
May 8, 2021
May 8, 2021
May 8, 2021
May 8, 2021
May 8, 2021
May 8, 2021
May 8, 2021
May 8, 2021
May 8, 2021
May 8, 2021
May 8, 2021
May 8, 2021

Repository files navigation

Cabin

Build codecov Maven Central GitHub license

Cabin是一款Java类隔离框架,用于隔离各个模块之间、模块和业务之间使用的第三方依赖库。使用Cabin后,模块SDK将只是用自己指定 版本的三方依赖,不会被业务引入的依赖所影响,保证业务正常运行,提高业务的开发效率,减少中间件同学此类重复的支持工作。

概述

ESA Cabin用于解决模块间类冲突。要解决的问题是,当一个应用项目中使用到了多个框架或者SDK(后续称为LibModule)时, 各个LibModule之间、LibModule和应用业务代码(后续称为BizModule)之间可能存在包冲突,即“使用了三方依赖的不兼容的不同版本”。 业务遇到包冲突的时候,需要进行进行复杂的排包工作和兼容性实验,以期待找到一个能兼容各个模块的依赖版本;当LibModule使用 了不兼容的三方依赖版本发布的时候,还需要多方协调。一旦遇到依赖冲突,将严重影响开发效率和体验。

举例说明依赖冲突。假设如下场景,如果工程需要引入两个框架:FrameworkA 和 FrameworkB,其中 FrameworkA 需要依赖版本号为 1.0.0 的 DependencyC, FrameworkB 需要依赖版本号为 2.0.0 的 DependencyC,而这两个版本无法兼容(例如类名、方法名、参数有 变化或者增删):

image.png

为了彻底解决包冲突的问题,需要借助类隔离机制,将各个Framework打包为一个LibModule,使用各自的 ClassLoader 加载,业务代码 使用单独的ClassLoader加载,达到相互隔离的目的:

image.png

Quick Start

LibModule开发者

框架、中间件、SDK可以打包为特定的格式,使用Cabin容器来和业务代码进行类隔离。

打包

目前只支持maven打包。

打包插件

  • 使用cabin-module-maven-plugin插件打包 具体使用方式参考下文举例。

单module项目打包

将一个module及其依赖打包。此种打包方式需要指定classifier(例子里有说明),用户根据classifier来区分使用fatjar和普通jar。 此种打包方式会将module的所有maven依赖传递给使用者,使用者可以任意update/exclude这些依赖,不会影响module中的代码。
用esa-rpc-filter这个module举例:

...
<artifactId>esa-rpc-cluster</artifactId>
...
<dependencies>
    ...
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>io.esastack</groupId>
            <artifactId>cabin-module-maven-plugin</artifactId>
            <version>${cabin.version}</version>
            <executions>
                <execution>
                    <id>default-cli</id>
                    <goals>
                        <goal>module-repackage</goal>
                    </goals>
                    <configuration>
                        ## 一定要指定,否则会覆盖原有的标准jar包
			<classifier>cabin-module</classifier>
			## 需要导出的类、包、资源,一般使用package即可,资源一般是SPI的配置文件,Spring的xsd和spring.fatories文件等。
                        <exported>
                            <packages>
                                <package>esa.rpc.cluster</package>
                                <package>esa.commons.serialize</package>
                            </packages>
                            <resources>
                                <resource>META-INF/services/</resource>
                            </resources>
                        </exported>

                        ##imported的类是指被打包的模块需要优先从业务classpath中加载的类,比如Spring一般是使用业务的依赖;如果模块需要通过方法的参数和返回值传递三方依赖的类和对象,也建议使用import的方式从业务classpath加载这些类(例如需要传递netty的ChannelHandlerContext);提供了SPI接口的三方包,也建议从biz中import。
                        <imported>
                            <packages>
                                <package>javassist</package>
                                <package>org.springframework</package>
                            </packages>
                        </imported>
			##不影响使用的SPI,可以不import,在此处排除
 			<excludeCheckedSPIs>
			 	<excludeCheckedSPI>com.fasterxml.jackson.core.JsonFactory</excludeCheckedSPI>
                                <excludeCheckedSPI>io.grpc</excludeCheckedSPI>
                        </excludeCheckedSPIs>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

多module项目合并打包成All-In-One的FatJar

如果要将多个module项目合并打包成一个FatJar, 具体步骤如下:

  • 创建一个新module来依赖需要合并在一起的module,如xxx-cabin,在其pom中依赖此项目的所有其他module和额外需要的三方依赖;
  • 在xxx-cabin的pom中加入cabin-module-maven-plugin插件,并配置相关信息

下面用ESA RPC举例讲解如何打包: + esa-rpc-cabin (会传递三方依赖) ```xml ...加入需要合并打包的module和依赖 io.esastack cabin-module-maven-plugin ${cabin.version} default-cli module-repackage ### 需要导出的类、包、资源,一般使用package即可,资源一般是SPI的配置文件,Spring的xsd和spring.fatories文件等。 esa.rpc esa.commons.serialize esa.rpc.filter.consumer.Async2SyncFlter # META-INF/services/ META-INF/esa/ META-INF/esa-rpc.xsd META-INF/spring.* esa/rpc/spring/boot/autoconfigure/ESABeanConfiguration.class esa/rpc/spring/boot/autoconfigure/banner/ESAWelcomeLogoApplicationListener.class
                    #imported的类是指被打包的模块需要优先从业务classpath中加载的类,比如Spring一般是使用业务的依赖;如果模块需要通过方法的参数和返回值传递三方依赖的类和对象,也建议使用import的方式从业务classpath加载这些类(例如需要传递netty的ChannelHandlerContext);提供了SPI接口的三方包,也建议从biz中import。
                    <imported>
                        <packages>
                            <package>javassist</package>
                            <package>org.springframework</package>
                        </packages>
                    </imported>
                    ##开启检查SPI类的功能
                    <checkSPI>true</checkSPI>
		##在此处排除排除掉不必import的SPI类
		<excludeCheckedSPIs>
		 	<excludeCheckedSPI>com.fasterxml.jackson.core.JsonFactory</excludeCheckedSPI>
                            <excludeCheckedSPI>io.grpc</excludeCheckedSPI>
                    </excludeCheckedSPIs>
                </configuration>
            </execution>
        </executions>
    </plugin>
</plugins>
```

SPI资源文件和相关类的处理

  • 如果SDK自定义了SPI接口或者实现了其他的SPI接口,这些类都需要配置为exported(一般在exported/packages/package中已经包含,不用额外配置);
  • 如果SDK中引用了定义定实现了SPI接口的三方依赖,在启动的时候可能出现SPI接口和实现类不是一个ClassLoader加载的错误,此时建议import此三方依赖的类;
  • 可以开启checkSPI配置:会扫描所有依赖的SPI文件,如果SPI接口和实现类没被imported/exported则报错;如果确定不影响SDK使用的SPI,可以使用excludeCheckedSPIs/excludeCheckedSPI来跳过某个包或类的检查,具体的配置见上面的配置示例。

多module项目,其中任意个数的modules组合打包

当一个项目中有多个modules,业务使用方可以根据需要引用其中的一个或者一些module的情况下,按照下面的原则配置

业务开发者

引入依赖

  • 使用Cabin启动项目,应用需要在maven依赖中加入Cabin的依赖:
<dependency>
    <groupId>io.esastack</groupId>
    <artifactId>cabin-core</artifactId>
    <version>${cabin.version}</version>
</dependency>
  • 引用框架、中间件、SDK的支持类隔离的maven版本(使用Cabin插件打包的版本) 以ESA RPC(多模块打包)和ESA RPC Auth(单模块打包)为例:
<dependency>
    <groupId>esa</groupId>
    <artifactId>esa-rpc-cabin</artifactId>
    <version>${esa.rpc.version}</version>
</dependency>
<dependency>
    <groupId>esa</groupId>
    <artifactId>esa-rpc-auth</artifactId>
    <classifier>${cabin-classifier}</classifer>
    <version>${esa.rpc.version}</version>
</dependency>

开发

在main函数中添加一行代码,这行代码必须在main函数的第一行,在其前面不可以有任何代码。

public static void main(String[] args) {
    //此行代码必须添加,且必须在main函数内的第一行,
    //任何代码、包括打日志均不能在此之前。
    CabinAppBootstrap.run(args);

    //应用本来需要执行的业务代码,无论是普通应用、Spring、Springboot应用,均如此。
    ...
}

单元测试

单元测试时如果包冲突问题也可以用类隔离功能解决,目前只支持junit4.

  • 普通单元测试 在单元测试用例类上添加@Runwith注解,如下所示:
@RunWith(CabinJUnit4Runner.class)
public class UnitTest {

    @Test
    public void curatorTest() {
       //do test logic
    }
}
  • 使用spring-test进行单测 在单元测试用例类上添加@Runwith注解,如下所示:
@RunWith(CabinSpringRunner.class)
@SpringBootTest
public class UnitTest {

    @Test
    public void curatorTest() {
       //do test logic
    }
}

打包发布

zip包发布

如果用户使用java -classpath xxxApplicaion的方式启动,那么按照以前的打包方式即可。

jar包发布

如果要使用java -jar aaaApp.jar的方式启动,打包发布时需要使用Cabin的打包插件(目前只支持Maven)。

  • 添加Cabin Maven Plugin 不能再用springboot的打包插件(spring-boot-maven-plugin)打包,要用下面这个:
<plugin>
    <groupId>io.esastack</groupId>
    <artifactId>cabin-maven-plugin</artifactId>
    <version>${cabin.version}</version>
    <executions>
        <execution>
            <id>default-cli</id>
            <goals>
                <goal>repackage</goal>
            </goals>

            <configuration>
		<mainClass>应用主类全限定名</mainClass>
            </configuration>
        </execution>
    </executions>
</plugin>
  • 使用java -jar启动进程 应用打包后会在target目录下生成*.jar的文件,只要使用java -jar运行此文件即可。

About

ESA Cabin is a lightweight class isolation container for Java applications.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Java 100.0%