Spring 引导 JAX-RS/CXF 依赖项注入在 JAR 中有效,但在 WAR 中无效

Spring Boot JAX-RS/CXF dependency injection works in JAR but not WAR

我正在用 Spring 使用 cxf.jaxrs.classes-scan and cxf.jaxrs.classes-scan-packages 的引导应用程序包装现有的普通 JAX-RS 应用程序 JAR。当我 运行 作为 JAR 或 maven spring-boot:run 时,依赖注入工作正常。当我 运行 作为 WAR(在 WebSphere Liberty 17.0.0.2 上)时,@Inject-able 字段在 REST 请求期间是 null

这是 SpringBootApplication:

@SpringBootApplication(scanBasePackages = { "com.test" })
public class CustomerServiceApplication extends SpringBootServletInitializer {
  public static void main(String[] args) {
    SpringApplication.run(CustomerServiceApplication.class, args);
  }

  @Override
  protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
    return application.sources(CustomerServiceApplication.class);
  }
}

这里是src/main/resources/application.properties

cxf.path=/
cxf.jaxrs.classes-scan=true
cxf.jaxrs.classes-scan-packages=com.test,com.fasterxml.jackson.jaxrs.json

这是 Maven pom.xml(原始 JAX-RS 应用程序 JAR 是 customerservice-java,它位于本地存储库中):

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>customerservice-springboot</groupId>
    <artifactId>customerservice-springboot</artifactId>
    <packaging>war</packaging>
    <version>2.0.0-SNAPSHOT</version>
    <name>Customer Service :: Spring Boot</name>

    <!-- We need to use cxf-spring-boot-starter-jaxrs 3.2.0 because of https://issues.apache.org/jira/browse/CXF-7237 
        At the time of writing this code, the latest available version in Maven central 
        is 3.1.7 so we need to use the Apache snapshot repository. -->
    <repositories>
        <repository>
            <id>apache.snapshots</id>
            <name>Apache Development Snapshot Repository</name>
            <url>https://repository.apache.org/content/repositories/snapshots/</url>
            <releases>
                <enabled>false</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
    </repositories>

    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.4.RELEASE</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-spring-boot-starter-jaxrs</artifactId>
            <version>3.2.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.json</groupId>
            <artifactId>javax.json-api</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>1</version>
        </dependency>
        <dependency>
            <groupId>customerservice-java</groupId>
            <artifactId>customerservice-java</artifactId>
            <version>2.0.0-SNAPSHOT</version>
            <classifier>jar</classifier>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.jaxrs</groupId>
            <artifactId>jackson-jaxrs-json-provider</artifactId>
        </dependency>
        <dependency>
            <groupId>org.glassfish</groupId>
            <artifactId>javax.json</artifactId>
            <version>1.0.4</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

这是 JAR 项目中的 Web 服务:

package com.test;
import javax.inject.Inject;
import javax.ws.rs.CookieParam;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;

@Path("/")
public class CustomerServiceRest {

  @Inject
  CustomerService customerService;

  @GET
  @Path("/byid/{custid}")
  @Produces("text/plain")
  public Response getCustomer(@PathParam("custid") String customerid, @CookieParam("token") String jwtToken) {
      return Response.ok(customerService.getCustomerId(customerid)).build();
  }
}

这是豆子:

package com.test;
import javax.inject.Named;
@Named
public class CustomerService {
  public String getCustomerById(String username) {
    // ... implementation ..
    return customerDoc.toJson();
  }
}

这是 WAR 日志输出:

  .   ____          _            __ _ _
 /\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.4.RELEASE)
INFO  c.a.s.CustomerServiceApplication - Starting CustomerServiceApplication on 23fb5f5646c3 with PID 20 (/opt/ibm/wlp/usr/servers/defaultServer/apps/expanded/customerservice-springboot-2.0.0-SNAPSHOT.war/WEB-INF/classes started by root in /opt/ibm/wlp/output/defaultServer)
INFO  c.a.s.CustomerServiceApplication - No active profile set, falling back to default profiles: default
INFO  o.s.b.c.e.AnnotationConfigEmbeddedWebApplicationContext - Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@67e7ebcd: startup date [Wed Jul 19 19:36:12 UTC 2017]; root of context hierarchy
INFO  o.s.b.f.x.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [META-INF/cxf/cxf.xml]
INFO  o.s.b.f.a.AutowiredAnnotationBeanPostProcessor - JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
INFO  c.i.w.w.webapp - SRVE0292I: Servlet Message - [customerservice-springboot-2.0.0-SNAPSHOT]:.Initializing Spring embedded WebApplicationContext
INFO  o.s.w.c.ContextLoader - Root WebApplicationContext: initialization completed in 3654 ms
INFO  o.s.b.w.s.ServletRegistrationBean - Mapping servlet: 'dispatcherServlet' to [/]
INFO  o.s.b.w.s.ServletRegistrationBean - Mapping servlet: 'CXFServlet' to [/*]
INFO  o.s.b.w.s.FilterRegistrationBean - Mapping filter: 'errorPageFilter' to: [/*]
INFO  o.s.b.w.s.FilterRegistrationBean - Mapping filter: 'characterEncodingFilter' to: [/*]
INFO  o.s.b.w.s.FilterRegistrationBean - Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
INFO  o.s.b.w.s.FilterRegistrationBean - Mapping filter: 'httpPutFormContentFilter' to: [/*]
INFO  o.s.b.w.s.FilterRegistrationBean - Mapping filter: 'requestContextFilter' to: [/*]
INFO  o.s.w.s.m.m.a.RequestMappingHandlerAdapter - Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@67e7ebcd: startup date [Wed Jul 19 19:36:12 UTC 2017]; root of context hierarchy
INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
INFO  o.s.w.s.h.SimpleUrlHandlerMapping - Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
INFO  o.s.w.s.h.SimpleUrlHandlerMapping - Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
INFO  o.s.w.s.h.SimpleUrlHandlerMapping - Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
INFO  o.a.c.e.ServerImpl - Setting the server's publish address to be /
INFO  o.s.j.e.a.AnnotationMBeanExporter - Registering beans for JMX exposure on startup
INFO  c.a.s.CustomerServiceApplication - Started CustomerServiceApplication in 14.955 seconds (JVM running for 271.749)
INFO  o.a.c.e.S.e.o.i.l.E.i.w.j.2.0.c.0.17.cl170220170523-1818(id=171)] - Setting the server's publish address to be /

如果我在 application.properties 中启用 logging.level.org.springframework.beans.factory.support=TRACE,我会看到依赖注入在应用程序启动期间起作用:

DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'customerService'
DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating instance of bean 'customerService'
DEBUG o.s.b.f.s.DefaultListableBeanFactory - Eagerly caching bean 'customerService' to allow for resolving potential circular references
DEBUG o.s.b.f.s.DefaultListableBeanFactory - Finished creating instance of bean 'customerService' [...]
DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating instance of bean 'com.test.CustomerServiceRest'
DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'customerService'
DEBUG o.s.b.f.s.DefaultListableBeanFactory - Finished creating instance of bean 'com.test.CustomerServiceRest'

但是,当我发出 REST 请求时,我可以看到每个请求都会创建一个 CustomerServiceRest 的新实例(System.out.println 在构造函数中)并且 @Inject-able依赖项是 null(结果是 NullPointerException)。所以我想也许将 @Singleton 添加到 CustomerServiceRest 会起作用,但是每个请求仍然会创建一个新对象。

有谁知道如何使用单个 Web 服务 bean 或确保 Spring 注入所有依赖项? vanilla JAX-RS 应用程序 JAR 本身不能承担任何 Spring 依赖项。

此类问题发生在 spring 项目之一 但是,我们在 Class 声明中使用了 @Qualifier("name") 并且在使用 @Inject

自动连接时使用了相同的限定符

我在 Tomcat 8.5.x 和 JDK 1.8 上部署了您的代码,并且成功注入了 CustomerService。

你有机会在 Tomcat 下测试你的 war 吗?至少要了解这是关于代码还是应用服务器的问题。有时 WebSphere/Weblogic 中的嵌入式库会覆盖来自您的 war 包的 jar,并且会出现类似的问题。

我能够通过不使用 Liberty 的 CXF 来解决这个问题(例如,使用 servletjsp 特性而不是 webProfile 特性 [它引入了 Liberty 的 jaxrs feature]),并在 @SpringBootApplication 注释中添加 exclude = { DispatcherServletAutoConfiguration.class }。这仅适用于我的用例类型(例如微服务),其中 DispatcherServlet 作为默认 servlet 安装在 / 上,而 CXFServlet 使用 cxf.path=/ 安装(因此创建 /* URL 映射)。对于 Spring MVC 与 CXF 混合但 CXF 服务处于非根 URL 映射的其他情况,exclude 不是必需的。我仍在研究如何让它与 Liberty 的 CXF 一起工作,如果我发现了,我会更新这个答案。