前言

项目需要新集成一个sdk进行服务调用,在本地使用 Tomcat 开发调试好好的,更新到服务器上发现不行了,服务都被起不来了。报错 SecurityException: JCE cannot authenticate the provider BC
更换和东方通一样的版本jar后,又报错 java.lang.SecurityException class "org.bouncycastle.crypto.digests.GeneralDigest"'s signer information does not match signer information of other classes in the same package,版本太低又不满足sdk的使用

解决思路

在尝试不同的解决方法后

方式结果
项目依赖的jar进行降级项目报错
置换东方通的jar各种奇葩错
对sdk依赖的jar版本进行降级到与东方通的jar版本一致修改量比较大

同时也看到使用maven打包方式解决 【复盘】bcprov-jdk16包冲突问题(不同版本jar兼容) 以及 maven-shade-plugin的使用 ,可惜我用不了。同时给我提供了一个思路:更换包名

思路有了那就网上看看有没有好用的工具,最终发现一个打包替换工具:jarjar.jar

通过jarjar.jar来替换JAR包名的详细介绍

  • 获取jar
    https://mvnrepository.com/artifact/com.googlecode.jarjar/jarjar这个jar版本比较老,针对有注解的jar包不能处理,java8的函数特性也不能有效处理
    https://mvnrepository.com/artifact/com.eed3si9n.jarjar/jarjar-assembly/1.13.1版本比较新,使用规则和较老的jarjar比较像,放心使用
    使用 java -jar jarjar-1.3.jar 可以查看帮助
houzw@DESKTOP-J9FIROU MINGW64 ~/Downloads/testtt$ java -jar jarjar-1.3.jarJar Jar Links - 一个重新打包和嵌入Java库的工具Copyright 2007 Google Inc.命令行用法:java -jar jarjar.jar [help]打印此帮助消息。java -jar jarjar.jar strings <cp>转储类路径<cp>中的所有字符串字面值。如果类具有调试信息,则将包含行号。java -jar jarjar.jar find <level> <cp1> [<cp2>]打印类路径<cp1>中对类路径<cp2>的依赖项。如果省略<cp2>,则两个参数都使用<cp1>。level参数必须是"class""jar"。前者打印各个类之间的依赖关系,而后者仅打印 jar->jar 依赖关系。这里的"jar"实际上是任何类路径组件,可以是jar文件、zip文件或父目录(见下文)java -jar jarjar.jar process <rulesFile> <inJar> <outJar> 转换jar文件,将新的jar文件写入。任何以。命名的现有文件将被删除。 转换是由rules参数指定的文件中的一组规则定义的(见下文)。类路径的格式:classpath参数是目录、jar文件或zip文件的冒号或分号分隔集(取决于平台)。有关更多详细信息,请参阅以下页面:http://java.sun.com/j2se/1.5.0/docs/tooldocs/solaris/classpath.htmlMustang-style 通配符支持:http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6268383规则文件格式:规则文件是一个文本文件,每行一个规则。前导和尾随空格将被忽略。有三种类型的规则:rule <pattern> <result>zap <pattern>keep <pattern>标准规则("rule")用于重命名类。所有对重命名类的引用也将被更新。如果一个类名与多个规则匹配,则只应用第一个规则。<pattern> 是带有可选通配符的类名。 "**" 将匹配任何有效的类名子字符串。为了匹配单个包装组件(从匹配中排除"."), 可以使用单个"*"代替。<result> 是一个类名,它可以选择性地引用由通配符匹配的子字符串。对<pattern>中的每个"*""**"都有编号引用,从左到右:"@1", "@2",等。一个特殊的"@0"引用包含了整个匹配的类名。"zap" 规则将导致从生成的jar文件中删除任何匹配的类。在重命名规则之前处理所有zap规则。"keep" 规则将所有匹配的类标记为"roots"。如果定义了任何keep规则,那么在编写输出jar时,不能通过依赖分析从根目录访问的所有类都将被丢弃。这是重命名和删除之后的最后一步。

使用jarjar修改sdk和bcprov等jar

编写rule.txt

rule org.bouncycastle.** shade.bouncycastle.@1

替换 package 和 import

houzw@DESKTOP-J9FIROU MINGW64 ~/Downloads/testtt$ java -jar jarjar-1.3.jar process rule.txt bcprov-jdk15to18-1.67.jar shade-bcprov-jdk15to18-1.67.jarhouzw@DESKTOP-J9FIROU MINGW64 ~/Downloads/testtt$ java -jar jarjar-1.3.jar process rule.txt bcpkix-jdk15to18-1.67.jar shade-bcpkix-jdk15to18-1.67.jarhouzw@DESKTOP-J9FIROU MINGW64 ~/Downloads/testtt$ java -jar jarjar-1.3.jar process rule.txt send-api-sdk-1.2.1.jar newsend-api-sdk-1.2.1.jar

最终反编译结果如下


可以发现对package,import 都进行了替换。将替换后的sdk和bcprov给替换到线上,原来冲突的jar给删除,服务正常,到此完结撒花~~

拓展:如果有个性化的修改也可以使用增量更新的方式(一般用不到,此处只是抛砖引玉)

注意:如果只是A.jar依赖B.jar 只是想 A和B的依赖绑定,使用上面的方法对 A.jar 和 B.jar 都修改就可以满足需求,下面的个性化修改场景是,不光对依赖有修改,其中的逻辑也有修改,可以利用 java 同包同名类覆盖的特性进行某个 class 重新编译。
替换原来sdk的jar中class调用替换包名后的加密JAR,具体可以移步《Java项目中 Jar 包增量更新办法,解压修改 Jar 包中的文件后重新打成 Jar 包》

houzw@DESKTOP-J9FIROU MINGW64 ~/Downloads$ cd aaaaaaa/houzw@DESKTOP-J9FIROU MINGW64 ~/Downloads/aaaaaaa$ lltotal 60-rw-r--r-- 1 houzw 197121 58060 Oct7 17:29 send-api-sdk-1.2.1.jarhouzw@DESKTOP-J9FIROU MINGW64 ~/Downloads/aaaaaaa$ jar -xvf send-api-sdk-1.2.1.jar▒Ѵ▒▒▒: META-INF/▒ѽ▒ѹ: META-INF/MANIFEST.MF...houzw@DESKTOP-J9FIROU MINGW64 ~/Downloads/aaaaaaa$ jar -uvf0 send-api-sdk-1.2.1.jar cn

保留替换的class,其他无关的文件都删除掉,执行 jar -uvf0 xxx.jar文件 文件夹使用文件夹jar文件进行增量更新。
更新完后反编译看一下,确实是调用新的方法。

将线上服务再次启动,就没有发现报错了,服务调用也正常。完事儿,撒花~~

总结

当有jar包冲突。且出现了既要又要的情况,需要兼容不同版本的jar,无论是maven方案还是自己修改jar 等都是规避jvm的双亲委派机制,层级越高的类加载器越先加载其加载路径下的类。如maven最短依赖路径、自己修改包名等。
所以当上述既要又要的情况,那就将按版本进行包名和import替换无疑是比较好的解决方法,只需要执行命令行一个全新的一套jar就完成了。jarjar.jar无疑也是首要选择

最后祝大家玩的开心!!!