Jersey 框架如何在 REST 中实现 JAX-RS API?

Posted

技术标签:

【中文标题】Jersey 框架如何在 REST 中实现 JAX-RS API?【英文标题】:How is Jersey framework implement JAX-RS API in REST? 【发布时间】:2017-12-11 11:22:14 【问题描述】:

我知道这个问题有很多答案,但我仍然对 JAX-RS API(规范)和 Jersey 框架(参考实现)之间的区别感到困惑。

我读到了:

Jersey 框架基本上使用 com.sun.jersey.spi.container.servlet.ServletContainer servlet 来拦截所有传入的请求。正如我们在项目 web.xml 中配置的那样,所有传入的休息请求都应由该 servlet 处理。有一个使用 jersey servlet 配置的 init-param 来查找您的 REST 服务类。 REST 服务类不是 Servlet,它们不需要像您在代码中那样扩展 HttpServlet。这些 REST 服务类是简单的 POJO,经过注释告诉 jersey 框架不同的属性,例如路径、消费、生产等。当您从服务方法返回时,jersey 负责将这些对象编组到定义的“PRODUCES”responseType 中并编写它在客户端流上

我的问题是,当您说:“jersey 负责在定义的 'PRODUCES' responseType 中编组这些对象并将其写入客户端流”时,您所说的 jersey 是什么意思,什么是处理对象的实际类或库。

当我读到 jersey 是处理 JAX-RS API 规范的引擎时,我感到很困惑。有人可以解释一下这句话中 jersey 背后的确切含义吗?泽西岛的哪个实际班级负责处理泽西岛的请求和响应?

【问题讨论】:

【参考方案1】:

规范和实现的概念是非常基本的软件工程概念。您的规范是高级设计。为了帮助理解,我只是想出了一个非常简单的例子。

假设我想要一个解析库。我知道我希望如何使用它。唯一的问题是我不太擅长编写解析代码。所以我创建了一个高级规范,并将实施外包。以下是规范中的三个类。它们都包含在一个“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 规范,那么您就会知道处理请求的唯一方法是编写 HttpServletFilter。因此,如果您想实现 JAX-RS,那么您需要能够通过 HttpServletFilter 处理请求。您提到的ServletContainer 实际上是两者。所以对于泽西岛,就请求处理而言,这是泽西岛应用程序的“入口点”。它可以通过多种方式进行配置(我将这项研究留给你)。

如果您了解如何编写自己的 servlet,那么您知道您得到的只是HttpServletRequestHttpServletResponse。你需要弄清楚从那里做什么;从请求中获取请求信息,并在响应中发送回响应信息。泽西岛处理所有这些。

如果您真的想深入了解幕后发生的事情的血腥细节,您只需要从入口点ServletContainer 开始深入研究源代码。准备好花几个月的时间来真正了解它是如何工作的。如果这是您所期望的,那不是可以在一篇 Stack Overflow 帖子中解释的。

【讨论】:

这是有史以来最好的答案!太棒了,伙计。现在我只需要研究它。:)【参考方案2】:

您已经指出 JAX-RS 是一个规范,而 Jersey 是 实现,这就是 Java 尤其是 Java EE 的工作方式,也许这个 article 可以解释更多更好。

总而言之,JAX-RS 只是一个规范,没有真正的实现。真正的实现是由 Jersey 和其他遵循 JAX-RS 规范的库完成的。

【讨论】:

球衣实际是如何实现的,能否请您解释一下工作流程?除了处理请求的 ServletContainer 之外,实际实现还涉及哪些类? 对不起,我没有信心解释球衣的实际实现方式。因为实现 Jersey 的主要是应用服务器,例如 Glassfish、Wildfly 等。但是如果您愿意,可以在 here 上查看有关 JAX-RS 的完整文档。它确实对 JAX-RS 以及如何实现它有完整的解释,你也许可以理解 Jersey 是如何工作的。 好的,但是你能解释一下应用服务器实现一些规范时的工作流吗?

以上是关于Jersey 框架如何在 REST 中实现 JAX-RS API?的主要内容,如果未能解决你的问题,请参考以下文章

JAX-RS (jersey) -> Angular.js 通过 REST

JAX-RS(基于Jersey) + Spring 4.x + MyBatis构建REST服务架构

Jersey JAX-RS REST“getter”方法总是被调用

分布式系统REST风格架构常用技术:Jersey,ApacheCXF,Spring MVC

使用 servlet 在 Java 中实现 REST Web 服务 [重复]

你如何在 django rest 框架中实现 CSRF 令牌?