动态加载实现时如何使接口成为仅编译依赖项
How to make an interface a compile-only dependency when loading its implementation dynamically
考虑以下界面
// src/MyInterface.java
interface MyInterface {
public void quack();
}
在下面的应用中动态使用;即它的实现是动态加载的——出于演示目的,我们将只使用实现 class' 名称来确定要加载哪个实现。
// src/Main.java
class Main {
public static void main(String[] args) {
try {
MyInterface obj = (MyInterface) Class.forName("Implementation")
.getDeclaredConstructor()
.newInstance();
obj.quack();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
以下接口实现可用:
// src/Implementation.java
class Implementation implements MyInterface {
public void quack() {
System.out.println("This is a sample implementation!");
}
}
正如我的直觉所想,MyInterface
提供了仅在编译时相关的信息,例如可以在实现它的对象上调用哪些方法,但在 运行时间,因为它不提供任何 "executable code"。但事实并非如此:如果我尝试 运行 没有 MyInterface.class
的已编译 Main.class
,它会抱怨:
$ javac -d bin/ src/*
$ rm bin/MyInterface.class
$ java -cp bin/ Main
Exception in thread "main" java.lang.NoClassDefFoundError: MyInterface
[...]
我想这是有道理的,因为它需要访问 MyInterface
的 Class
对象来执行到 MyInterface
的转换,所以它需要加载 MyInterface
.但我觉得应该有一种方法可以使它成为仅编译时依赖项。怎么样?
一些上下文
当我了解到可以存在仅编译时依赖项时,出现了这个问题,an example of which is the servlet api。我读到在编译 servlet 代码时,您需要有 servlet-api
(在 Tomcat 的情况下)jar,但在 运行 时不需要它,因为服务器提供了一个实现。由于我不明白它究竟是如何工作的,所以我尝试进行上面的小实验。我是不是误解了什么意思?
编辑: this Gradle page 提到编译时唯一依赖项可能是
Dependencies whose API is required at compile time but whose implementation is to be provided by a consuming library, application or runtime environment.
有什么例子可以说明这一点?我觉得这句话有点令人困惑,因为它似乎暗示 API 在 运行 时 不需要 ,只有实现才是。从答案中,我认为这是不可能的,对吧? (除非以某种方式实现自定义 classloader?)
是的,看起来你误解了 servlet-api.jar
的例子。您在项目中需要它作为编译时依赖项,因为 Tomcat 本身带有该 jar,并且该 jar 将由 Tomcat 添加到 运行time 类路径。
如果您在代码中使用 classes/interfaces,它们应该以某种方式添加到类路径中,因为您的代码依赖于它们。
并且开始 Java 8 个接口可以有方法的默认实现("executable code")并且接口也可以有常量。
也许 运行 应用程序可以不声明接口,但在这种情况下,您需要开发自定义类加载器,它将检查接口实现并加载它而不是接口本身。
Did I misunderstand what that means?
是的。
您在谈论 "provided" 依赖项(至少 that's what Maven calls them)。这种依赖性仍然必须在编译时和运行时存在于 classpath/modulepath 上。但是,您不必在部署应用程序时将提供的依赖项包含在应用程序中,因为目标 container/framework 已经包含依赖项。
考虑以下界面
// src/MyInterface.java
interface MyInterface {
public void quack();
}
在下面的应用中动态使用;即它的实现是动态加载的——出于演示目的,我们将只使用实现 class' 名称来确定要加载哪个实现。
// src/Main.java
class Main {
public static void main(String[] args) {
try {
MyInterface obj = (MyInterface) Class.forName("Implementation")
.getDeclaredConstructor()
.newInstance();
obj.quack();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
以下接口实现可用:
// src/Implementation.java
class Implementation implements MyInterface {
public void quack() {
System.out.println("This is a sample implementation!");
}
}
正如我的直觉所想,MyInterface
提供了仅在编译时相关的信息,例如可以在实现它的对象上调用哪些方法,但在 运行时间,因为它不提供任何 "executable code"。但事实并非如此:如果我尝试 运行 没有 MyInterface.class
的已编译 Main.class
,它会抱怨:
$ javac -d bin/ src/*
$ rm bin/MyInterface.class
$ java -cp bin/ Main
Exception in thread "main" java.lang.NoClassDefFoundError: MyInterface
[...]
我想这是有道理的,因为它需要访问 MyInterface
的 Class
对象来执行到 MyInterface
的转换,所以它需要加载 MyInterface
.但我觉得应该有一种方法可以使它成为仅编译时依赖项。怎么样?
一些上下文
当我了解到可以存在仅编译时依赖项时,出现了这个问题,an example of which is the servlet api。我读到在编译 servlet 代码时,您需要有 servlet-api
(在 Tomcat 的情况下)jar,但在 运行 时不需要它,因为服务器提供了一个实现。由于我不明白它究竟是如何工作的,所以我尝试进行上面的小实验。我是不是误解了什么意思?
编辑: this Gradle page 提到编译时唯一依赖项可能是
Dependencies whose API is required at compile time but whose implementation is to be provided by a consuming library, application or runtime environment.
有什么例子可以说明这一点?我觉得这句话有点令人困惑,因为它似乎暗示 API 在 运行 时 不需要 ,只有实现才是。从答案中,我认为这是不可能的,对吧? (除非以某种方式实现自定义 classloader?)
是的,看起来你误解了 servlet-api.jar
的例子。您在项目中需要它作为编译时依赖项,因为 Tomcat 本身带有该 jar,并且该 jar 将由 Tomcat 添加到 运行time 类路径。
如果您在代码中使用 classes/interfaces,它们应该以某种方式添加到类路径中,因为您的代码依赖于它们。
并且开始 Java 8 个接口可以有方法的默认实现("executable code")并且接口也可以有常量。
也许 运行 应用程序可以不声明接口,但在这种情况下,您需要开发自定义类加载器,它将检查接口实现并加载它而不是接口本身。
Did I misunderstand what that means?
是的。
您在谈论 "provided" 依赖项(至少 that's what Maven calls them)。这种依赖性仍然必须在编译时和运行时存在于 classpath/modulepath 上。但是,您不必在部署应用程序时将提供的依赖项包含在应用程序中,因为目标 container/framework 已经包含依赖项。