带有Apache CXF和CDI的Spring Boot



当使用带有CXF CDI依赖项(cxf-integration-cdi(的Apache的CXF JAX-RS Spring Boot启动器时,Spring尝试自动布线失败,因为它只支持JSR 330而不是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 1n";
}
}
@Named
public static class Implementation2 implements InvokerInterface {
public String invoke() {
return "Hello World 2n";
}
}
}

绒球.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>

例外:

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 自动布线(例如scanBasePackages@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;
}
}

TestWebServices应用程序.java:

package com.test.webservices;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
@ApplicationPath("/")
public class TestWebServicesApplication extends Application {
}

测试网络服务.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 1n";
}
}
@Vetoed
public static class Implementation2 implements InvokerInterface {
public String invoke() {
return "Hello World 2n";
}
}
}

绒球.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,所以我还添加了一个 CDIExtension,用于创建JacksonJaxbJsonProvider作为AnnotatedType(这似乎还要求 JAX-RSApplication对象不会覆盖getClassesgetSingletons,而是自动发现所有@Path(:

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

您需要创建bean或使用@Autowired将类注入javax.enterprise.inject.InstanceTestWebServices。 您可以在 ApplicationContaxt 中创建bean.xml并使用@Autowired @Qualifier注入类。

最新更新