【问题标题】:How to use Spring Boot 1.5.2 to inject implementations of Jersey endpoint interfaces?如何使用 Spring Boot 1.5.2 注入 Jersey 端点接口的实现?
【发布时间】:2017-04-06 22:42:14
【问题描述】:

在 Spring Boot 1.5.2 上,我正在缩减一个包含 Web 服务的大型 Web 应用程序,使其仅成为 Jersey Web 服务。因为 Web 服务已经有一套完整的由 Apache Wink 实现的 JAX-RS 注释,所以我决定使用 Spring + Jersey 而不是 Spring Rest。我发现这个spring-boot-jersey-sample 应用程序可以用作参考。我正在开发的应用程序与示例之间的最大区别在于我的端点定义分为接口和实现。

我在 pom.xml 中添加了以下内容:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jersey</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <scope>provided</scope>
    </dependency>

我的新泽西配置如下所示:

package com.example.configuration;

import org.glassfish.jersey.server.ResourceConfig;
import com.example.EndpointImpl;
import org.springframework.stereotype.Component;

@Component
public class JerseyConfiguration extends ResourceConfig {
  public JerseyConfiguration() {
    registerEndpoints();
  }

  private void registerEndpoints() {
    register(EndpointImpl.class);     
  }
}

然后我有以下Application.java:

package com.example;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;

@SpringBootApplication
public class Application extends SpringBootServletInitializer{
  public static void main(String[] args) {
    new Application().configure(new SpringApplicationBuilder(Application.class)).run(args);
  }
}

端点被定义为接口和实现,如下所示(减去导入):

public interface Endpoint {
  @GET
  @Produces({MediaType.APPLICATION_JSON})
  public Response getHello(@Context ServletContext sc, @Context HttpServletRequest req, @Context HttpHeaders httpHeaders) ;
}
@Path("")
@Component
public class EndpointImpl implements Endpoint {
  @Override
  public Response getHello(@Context ServletContext sc, @Context HttpServletRequest req,
      @Context HttpHeaders httpHeaders)  {
      return Response.ok("hello").build();
  }
}

当我启动我的应用程序时,我看到消息说 Tomcat 已启动,包括一个消息说 Mapping servlet: 'com.example.configuration.JerseyConfiguration' to [/*]。但是,当我使用 Web 浏览器访问 / 时,我收到 404 Not Found 错误。看起来 GET 定义并没有被采纳。

【问题讨论】:

    标签: spring spring-boot jersey jax-rs


    【解决方案1】:

    第 3.6 节注释继承中的 JAX-RS spec 解释了此问题。

    JAX-RS 注解可用于超类或已实现接口的方法和方法参数。此类注解由相应的子类或实现类方法继承前提是该方法及其参数本身没有任何 JAX-RS 注解。

    如果子类或实现方法有任何 JAX-RS 注释,则忽略超类或接口方法上的所有注释。例如:

    public interface ReadOnlyAtomFeed {
      @GET @Produces("application/atom+xml")
      Feed getFeed();
    }
    
    @Path("feed")
    public class ActivityLog implements ReadOnlyAtomFeed {
      public Feed getFeed() {...}
    }
    

    上面ActivityLog.getFeed从接口继承了@GET@Produces注解。

    反之:

    @Path("feed")
    public class ActivityLog implements ReadOnlyAtomFeed {
      @Produces("application/atom+xml")
      public Feed getFeed() {...}
    }
    

    在上面,ReadOnlyAtomFeed.getFeed 上的 @GET 注释不被 ActivityLog.getFeed 继承,它需要自己的请求方法指示符 (@GET),因为它重新定义了 @Produces 注释。

    为了与其他 Java EE 规范保持一致,建议始终重复注解,而不是依赖注解继承。

    我已经强调了重要的端口。应该很清楚为什么它不适合你。在您的EndpointImpl 中,您重复了@Context 注释,因此导致“忽略超类或接口方法上的所有注释”。这包括@GET。所以最终,这会导致该端点没有被注册,因为端点需要@METHOD

    至于块引用中的最后一段,您可以选择是否遵循它。我只是为了完整性把它扔在那里。

    【讨论】:

    • 这似乎是主要的罪犯。现在的问题是,我是不是应该把所有的注解都放在接口上,让以后的程序员陷入在实现中加注解破坏服务的陷阱?或者我应该在接口和实现上放置相同的注释,让他们在两个地方都维护它们的痛苦。
    • 就我个人而言,我一直不明白为什么人们会选择使用接口。我真的没有看到太多好处。如果您曾经使用过 Spring,那么您永远不会看到有人使用控制器接口。我可以理解将抽象类与公共端点的基本实现一起使用(无注释继承)。但是接口,我真的不明白。所以我没有给你答案:-(
    • 是的,我已经剥离了接口。我想这个项目的未来开发者会感谢我。
    • 即使在实现带注释接口的类中没有单个 jax-rs 注释时,它对我也不起作用......
    • @mirec 您仍然需要实现类上的@Path。除此之外,如果没有看到一些代码,真的无能为力。如果您想发布一个新问题,我会很高兴看到它。
    【解决方案2】:

    你的 application.yml (.properties) 是什么样子的:

    您可能需要声明两个路径映射,一个用于Spring MVC 调度程序servlet,一个用于Jersey 调度程序servlet。比如:

    application.yml

    ...
    # Spring MVC dispatcher servlet path. Needs to be different than Jersey's to enable/disable Actuator endpoints access (/info, /health, ...)
    server.servlet-path: /
    # Jersey dispatcher servlet
    spring.jersey.application-path: /api
    ...
    

    您现在应该可以访问位于 /api/..... 的 Jersey 端点了

    我在 2016 年 4 月发表的一篇博文中对此进行了介绍:Microservices using Spring Boot, Jersey, Swagger and Docker

    【讨论】:

    • 我认为这可能是我也遇到的次要问题!我没有冲突的 Spring dispatcher-servlet,因为我删除了 Spring Boot webapp starter,并且似乎不再插入 Spring dispatcher-servlet。但是,我认为我正在缩小的这个应用程序中的一些代码期望应用程序路径不在根目录。我会进一步调查。
    【解决方案3】:

    除非在最近的 Spring Boot/Spring MVC + Jersey 版本中有所改变,否则要让 Spring Boot 识别 Jersey 端点,您需要像这样使用扩展 Jersey 的 ResourceConfig 的附加 @Component 注册它:

    @Component
    public class JerseyExampleConfig extends ResourceConfig {
    
        public JerseyExampleConfig() {
            register(EndpointImpl.class);
        } 
    }
    

    【讨论】:

    • 奇怪的是,这看起来与我的问题(第二个代码块)中的 Jersey Config 几乎相同,只是我调用了一个方法 registerEndpoints(),它调用了 register 方法。
    • 你是对的,不知道为什么我在回答时没有发现,但我想你已经意识到这是必需的:-)
    猜你喜欢
    • 2019-04-09
    • 1970-01-01
    • 1970-01-01
    • 2015-07-18
    • 1970-01-01
    • 2019-11-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多