Introduction
The JAX-RS 2.0 specification (JSR-339) mandates the support of CDI 1.1 (JSR-346) and Apache CXF starting from the version 3.0 introduces the initial support of this feature. As the starting point, the emphasis has been done on supporting embedded Jety 8/9 and Tomcat 7/8 containers as primary deployment (though other application servers will be supported in the future).
Architecture and design
At the moment, the integration of Apache CXF and CDI revolves around two key components, which reside in the new module called cxf-integration-cdi
- CXFCdiServlet servlet
- JAXRSCdiResourceExtension portable CDI extension
The fact of including cxf-integration-cdi as a dependency allows JAXRSCdiResourceExtension portable CDI extension to be discovered by CDI container. The JAXRSCdiResourceExtension creates the instance of the Bus and registers it with BeanManager. From this point, the Bus instance is a regular CDI bean (with @Application scope) available for injection. This instance of the Bus is being injected into CXFCdiServlet servlet once it is initialized by servlet container.
Depending on the design, JAXRSCdiResourceExtension may use zero-based configuration approach or rely on particular JAX-RS Application instances. The org.apache.cxf.cdi.CXFCdiServlet should be configured as well (more examples for programmatic and WAR scenarios below).
Zero-based Configuration
If the Apache CXF application contains either no instance of JAX-RS Application or only one single instance of JAX-RS Application (annotated with @ApplicationPath) with no singletons and classes defined, the following rules are being applied by JAXRSCdiResourceExtension in order to configure and publish the configured JAX-RS resources:
- the instance of the JAX-RS Application (annotated with @ApplicationPath) is being created and registered with BeanManager
- all instances of the discovered JAX-RS providers (annotated with @Provider) are being created and registered with BeanManager
- all instances of the discovered JAX-RS resources (annotated with @Path) are being created and registered with BeanManager
Lastly, the instance of the JAXRSServerFactoryBean is being created and configured with all service beans and providers discovered before. Additionally, the providers are enriched with the services for javax.ws.rs.ext.MessageBodyReader and javax.ws.rs.ext.MessageBodyWriter, loaded via ServiceLoader. From this moment, Apache CXF application is ready to serve the requests. The quick example is shown below.
The empty JAX-RS Application:
@ApplicationPath("/api") public class BookStoreApplication extends Application { }
And one JAX-RS resource:
@Path("/bookstore/") public class BookStore { @Inject private BookStoreService service; @GET @Path("/books/{bookId}") @Produces(MediaType.APPLICATION_JSON) public Book getBook(@PathParam("bookId") String id) { return service.get(id); } }
Customized Configuration
If the Apache CXF application contains one or more instances of JAX-RS Application (annotated with @ApplicationPath) with singletons or classes defined, the following rules are being applied by JAXRSCdiResourceExtension in order to configure and publish the configured JAX-RS resources:
- the instance of each JAX-RS Application (annotated with @ApplicationPath) is being created and registered with BeanManager
- for each JAX-RS Application instance created before, the instance of the JAXRSServerFactoryBean is being created and configured in a way that application's singletons/classes are being splitted to providers (annotated with @Provider), service beans (annotated with @Path) and features (implementation of org.apache.cxf.feature.Feature)
From this moment, Apache CXF application is ready to serve the requests. The quick example is shown below.
The configured JAX-RS Application:
@ApplicationPath("/custom") public class BookStoreCustomApplication extends Application { @Inject private BookStore bookStore; @Override public Set< Object > getSingletons() { return Sets.< Object >newHashSet( bookStore, new JacksonJsonProvider(), new ValidationExceptionMapper(), new JAXRSBeanValidationFeature()); } }
And one JAX-RS resource:
@Path("/bookstore/") public class BookStore { @Inject private BookStoreService service; @GET @Path("/books/{bookId}") @Produces(MediaType.APPLICATION_JSON) public Book getBook(@PathParam("bookId") String id) { return service.get(id); } }
Additional Configuration Capabilities
If you have a need to, you can configure the underlying JAXRSServerFactoryBean by implementing the interface JAXRSServerFactoryCustomizationExtension. Instances of this extension can be located via ServiceLoader of as CDI beans. You can use this to programmatically add new providers, or otherwise manipulate the runtime before the server is created. An example of how this is used can be seen at SseTransportCustomizationExtension.
CDi Lifecycle for JAX-RS Context Objects
The CDI extension also supports the injection (via CDI) of @Context objects. Any known object can be injected via CDI. This happens automatically when the CDI extension is used, and a @Context is declared as a field on a resource class. Note that this does allow you to inject your @Context objects in non-JAX-RS components as well. The logic is in two parts:
- Rewriting injection points
- When an injection point is found that contains @Context two additional annotations are added
- These annotations are @Inject and @ContextResolved
- The use of @Inject will force the CDI runtime to take over the injection of the bean value, while @ContextResolved is simply a qualifier we apply to the injection point to avoid ambiguities
- Providing Beans
- For each of the built in @Context types, we register a bean that provides a @RequestScoped bean that resolves the value, by looking up the internal context value
- In addition, a user or intergrator can implement ContextClassProvider to register an additional class as context type to be resolved.
Deploying with embedded Jetty 8/9 (programmatic configuration)
With Jetty 8/9 it possible to create fully embeddable REST / JAX-RS servers without web.xml or WAR files involved. For Apache CXF applications which are using CDI 1.1, the CXFCdiServlet servlet should be used as a starting point. Following example demonstrates the necessary configuration points in order to create embedded Jetty 8/9 instance. As a CDI 1.1 implementation, JBoss Weld 2.0 is being used.
System.setProperty("java.naming.factory.url", "org.eclipse.jetty.jndi"); System.setProperty("java.naming.factory.initial", "org.eclipse.jetty.jndi.InitialContextFactory"); // Register and map the dispatcher servlet final Server server = new Server(<port>); final ServletHolder servletHolder = new ServletHolder(new CXFCdiServlet()); final ServletContextHandler context = new ServletContextHandler(); context.setContextPath(<context path>); context.addEventListener(new Listener()); context.addEventListener(new BeanManagerResourceBindingListener()); context.addServlet(servletHolder, "/rest/*"); server.setHandler(context); server.start();
This code snippet is enough to trigger the CDI portable extension discovery, to perform the configuration (depending on JAX-RS Applications present) and to wire up all defined dependencies together.
Deploying with embedded Jetty 8/9 (WAR-based deployment)
Another option to deploy Apache CXF application with CDI 1.1 support and embedded Jetty 8/9 is by using web.xml descriptor and WAR-like deployment structure. In this case, the Apache CXF application needs to declare CXFCdiServlet servlet (and its mappings) and, if required, CDI-specific listeners (in the example below the JBoss Weld 2.0 is being used as CDI 1.1 container).
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <listener> <listener-class>org.jboss.weld.environment.servlet.Listener</listener-class> </listener> <servlet> <servlet-name>CXFServlet</servlet-name> <display-name>CXF Servlet</display-name> <servlet-class>org.apache.cxf.cdi.CXFCdiServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>CXFServlet</servlet-name> <url-pattern>/rest/*</url-pattern> </servlet-mapping> <resource-env-ref> <resource-env-ref-name>BeanManager</resource-env-ref-name> <resource-env-ref-type>javax.enterprise.inject.spi.BeanManager </resource-env-ref-type> </resource-env-ref> </web-app>
The server initialization in this case looks simpler.
System.setProperty("java.naming.factory.url", "org.eclipse.jetty.jndi"); System.setProperty("java.naming.factory.initial", "org.eclipse.jetty.jndi.InitialContextFactory"); final Server server = new Server(<port>); final WebAppContext context = new WebAppContext(); context.setContextPath(<context path>); context.setWar(<path to WAR folder/file>); context.setServerClasses(new String[] { "org.eclipse.jetty.servlet.ServletContextHandler.Decorator" }); HandlerCollection handlers = new HandlerCollection(); handlers.setHandlers(new Handler[] {context, new DefaultHandler()}); server.setHandler(handlers); server.start();
Please notice, usage of Jetty-specific server classes ("org.eclipse.jetty.servlet.ServletContextHandler.Decorator") is very important to allow CDI 1.1 injections (backed by JBoss Weld 2.0) to work seamlessly across servlets / listeners / filters. It is not stricktly necessary for Apache CXF (everything will work as expected) but complex applications would definitely benefit from that.
Deploying with embedded Tomcat 7/8 (WAR-based deployment)
In case of embedded Tomcat 7/8, Apache CXF application with CDI 1.1 support could be deployed with web.xml descriptor and WAR-like deployment structure, similarly to Jetty 8/9 WAR-based deployment. Apache CXF application needs to declare CXFCdiServlet servlet (and its mappings) and, if required, CDI-specific listeners (in the example below the JBoss Weld 2.0 is being used as CDI 1.1 container).
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <listener> <listener-class>org.jboss.weld.environment.servlet.Listener</listener-class> </listener> <servlet> <servlet-name>CXFServlet</servlet-name> <display-name>CXF Servlet</display-name> <servlet-class>org.apache.cxf.cdi.CXFCdiServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>CXFServlet</servlet-name> <url-pattern>/rest/*</url-pattern> </servlet-mapping> <resource-env-ref> <resource-env-ref-name>BeanManager</resource-env-ref-name> <resource-env-ref-type>javax.enterprise.inject.spi.BeanManager </resource-env-ref-type> </resource-env-ref> </web-app>
Tomcat 7/8 server has a different API, by still quite simple initialization procedure.
final Tomcat server = new Tomcat(); server.setPort(<port>); final File base = createTemporaryDirectory(); server.setBaseDir(base.getAbsolutePath()); server.getHost().setAppBase(base.getAbsolutePath()); server.getHost().setAutoDeploy(true); server.getHost().setDeployOnStartup(true); server.addWebapp(<context path>, <path to WAR folder/file>); server.start();
Future work
The Apache CXF team is committed to improve the CDI integration and to cover more deployment scenarios and wide range of application configurations and demands. However, by and large the CDI integration is implementation-independent and it is expected to work in JBoss WildFly or other EE containers.