带有 Apache CXF 和 CDI 的 Spring Boot

标签 spring spring-boot jax-rs cxf cdi

使用时 Apache's CXF JAX-RS Spring Boot starterCXF CDI dependency ( cxf-integration-cdi ),Spring 尝试 Autowiring 失败,因为它只支持 JSR 330not CDI .有没有办法让 CDI 与 Spring Boot 一起工作?

代码:

package com.ibm.test.webservices;

import javax.enterprise.inject.Any;
import javax.enterprise.inject.Default;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;
import javax.inject.Named;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Application;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@ApplicationPath("/")
@Path("/")
public class TestWebServices extends Application {
    public static void main(String[] args) {
        SpringApplication.run(
            TestWebServices.class,
            "--cxf.path=/",
            "--cxf.jaxrs.classes-scan=true",
            "--cxf.jaxrs.classes-scan-packages=" +
                TestWebServices.class.getPackage().getName()
        );
    }

    @Inject
    @Any
    private Instance<InvokerInterface> impl;

    @GET
    @Produces("text/plain")
    @Path("/")
    public String helloWorld() {
        return impl.get().invoke();
    }

    public interface InvokerInterface {
        String invoke();
    }

    @Named
    @Default
    public static class Implementation1 implements InvokerInterface {
        public String invoke() {
            return "Hello World 1\n";
        }
    }

    @Named
    public static class Implementation2 implements InvokerInterface {
        public String invoke() {
            return "Hello World 2\n";
        }
    }
}

pom.xml:
<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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.ibm.test</groupId>
    <artifactId>test-spring-boot-with-cdi</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>Test Spring Boot with Apache CXF and CDI</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>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.6.RELEASE</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-spring-boot-starter-jaxrs</artifactId>
            <version>3.2.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-integration-cdi</artifactId>
            <version>3.1.11</version>
        </dependency>
    </dependencies>

    <!-- Required for a standalone JAR: -->
    <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>

异常(exception):
2017-07-28 16:32:59.527 ERROR 9630 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

Field impl in com.ibm.test.webservices.TestWebServices required a bean of type 'javax.enterprise.inject.Instance' that could not be found.


Action:

Consider defining a bean of type 'javax.enterprise.inject.Instance' in your configuration.

[WARNING] 
java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:90)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:55)
    at java.lang.reflect.Method.invoke(Method.java:508)
    at org.springframework.boot.maven.AbstractRunMojo$LaunchRunner.run(AbstractRunMojo.java:527)
    at java.lang.Thread.run(Thread.java:785)
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'testWebServices': Unsatisfied dependency expressed through field 'impl'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'javax.enterprise.inject.Instance<com.ibm.test.webservices.TestWebServices$InvokerInterface>' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@javax.inject.Inject(), @javax.enterprise.inject.Any()}
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:588)
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:366)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1264)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543)
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:693)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:360)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:303)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1118)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1107)
    at com.ibm.test.webservices.TestWebServices.main(TestWebServices.java:22)
    ... 6 more
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'javax.enterprise.inject.Instance<com.ibm.test.webservices.TestWebServices$InvokerInterface>' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@javax.inject.Inject(), @javax.enterprise.inject.Any()}
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1493)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1104)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:585)
    ... 25 more

最佳答案

关键的见解是必须避免 Spring Autowiring (例如 scanBasePackages on @SpringBootApplication@ComponentScan 等)。以下工作:

TestSpringBootApplication.java:

package com.test.webservices;

import org.apache.cxf.cdi.CXFCdiServlet;
import org.jboss.weld.environment.se.Weld;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
// @ServletComponentScan only occurs with an embedded web server, and this is
// needed for Tomcat:
// https://docs.jboss.org/weld/reference/latest/en-US/html/environments.html#_tomcat
@ServletComponentScan(basePackageClasses = { org.jboss.weld.environment.servlet.Listener.class })
public class TestSpringBootApplication extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(TestSpringBootApplication.class);
        app.addInitializers(new ApplicationContextInitializer<ConfigurableApplicationContext>() {
            @Override
            public void initialize(ConfigurableApplicationContext applicationContext) {
                new Weld().initialize();
            }
        });
        app.run(args);
    }

    @Bean
    public ServletRegistrationBean cxfServletRegistration() {
        // http://cxf.apache.org/docs/using-cxf-and-cdi-11-jsr-346.html
        ServletRegistrationBean registration = new ServletRegistrationBean(new CXFCdiServlet(), "/*");
        registration.setLoadOnStartup(1);
        return registration;
    }
}

TestWebServicesApplication.java:
package com.test.webservices;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("/")
public class TestWebServicesApplication extends Application {
}

测试WebServices.java:
package com.test.webservices;

import javax.enterprise.inject.Any;
import javax.enterprise.inject.Instance;
import javax.enterprise.inject.Vetoed;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;

@Path("/")
public class TestWebServices {
    @Inject
    @Any
    private Instance<InvokerInterface> impl;

    @GET
    @Produces("text/plain")
    @Path("/")
    public String helloWorld() {
        return impl.get().invoke();
    }

    public interface InvokerInterface {
        String invoke();
    }

    public static class Implementation1 implements InvokerInterface {
        public String invoke() {
            return "Hello World 1\n";
        }
    }

    @Vetoed
    public static class Implementation2 implements InvokerInterface {
        public String invoke() {
            return "Hello World 2\n";
        }
    }
}

pom.xml:
<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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.test</groupId>
    <artifactId>test-spring-boot-with-cdi</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>Test Spring Boot with Apache CXF and CDI</name>

    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

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

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.6.RELEASE</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-spring-boot-starter-jaxrs</artifactId>
            <version>3.2.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-integration-cdi</artifactId>
            <version>3.1.11</version>
        </dependency>
        <dependency>
            <groupId>org.jboss.weld.servlet</groupId>
            <artifactId>weld-servlet</artifactId>
            <version>2.4.4.Final</version>
        </dependency>
        <dependency>
            <groupId>org.jboss.weld.se</groupId>
            <artifactId>weld-se</artifactId>
            <version>2.4.4.Final</version>
        </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>

    <!-- Required for a standalone JAR: -->
    <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>

src/main/resources/META-INF/beans.xml:
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
       version="1.1" bean-discovery-mode="all">
</beans>

src/main/resources/META-INF/context.xml:
<!-- Required for Tomcat: https://docs.jboss.org/weld/reference/latest/en-US/html/environments.html#_tomcat -->
<Context>
    <Resource name="BeanManager" auth="Container"
        type="javax.enterprise.inject.spi.BeanManager" factory="org.jboss.weld.resources.ManagerObjectFactory" />
</Context>

除了JSON还要使用JAXB,好像是CXF的JAXRSCdiResourceExtension没有找到任何 @Provider s,所以我还加了一个 CDI Extension创建 JacksonJaxbJsonProvider作为 AnnotatedType (这似乎也要求 JAX-RS Application 对象做 不是 覆盖 getClassesgetSingletons 而是自动发现所有 @Path s):
package com.test;

import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.BeforeBeanDiscovery;

import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;

public class RegisterCDIBeans implements javax.enterprise.inject.spi.Extension {
    public void beforeBeanDiscovery(@Observes BeforeBeanDiscovery bbd, BeanManager beanManager) {
        final AnnotatedType<?> annotatedType = beanManager.createAnnotatedType(JacksonJaxbJsonProvider.class);
        bbd.addAnnotatedType(annotatedType, annotatedType.toString());
    }
}

然后在 src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension 中注册 CDI 扩展:
com.test.RegisterCDIBeans

关于带有 Apache CXF 和 CDI 的 Spring Boot,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45383849/

相关文章:

java - Jersey : combine multiple path params into one

java - 如何修改基于 Gradle 构建的 Jersey 项目的依赖关系?

spring - @CacheEvict 在 SpringBoot 中不起作用

java - jackson 无视@JsonIgnore?

spring-boot - 将所有 Zuul 代理请求动态重新路由到单个 URL

Spring Boot,静态资源和mime类型配置

amazon-web-services - 在 AWS elasticbeanstalk 中使用 .ebextensions 配置 nginx 配置文件未找到

java - @ConvertGroup 基于参数

java - 有关 @Value 错误行为的文档

java - 嵌套异常是 com.microsoft.sqlserver.jdbc.SQLServerException :The server failed to resume the transaction