Scala库extensions能否配置“始终Shading”?相关技术问题咨询
背景
我开发了一个Scala库extensions,其扩展方法位于extensions包下,版本迭代较快:1.1版本在1.0基础上新增扩展方法,2.0版本则修改1.0版本的方法签名,完全破坏兼容性。
假设有一个库library(可能由我维护或不受我控制),其1.0.0版本依赖extensions %% 1.0,但仅在方法体或私有方法中使用,无需导出该依赖。我希望library %% 1.0.1(与1.0.0二进制兼容)能够使用extensions %% 1.1甚至2.0版本。我了解到SBT插件sbt-shading可让library将对extensions的依赖设为shaded,即将extensions的所有类迁移至library.extensions.shaded这类仅library内部可访问的包中。
我尚未使用过该插件,但其项目文档显示如何在library中配置对extensions的依赖,使仅library包内的类可访问library.extensions.shaded中的类。这属于使用端shading,需在使用方项目中配置,而非依赖库本身。
现在假设有第三方库third依赖library %% 1.0.0。third的开发者可能不知道library中包含了shaded的extensions库。但由于extensions的类/扩展方法被迁移至library.extensions.shaded而非library包(此举是为避免命名冲突),根据JVM规范,这些类在字节码中是public的。这意味着third的开发者可能会误将library.extensions.shaded中的类当作library的公共API来使用。
任何使用third的项目若改用library %% 1.0.1(合理预期其与1.0.0二进制兼容),在加载third的类时会抛出异常,因为library.extensions.shaded中的类与third编译时依赖的签名不匹配。third的开发者不应被要求必须使用sbt-shading并修改sbt文件来禁止使用library.extensions.shaded,仅允许通过显式依赖使用extensions类。即便third开发者有意或无意避免了此类用法,IDE也无法识别,在编写third代码时仍会推荐导入library.extensions.shaded中的符号,这一问题亟待解决。
问题1:我是否对sbt-shading存在误解?shaded后的类是否会被声明为private[library],以防止third误使用?
默认情况下,sbt-shading不会自动修改类的访问修饰符——它仅负责将目标类的包路径重命名(比如把extensions下的类迁移到library.extensions.shaded),但这些类在字节码中仍然是public的,第三方代码可以直接访问。
不过,sbt-shading支持通过自定义规则调整类的访问权限。你可以在library的SBT配置中添加shadeRules,将shaded后的类设置为仅对library包可见的包私有级别(对应Scala的private[library])。示例配置大致如下:
shadeRules += ShadeRule.rename("extensions.**" -> "library.extensions.shaded.@1") .inAllJars .withAccess("private[library]")
但这种配置需要library的开发者手动设置,并非插件的默认行为。
问题2:能否实现声明端shading,让library开发者只需一行配置即可避免上述问题?
作为extensions库的作者,你可以通过以下几种方式实现目标,无需依赖方编写复杂的SBT配置:
方案1:发布预shaded的extensions变体
预先将extensions的类重命名到内部私有包(比如org.extensions.internal),然后发布一个专门的"shaded"版本,比如extensions-shaded。依赖方只需在library.sbt中添加一行依赖:
libraryDependencies += "org" %% "extensions-shaded" % "1.0"
这种方式下,library引入的是已经处理好的内部版本,类路径不会和其他extensions版本冲突,且第三方无法通过IDE自动提示访问内部包的类(只要你在文档中明确说明这是内部专用依赖)。
方案2:编写轻量SBT插件封装shading逻辑
开发一个极简的SBT插件,封装sbt-shading的所有配置细节。依赖方只需两步:
- 在
plugins.sbt中添加插件:
addSbtPlugin("org" % "sbt-extensions-shade" % "0.1")
- 在
library.sbt中用一行声明依赖:
shadeExtensions("org" %% "extensions" % "1.0")
插件内部自动完成包重命名、访问权限设置等操作,对依赖方完全透明,接近你要求的"一行配置"目标。
方案3:重构extensions的包可见性
在extensions库内部,将所有不稳定的类标记为private[extensions],仅对外暴露稳定的公共API。这样即使依赖方没有做shading,第三方也无法直接访问内部类。但这种方式无法解决版本冲突问题——如果library升级extensions版本,仍可能和其他依赖的extensions版本产生冲突。
内容的提问来源于stack exchange,提问作者Turin

