Posts Tagged ‘xfire’
Making Spring MVC and CXF play well together
I have found the spring mvc and CXF really don’t play very well together. When using spring MVC is you are pretty much stuck using the spring DispatcherServlet which for some odd reason doesn’t well with the spring ContextLoaderListener and wants to initialize the config files itself.
The problem with this that if you use the DispatcherServlet to initialize the files then the CXFServlet isn’t able to find the context while it is initializing.
The only way I was able to get this to work out of the box was to list my config files in both places
<!-- inside the web.xml --> <!-- don't do this, duplicates bean instances --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/myconfig.xml</param-value> </context-param> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <servlet> <servlet-name>spring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <description>Spring Configuration Files</description> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/myconfig.xml </param-value> </init-param> <load-on-startup>2</load-on-startup> </servlet>
The problem is that it will duplicate all your bean instances.
My solution is to just create a spring controller that handles the CXF requests. This was relatively easy to do using CXFServlet and AbstractCXFServlet as examples.
The benefit of doing this is that CXFController we create can be directly initialized with the ApplicationContext that it is created in.
package com.foo;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.cxf.Bus;
import org.apache.cxf.BusException;
import org.apache.cxf.BusFactory;
import org.apache.cxf.bus.spring.SpringBusFactory;
import org.apache.cxf.resource.ResourceManager;
import org.apache.cxf.transport.DestinationFactory;
import org.apache.cxf.transport.DestinationFactoryManager;
import org.apache.cxf.transport.servlet.ServletContextResourceResolver;
import org.apache.cxf.transport.servlet.ServletController;
import org.apache.cxf.transport.servlet.ServletTransportFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.ContextStartedEvent;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import java.util.Enumeration;
public class CXFController implements Controller, ApplicationContextAware
{
private static final Log log = LogFactory.getLog(CXFController.class);
protected Bus bus;
protected ServletTransportFactory servletTransportFactory;
protected ServletController controller;
protected WebApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
{
this.applicationContext = (WebApplicationContext) applicationContext;
}
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception
{
try {
BusFactory.setThreadDefaultBus(bus);
controller.invoke(request, response);
}
catch (Exception e) {
log.error(e.getMessage(),e);
throw e;
}
finally {
BusFactory.setThreadDefaultBus(null);
}
return null;
}
public void init()
{
bus = new SpringBusFactory(applicationContext).createBus();
ResourceManager resourceManager = bus.getExtension(ResourceManager.class);
resourceManager.addResourceResolver(new ServletContextResourceResolver(
applicationContext.getServletContext()));
replaceDestinationFactory();
// Set up the ServletController
controller = new ServletController(servletTransportFactory,
SERVLET_CONFIG,
applicationContext.getServletContext(),
bus);
}
public void destroy() {
bus.shutdown(true);
}
protected void replaceDestinationFactory()
{
DestinationFactoryManager dfm = bus.getExtension(DestinationFactoryManager.class);
try
{
DestinationFactory df = dfm
.getDestinationFactory("http://cxf.apache.org/transports/http/configuration");
if (df instanceof ServletTransportFactory)
{
servletTransportFactory = (ServletTransportFactory) df;
log.info("DESTIONFACTORY_ALREADY_REGISTERED");
return;
}
}
catch (BusException e)
{
// why are we throwing a busexception if the DF isn't found?
}
DestinationFactory factory = createServletTransportFactory();
for (String s : factory.getTransportIds())
{
registerTransport(factory, s);
}
log.info("REPLACED_HTTP_DESTIONFACTORY");
}
private void registerTransport(DestinationFactory factory, String namespace) {
bus.getExtension(DestinationFactoryManager.class).registerDestinationFactory(namespace, factory);
}
protected DestinationFactory createServletTransportFactory() {
if (servletTransportFactory == null) {
servletTransportFactory = new ServletTransportFactory(bus);
}
return servletTransportFactory;
}
/**
* Annoying hack...
*
* The ServletController class uses the servlet config to get other init parameters. This
* mostly just prevents ServletController from throwing NullPointerExceptions
*
* We don't care about any of them but here is teh list if we decide we do:
* hide-service-list-page
* disable-address-updates
* base-address
* service-list-stylesheet
*/
public static ServletConfig SERVLET_CONFIG = new ServletConfig()
{
public String getServletName()
{
return null;
}
public ServletContext getServletContext()
{
return null;
}
public String getInitParameter(String name)
{
return null;
}
public Enumeration getInitParameterNames()
{
return null;
}
};
}
There are three caveats to the examples above:
1. In CXFServlet it uses the ServletConfig to find specific init parameters, this can be done still with a little bit of effort by passing them in through the spring config and then use the SERVLET_CONFIG instance of ServletConfig in the example above to provide the values to the ServletController instance.
2. I don’t need the multiple cxf bus support so I didn’t port it over.
3. I didn’t add reloading support to my controller.
Finally you just need to configure spring to use this new controller:
<bean name="cxfController" class="com.foo.CXFController" init-method="init" destroy-method="destroy" lazy-init="true"/> <bean id="beanMappings" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" p:order="0" /> <bean id="urlMappings" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping" p:order="1"> <property name="urlMap"> <util:map> <entry key="/cxf*/**" value-ref="cxfController"/> </util:map> </property> </bean>
It is very important to note that the cxfController is lazy initialized. This allows all services to be loaded before the CXFController does. This could probably also be done by making the CXFController an application listener and waiting until the spring context was fully initialized before initializing, but it was quicker to just make it lazy initialized