背景:项目是通过jenkis打包,通过K8S更新,从开发环境推送到测试环境,再推送UAT环境,最后推送生产环境。项目中与某银行对接,银行给的SDK包在测试,UAT,生产环境上都不一样,先把不同的包分别增加以test,uat,prod后缀命名artifactId上传到maven私库。

方案一:修改pom文件依赖,每次都从开发环境一路更新到对应的环境,此方案的缺点有2个,第一点是很麻烦,每次都得改pom文件依赖,得重新打包更新。第二点是生产环境风险比较大,很大可能上线的时候忘记修改相应依赖重新打包推送而导致生产环境的依赖的SDK不是生产的包,导致出现生产事故。

方案二:通过Maven 不同环境引用不同的依赖和配置,如下

<profiles>
    <!-- 测试环境 -->
    <profile>
        <id>dev</id>
        <!-- 默认激活 dev 环境 -->
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <properties>
            <profiles.active>dev</profiles.active>
        </properties>
        <dependencies>
            <!-- pay_provider -->
            <dependency>
                <groupId>com.test</groupId>
                <artifactId>test-test</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>

        </dependencies>
    </profile>

    <!-- UAT环境 -->
    <profile>
        <id>UAT</id>
        <properties>
            <profiles.active>UAT</profiles.active>
        </properties>
        <dependencies>
            <dependency>
                <groupId>com.test</groupId>
                <artifactId>test-uat</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
        </dependencies>
    </profile>

    <!-- 生产环境 -->
    <profile>
        <id>prod</id>
        <properties>
            <profiles.active>prod</profiles.active>
        </properties>
        <dependencies>
            <dependency>
                <groupId>com.test</groupId>
                <artifactId>test-prod</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
        </dependencies>
    </profile>
</profiles>

然后在jenkis配置中通过如下类似的命令进行不同环境的打包

## 测试环境打包
mvn clean package -P dev
 
## uat环境打包
mvn clean package -P uat
 
## 生产环境打包
mvn clean package -P prod

方案二解决了方案一中要修改POM文件的问题,但每次不同环境得修改jenkis的打包配置,且还是可能导致生产环境风险比较大。

方案三:通过JAVA类加载器的双亲委派机制来解决,因为双亲委派机制,相同路径的类加载后就不会加载了,所以思路是让父类或者应用类加载器去加载自己需要的类或者文件。

目前java提供了三个ClassLoader:

1,BootstrapClassLoader

用于加载JAVA核心类库,也就是环境变量的%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar等。

在JVM启动时加入-Xbootclasspath参数,可以把对应路径也加载到Bootstrap的路径列表中来。

-Xbootclasspath: 完全取代基本核心的Java class 搜索路径,不常用,否则要重新写所有Java 核心class。
-Xbootclasspath/a: 后缀在核心class搜索路径后面,常用!!
-Xbootclasspath/p: 前缀在核心class搜索路径前面.不常用,避免引起不必要的冲突。

详细的两种用法:

1),-Xbootclasspath/a:{人工指定路径},把对应路径加载到Bootstrap默认路径后面,也就是说,如果有class重复,以Bootstrap默认路径下的类为准(因为是按照路径列表顺序加载的),举例:

java -Xbootclasspath/a:D:\extjar\testJar-2.0-SNAPSHOT.jar

2),-Xbootclasspath/p: {人工指定路径},把对应路径加载到Bootstrap默认路径后面,也就是说,如果有class重复,以指定路径下的类为准,举例:

java -Xbootclasspath/p:D:\extjar\testJar-2.0-SNAPSHOT.jar

2,Extention ClassLoader

扩展类加载器,加载环境变量%JRE_HOME%\lib\ext目录下的class文件

这个加载器也可以在JVM启动时使用参数改变加载的行为,参数是-D java.ext.dirs=,作用是替换Java扩展类加载器所加载的文件目录。

注意,该参数是替换而不是追加,因为这个加载器的加载路径只有一个,也就是说,%JRE_HOME%\lib\ext是扩展类加载器的默认路径,如果我们在启动时使用-Djava.ext.dirs=d:/test,那么java就不再加载%JRE_HOME%\lib\ext路径下的文件。

3,AppclassLoader

加载classpath中的class类,通过在JVM启动命令中的-classpath参数指定路径,可以指定绝对路径、相对路径、环境变量等,举例:

java –classpath %CLASSPATH%

通过上面的知识,解决方法有如下3种

提前准备如下

1.在项目中pom依赖为生产的SDK包,保证生产环境依赖的SDK包永远是对的。

2.在测试,UAT环境中分别放上需要的SDK包。

一、AppclassLoader用来加载项目中的类,因为不同包中相同路径的类的加载顺序不能保证,但可以通过-classpath配置来处理,所以可以-classpath SDK包路径:项目执行包  这样的配置来让AppclassLoader优先加载SDK包来处理。

二、ExtentionClassLoader加载路径的修改不是追加的形式而是变更,所以如果要用ExtentionClassLoader去加载,只能把相应的SDK包放到%JRE_HOME%\lib\ext路径目录去,但有可能此服务器上也部署了其它的JAVA应用,可能会有影响,所以此方法不太推荐。

三、BootstrapClassLoader有追加路径的配置,所以我们可以通过BootstrapClassLoader加载我们的包来处理,解决办法是:

-Xbootclasspath/a:SDK包路径,让不同环境加载到不同的包,生产环境不需要加直接依赖打到项目中的生产SDK包就可以。

此方案不用修改代码和配置,也可以保证生产环境的包是对的。

如果依赖的SDK包有依赖其他不属于JRE包的第三方包的类,那用以上BootstrapClassLoader追加路径的方法会报 java.lang.NoClassDefFoundError错误,这是因为SDK的类是通过BootstrapClassLoader加载的,而SDK包依赖的其它的类是通过AppclassLoader加载的,所以会报错,此情况下,则只能通过AppclassLoader去加载需要的SDK包,即通过第一种去处理。

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐