Wednesday, 6 July 2011

Spring, Apache CXF and the Autowired annotation issue

There are a number of websites, forums and blogs that provide examples of how to get Spring, Apache CXF working together, and although each example worked and was helpful, none actually resolved the issues we were facing.

Scenario:
Injecting Spring beans into CXF web service where beans are defined in multiple jar files.

Description:
Our project consists of multiple Maven modules with each module producing a jar file, so consider a typical project with modules for the domain model (entities), the data access objects (DAOs) and services. Each of these modules defines Spring beans and contains its own application context file. Finally there is a web service module which exposes required operations for clients to invoke. The web service is implemented using Apache CXF and produces a war file.
Listing 1- Entity, DAO and Service beans

@Entity
@Table(name=”T_CUSTOMER”)
public class Customer {
  //field details omitted for brevity
}
public class CustomerDAOImpl implements CustomerDAO {
  private EntityManager em;

  @PersistenceContext
  public void setEntityManager(EntityManager entityManager) {
    this.em = entityManager;
  }
  public List<Customer> getCustomerList() {
    // details omitted for brevity
  }
}

public class CustomerServiceImpl implements CustomerService {
  @Autowired
  private CustomerDAO customerDAO;

  public List<Customer> getCustomerList() {
    return customerDAO.getCustomerList();
  }
}

Listing 1 above, shows the entity, DAO and service classes. Details have been omitted for the sake of brevity.

Listing 2 – Web Service

@WebService(endpointInterface=”com.javaworkbench.webservices.api.CustomerWebService”,
serviceName="/CustomerService")
public class CustomerWebServiceImpl implements CustomerWebService {
  @Autowired
  private CustomerService customerService;

  @WebMethod
  public CustomerServiceResponse execute(CustomerServiceRequest request) {
    //details omitted for brevity
  }
}

Listing 2 shows the web service with the CustomerService to be injected using the Spring Autowired annotation. The web service configuration file is the same as that described on the Apache CXF website regarding writing a service with Spring. Listing 3 shows a sample of the configuration file with the schema references omitted. Listing 4 shows the web.xml for the Apache CXF servlet.

Listing 3 – cxf.xml, CXF config file

<?xml version="1.0" encoding="UTF-8"?>
<beans...>
  <import resource="classpath:META-INF/cxf/cxf.xml" />
  <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
  <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

  <jaxws:endpoint id="customerService" implementorClass= "com.javaworkbench.webservices.impl.CustomerWebServiceImpl"
                  address="/CustomerService"/>
                 
</beans>

Listing 4 – web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"...>
  <display-name>CustomerService</display-name>
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>WEB-INF/mybeans.xml WEB-INF/cxf.xml</param-value>
  </context-param>
  <listener>
    <listener-class>
      org.springframework.web.context.ContextLoaderListener
    </listener-class>
  </listener>
  <servlet>
    <servlet-name>CXFServlet</servlet-name>
    <servlet-class>
        org.apache.cxf.transport.servlet.CXFServlet
    </servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>CXFServlet</servlet-name>
    <url-pattern>/CustomerService/*</url-pattern>
  </servlet-mapping>



Listing 5 – mybeans.xml

<beans... >

  <context:annotation-config/>
  <context:component-scan base-package="com.javaworkbench" />

  <import resource="classpath:META-INF/spring/dao-impl.xml"/>
  <import resource="classpath:META-INF/spring/service-impl.xml"/>
 
 </beans>

Listing 5 above shows the Spring application context file that is referenced from the web.xml. As the various entity, DAO and service beans are defined in separate modules, this file imports the bean definitions from the other modules.


This is the typical setup for Apache CXF with Spring and no different from numerous articles on the web. The web service was packaged as a war file with the various module jar files and deployed on Glassfish 3.1. To test the webservice, SOAP UI was used as well as writing a web service client. Eveything deployed smoothly and the wsdl was also generated and visible from a web browser. Now the drama began. When invoking the web service, the CustomerService reference was always null, meaning the CustomerService bean was not being injected. So what was going wrong? Turning on debug logging for Spring revealed that the all the beans were being created and available in the root hierarchy of the web application context. So why was the service not being injected? After numerous hours of trying every permutation and combination it transpired that the serviceName attribute in the WebService annotation was the offending element. Removing this element allowed the CustomerService to be injected into the web service. No idea why this would cause such problems as this is part of the JAX-WS annotations.

Removing the serviceName attribute has also changed the URL for the web service wsdl from http://localhost:8080/webservices-impl/CustomerService?wsdl to http://localhost:8080/webservices-impl/CustomerService/CustomerService?wsdl. We are still trying to understand how to correct this.

Now that the CustomerService was being injected, the next issue was that it didn't contain the injected DAO! After numerous iterations of checking all the code and configuration files and comparing them with the online examples and tutorials, nothing revealed as to why the DAO was not being injected. The online examples and tutorials were all using Spring 2.5, none showcased the use of Spring 3.0. So perhaps that may have been an issue, but even changing the configuration files to reference the Spring 2.5 schemas (xsd) and using Spring 2.5 jars still didn't work. After trying numerous angles of attack without any success the problem was resolved by simply re-installing Glassfish! In time, we are going to investigate what were the causes behind this but for now we are just relieved to have the application framework working.

No comments:

Post a Comment