Apache CXF實現Web Service(4)——Tomcat容器和Spring實現JAX-RS(RESTful) web service

Richaaaard發表於2015-12-02

準備

我們仍然使用 Apache CXF實現Web Service(2)——不借助重量級Web容器和Spring實現一個純的JAX-RS(RESTful) web service 中的程式碼作為基礎,並引入spring來進行RESTful web service的配置和管理。

專案目錄結構如下圖

首先我們要在web.xml中加入通過Spring的ContextLoaderListener載入的Spring執行時環境以及CXF的Spring配置檔案

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
   <display-name>cxf</display-name>  
   
    <context-param>  
        <param-name>contextConfigLocation</param-name>  
        <param-value>/WEB-INF/cxf-servlet.xml</param-value>  
    </context-param>  
      
    <!--設定一起動當前的Web應用,就載入Spring,讓Spring管理Bean -->  
    <listener>  
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
    </listener>  
   
  <servlet>
    <description>m CXF Endpoint</description>
    <display-name>cxf</display-name>
    <servlet-name>cxf</servlet-name>
    <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>cxf</servlet-name>
    <url-pattern>/services/*</url-pattern>
  </servlet-mapping>
  <session-config>
    <session-timeout>60</session-timeout>
  </session-config>
</web-app>

需要注意的是Spring配置檔案的命名和路徑"/WEB-INF/cxf-servlet.xml",如果將專案檔案重新命名為applicationContext.xml,同時修改web.xml裡面為"/WEB-INF/applicationContext.xml",Tomcat伺服器啟動的時候會提示錯誤資訊:(稍後我們來講如何解決這個問題)

org.springframework.beans.factory.BeanDefinitionStoreException: IOException parsing XML document from ServletContext resource [/WEB-INF/cxf-servlet.xml]; nested exception is java.io.FileNotFoundException: Could not open ServletContext resource [/WEB-INF/cxf-servlet.xml]
    at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:341)
    at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:302)
    at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:174)
    at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:209)
    at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:180)
    at org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(XmlWebApplicationContext.java:125)
    at org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(XmlWebApplicationContext.java:94)
    at org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory(AbstractRefreshableApplicationContext.java:130)
    at org.springframework.context.support.AbstractApplicationContext.obtainFreshBeanFactory(AbstractApplicationContext.java:537)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:451)
    at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:389)
    at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:294)
    at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:112)
    at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4793)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5236)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1408)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1398)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Caused by: java.io.FileNotFoundException: Could not open ServletContext resource [/WEB-INF/cxf-servlet.xml]
    at org.springframework.web.context.support.ServletContextResource.getInputStream(ServletContextResource.java:140)
    at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:328)
    ... 21 more

在/WEB-INF目錄下,我們加入檔案cxf-servlet.xml

<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans"   
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   
    xmlns:jaxrs="http://cxf.apache.org/jaxrs"
    xsi:schemaLocation="
    	http://www.springframework.org/schema/beans
	    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
	    http://www.springframework.org/schema/context
	    http://www.springframework.org/schema/context/spring-context-3.0.xsd
	    http://cxf.apache.org/jaxrs 
	    http://cxf.apache.org/schemas/jaxrs.xsd">  
    <import resource="classpath:META-INF/cxf/cxf.xml"/>   
    <import resource="classpath:META-INF/cxf/cxf-servlet.xml"/>  
    
    <bean id="roomService" 
    	class="com.cnblog.richaaaard.cxftest.spring.rs.helloworld.service.RoomService">  
    </bean>  
    
	<jaxrs:server id="restContainer" address="/">  
        <jaxrs:serviceBeans>  
            <ref bean="roomService" />  
        </jaxrs:serviceBeans>  
         <jaxrs:providers>
        	<bean class="org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider" />
    	</jaxrs:providers>
<!--         <jaxrs:extensionMappings> -->
<!--         <entry key="json" value="application/json" /> -->
<!--         <entry key="xml" value="application/xml" /> -->
<!--     </jaxrs:extensionMappings> -->
    </jaxrs:server>
</beans>

  注意

檔案中的Spring支援,還有http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd(如果是ws型別的web service則需要引入jaxws的規範)

我們還可以看到,檔案通過Spring bean的配置將roomService的例項注入到了jaxrs:server之中。

紅色高亮的"import resource"部分也是必須的,否則在Tomcat伺服器啟動的時候會出錯:

資訊: Loading XML bean definitions from URL [file:/Users/Richard/Documents/Dev/workspace/eclipse/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/cxf-test-rs-spring-helloworld/WEB-INF/cxf-servlet.xml]
十二月 02, 2015 4:34:02 下午 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
資訊: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@58318a06: defining beans [roomService,restContainer]; parent: org.springframework.beans.factory.support.DefaultListableBeanFactory@22252012
566 [localhost-startStop-1] INFO org.apache.cxf.endpoint.ServerImpl - Setting the server's publish address to be /
十二月 02, 2015 4:34:02 下午 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry destroySingletons
資訊: Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@58318a06: defining beans [roomService,restContainer]; parent: org.springframework.beans.factory.support.DefaultListableBeanFactory@22252012
十二月 02, 2015 4:34:02 下午 org.apache.catalina.core.ApplicationContext log
嚴重: StandardWrapper.Throwable
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'restContainer': Invocation of init method failed; nested exception is org.apache.cxf.service.factory.ServiceConstructionException
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1482)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:521)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:458)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:295)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:223)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:292)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:628)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:932)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:479)
    at org.apache.cxf.transport.servlet.CXFServlet.createSpringContext(CXFServlet.java:151)
    at org.apache.cxf.transport.servlet.CXFServlet.loadBus(CXFServlet.java:74)
    at org.apache.cxf.transport.servlet.CXFNonSpringServlet.init(CXFNonSpringServlet.java:77)
    at org.apache.catalina.core.StandardWrapper.initServlet(StandardWrapper.java:1231)
    at org.apache.catalina.core.StandardWrapper.loadServlet(StandardWrapper.java:1144)
    at org.apache.catalina.core.StandardWrapper.load(StandardWrapper.java:1031)
    at org.apache.catalina.core.StandardContext.loadOnStartup(StandardContext.java:4978)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5270)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1408)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1398)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Caused by: org.apache.cxf.service.factory.ServiceConstructionException
    at org.apache.cxf.jaxrs.JAXRSServerFactoryBean.create(JAXRSServerFactoryBean.java:219)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)

"import resource"起什麼作用?

待研

最後

我們在Tomcat中執行Web專案,並通過瀏覽器訪問

 

*擴充套件

  • 為什麼使用applicationContext.xml伺服器會出錯,從錯誤資訊看,它仍然嘗試去尋找cxf-servlet.xml檔案?

    在檢視專案結構後發現在/WebContent/WEB-INF中還有一個web.xml裡面指定的檔案是cxf-servlet.xml,這個web.xml和我們在/src/main/webapp/WEB-INF下的檔案不同步,Tomcat啟動時讀取的檔案在WebContent下,修改後伺服器就正常了。

    

為什麼會這樣?那麼又如何使src下的檔案與WebContent中的檔案保持同步呢?(其實這都是網上轉來轉去的例子惹得禍)

我們右鍵檢視整個專案的屬性Properties->Web Deployment Assembly 發現在部署配置下 /src/main/webapp 和 /WebContent 都輸出到伺服器部署目標的根目錄下 "/" 且 "/WebContent" 在 "/src/main/webapp" 之後輸出,所以會覆蓋之前webapp下的內容,所以我們要做的是隻要保留一個輸出——刪除webapp這行配置,然後將原來webapp下的applicationContext.xml檔案拷貝到WebContent下。

經過試驗,server可以正常啟動。

  • "import resource"起什麼作用?為什麼它的路徑是"classpath:META-INF/cxf/cxf.xml"與"classpath:META-INF/cxf/cxf-servlet.xml"

我們發現cxf.xml和cxf-servlet.xml兩個檔案並不在我們專案原始碼的路徑中,那麼是不是在cxf相關的jar包中呢?

答案是肯定的

cxf.xml存在於cxf-core.jar中 /META-INF/cxf/cxf.xml

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <!--  For Testing using the Spring commons processor, uncomment one of:-->
    <!-- 
                <bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"/>
                <context:annotation-config/>
        -->
    <bean id="cxf" class="org.apache.cxf.bus.spring.SpringBus" destroy-method="shutdown"/>
    <bean id="org.apache.cxf.bus.spring.BusWiringBeanFactoryPostProcessor" class="org.apache.cxf.bus.spring.BusWiringBeanFactoryPostProcessor"/>
    <bean id="org.apache.cxf.bus.spring.Jsr250BeanPostProcessor" class="org.apache.cxf.bus.spring.Jsr250BeanPostProcessor"/>
    <bean id="org.apache.cxf.bus.spring.BusExtensionPostProcessor" class="org.apache.cxf.bus.spring.BusExtensionPostProcessor"/>
</beans>

  cxf-servlet.xml存在與cxf-rt-transports-http.jar中

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:foo="http://cxf.apache.org/configuration/foo" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">


</beans>

奇怪的是這裡的cxf-servlet.xml裡面什麼都沒有申明,那麼我們是否可以將其去掉呢?(答案???自行驗證)

所以之前在Spring中配置的cxf-servlet.xml只是與這裡的import resource裡面的恰好相同而已,兩個檔案不是同一個,路徑也不一樣。

那麼cxf-core.jar裡面的這個檔案cxf.xml裡面的配置有什麼用呢?

後文分解:)

參考:

https://www.mail-archive.com/users@cxf.apache.org/msg00488.html

http://www.cnblogs.com/hoojo/archive/2011/03/30/1999563.html

http://www.kuqin.com/shuoit/20140716/341250.html

https://cwiki.apache.org/confluence/display/CXF20DOC/JAXRS+Services+Configuration

相關文章