规范和实现的概念是非常基本的软件工程概念。您的规范是高级设计。为了帮助理解,我只是想出了一个非常简单的例子。
假设我想要一个解析库。我知道我希望如何使用它。唯一的问题是我不太擅长编写解析代码。所以我创建了一个高级规范,并将实施外包。以下是规范中的三个类。它们都包含在一个“API jar”中,例如 myparsers-api.jar
public interface Parser {
String[] parse(String s);
}
public interface ParserFactory {
Parser getBySpaceParser();
Parser getByCommaParser();
}
public class ParserDepot {
private static ServiceLoader<ParserFactory> loader
= ServiceLoader.load(ParserFactory.class);
public static ParserFactory getDefaultParserFactory() {
final List<ParserFactory> factories = new ArrayList<>();
loader.forEach(factories::add);
if (factories.isEmpty()) {
throw new IllegalStateException("No ParserFactory found");
}
return factories.get(0);
}
}
所以在这一点上,我实际上可以针对这个 jar 进行编码。如果我现在在另一个项目中使用它,该项目将编译得很好。
ParserFactory factory = ParserDepot.getDefaultParserFactory();
Parser parser = factory.getBySpaceParser();
String[] tokens = parser.parse("Hello World");
System.out.println(Arrays.toString(tokens));
因此,即使该规范没有实现,我仍然可以针对它进行编码,并针对它进行编译。但是当我尝试实际运行该程序时,它不会工作,因为没有实现。您可以尝试运行此代码,您将得到一个IllegalStateException(如果您不熟悉此模式,请参阅the docs for ServiceLoader)。
所以我将实施外包给了一家名为 Stack Overflow 的公司。他们得到了我的 myparsers-api.jar,他们需要给我一个实现。他们需要实现一个ParserFactory 和几个Parsers。它们可能看起来像这样
public class SoByCommaParser implements Parser {
@Override
public String[] parse(String s) {
return s.split("\\s+,\\s+");
}
}
public class SoBySpaceParser implements Parser {
@Override
public String[] parse(String s) {
return s.split("\\s+");
}
}
public class SoParserFactory implements ParserFactory {
@Override
public Parser getBySpaceParser() {
return new SoBySpaceParser();
}
@Override
public Parser getByCommaParser() {
return new SoByCommaParser();
}
}
现在 Stack Overflow 给我一个 jar(比如 so-myparsers-impl.jar),里面有这三个类和所需的 META-INF/services 文件(根据 ServiceLoader 模式),现在当我将 so-myparsers-impl.jar 添加到我的项目并尝试再次运行它时,程序现在可以运行了,因为它现在有一个实现。
这正是 JAX-RS 规范的工作方式。它只定义了它应该如何工作的高级设计。作为该设计一部分的类、接口和注释被放置在一个“API jar”中,就像我的高级解析器被放置在一个 jar 中一样。实现不能改变这些类。所有属于 JAX-RS 规范(版本 2.x)的类都放入一个单独的 jar javax.ws.rs-api。您可以针对该 jar 进行编码,并且您的代码将编译得很好。但是没有什么可以让它“工作”。
查看the written specification 和classes defined by the specification,您会注意到源代码中包含的唯一类是规范中提到的类。但是你应该注意到的是,书面规范根本没有提到它应该如何实现。以下面的代码为例
@Path("/test")
public class TestResource {
@GET
public String get() {
return "Testing";
}
}
@ApplicationPath("/api")
public class MyApplication extends Application {
@Override
public Set<Class<?>> getClasses() {
Set<Class<?>> classes = new HashSet<>();
classes.add(TestResource.class);
return classes;
}
}
现在规范指出,这就是我们在 servlet 容器中运行 JAX-RS 应用程序所需的全部内容。这就是它所说的。它没有说明这一切应该如何工作。这就是它设计的工作方式。
那么,Java 中是否有一些我们不知道的魔法巫术会使这个Application 类启动一个服务器,以及一些使@Path 注释类自动接受请求的恶作剧。不。有些机构需要提供引擎。引擎可能是 20,000 行代码,只是为了使上述代码按规定工作。
话虽如此,Jersey 只是一个实现的名称。就像我将解析器实现外包给 Stack Overflow 时一样; Jersey 这个名称本身就是项目的名称,就像 Hadoop 是项目的名称一样。在这种情况下,项目是 JAX-RS 规范的实现。而且因为 JAX-RS 只是一个规范,这意味着 任何人 都可以实现它。如果您愿意,您可以编写自己的实现。只要它按照书面规范中定义的方式工作,那么您可以说您的代码是 JAX-RS 的实现。那里不仅有泽西岛;你还有RESTEasy,这是另一个实现。
就如何 Jersey 实现引擎而言,这太宽泛了。我能做的就是让您对幕后发生的事情有一个高层次的了解。
JAX-RS 应用程序被定义为在 servlet 容器内运行。如果您了解 servlet 容器和 servlet 规范,那么您就会知道处理请求的唯一方法是编写 HttpServlet 或 Filter。因此,如果您想实现 JAX-RS,那么您需要能够通过 HttpServlet 或 Filter 处理请求。您提到的ServletContainer 实际上是两者。所以对于泽西岛,就请求处理而言,这是泽西岛应用程序的“入口点”。它可以通过多种方式进行配置(我将这项研究留给你)。
如果您了解如何编写自己的 servlet,那么您知道您得到的只是HttpServletRequest 和HttpServletResponse。你需要弄清楚从那里做什么;从请求中获取请求信息,并在响应中发送回响应信息。泽西岛处理所有这些。
如果您真的想深入了解幕后发生的事情的血腥细节,您只需要从入口点ServletContainer 开始深入研究源代码。准备好花几个月的时间来真正了解它是如何工作的。如果这是您所期望的,那不是可以在一篇 Stack Overflow 帖子中解释的。