kgibm
kgibm

Reputation: 1038

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

I'm wrapping an existing vanilla JAX-RS application JAR with a Spring Boot application using cxf.jaxrs.classes-scan and cxf.jaxrs.classes-scan-packages. When I run as a JAR or with maven spring-boot:run, dependency injection works fine. When I run as a WAR (on WebSphere Liberty 17.0.0.2), the @Inject-able fields are null during REST requests.

Here's the 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);
  }
}

Here's src/main/resources/application.properties:

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

Here's the Maven pom.xml (the vanilla JAX-RS application JAR is customerservice-java which is in the local repository):

<?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>

Here's the web service in the JAR project:

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();
  }
}

Here's the bean:

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

Here's the WAR logging output:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: 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 /

If I enable logging.level.org.springframework.beans.factory.support=TRACE in application.properties, I see the dependency injection working during application startup:

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'

However, when I make REST requests, I can see that a new instance of CustomerServiceRest is created on each request (System.out.println in the constructor) and the @Inject-able dependencies are null (resuting in a NullPointerException). So I thought maybe adding @Singleton to CustomerServiceRest would work, but a new object is still created on each request.

Does anyone know how to either use a single web service bean or to ensure that Spring injects all the dependencies? The vanilla JAX-RS application JAR can't itself take on any Spring dependencies.

Upvotes: 7

Views: 2367

Answers (3)

kgibm
kgibm

Reputation: 1038

I was able to solve this by not using Liberty's CXF (e.g. using servlet & jsp features instead of the webProfile feature [which brings in Liberty's jaxrs feature]), and adding exclude = { DispatcherServletAutoConfiguration.class } to the @SpringBootApplication annotation. This is only needed for my type of use case (e.g. microservices) where the DispatcherServlet is mounted as the default servlet on / and the CXFServlet is mounted with cxf.path=/ (thus creating a /* URL mapping). For other cases where Spring MVC is mixed with CXF but the CXF services are at a non-root URL mapping, the exclude is not necessary. I'm still investigating how to get this to work with Liberty's CXF and I'll update this answer if I find out.

Upvotes: 4

taner
taner

Reputation: 83

I deployed your code on Tomcat 8.5.x with JDK 1.8 and CustomerService was injected successfully.

Do you have any chance to test your war under Tomcat. At least to understand if it is an issue about the code or app server. Sometimes embedded libraries inside WebSphere/Weblogic are overriding jars coming from your war packages and similar problems occur.

Upvotes: 0

Rizwan
Rizwan

Reputation: 2437

This kind of issue occurred in one of the spring project However we have used the @Qualifier("name") on the Class declaration and the same qualifier has been used while auto wiring with the @Inject

Upvotes: 4

Related Questions