TL;DR 没有一种灵丹妙药的解决方案,但可以利用许多不同的工具
有多种不同的技术可以隔离软件应用程序的不同部分,但我认为没有任何一种解决方案可以解决所有问题。一些构建系统可以限制目标之间的依赖关系(例如,Bazel在构建目标上有一个visibility
属性,可以防止一个目标依赖于另一个目标,即使它们通过 Java 的类可见性彼此可见),可以与 Java 的内置函数结合使用能见度。例如:
// Foo.java
package com.yourcompany.foo;
public class Foo {}
// Build rule for Foo.java
java_library(
name = "Foo",
srcs = ["Foo.java"],
# Restricts visibility to this directory, even though
# the class visibility was "public"
visibility = ["//visibility:private"],
)
// Bar.java
package com.yourcompany.bar;
import com.yourcompany.foo.Bar; // prevented by build visibility system
public class Bar {
Foo foo = new Foo();
}
也可以使用接口来调解逻辑组件之间的所有交互并隐藏这些接口的实现(例如,仅通过服务注册接口或通过接口依赖注入来公开实现)。例如,使用Dagger,您可以为每一层创建一个单独的组件,这将允许您编写如下代码:
final class ControllerImpl implements Controller {
// Since "ControllerImpl" is instantiated / wired into the
// controller layer, the database dependency is available /
// exposed for injection within this layer. The access control is
// strictly performed by the way the dependencies are wired.
@Inject
public ControllerImpl(Database database) {
// ...
}
}
除了上述之外,您还可以使用依赖分析/依赖分析测试或提交挂钩来自动检测依赖规则违规(并根据它们触发错误/拒绝提交)。例如,穷人的解决方案是简单地扫描每个文件的包声明和导入语句,然后使用一些启发式方法来检测不良依赖关系。
另一种方法是将不同的组件捆绑在单独的 JAR 中并使用自定义ClassLoader加载它们,这将允许您使用反射防止非法访问(否则可能会绕过任何程序结构)。
除了自动化方法之外,手动方法也有其价值。手动方法包括定期代码审查和在这些代码审查和审计期间强制执行的策略。
简而言之,没有一个正确的答案。有必要结合使用几种不同的方法,具体取决于这种分离的重要性。