Apache CXF 在 WebLogic 9.2 上的問題定位分析及權宜之計

阿敏總司令發表於2007-10-03
同樣的程式在 Tomcat 5.5.16/25 以及 Jetty 5.1.12 上都能夠成功部署及執行,但是部署到 WebLogic 9.2 上時出錯
org.springframework.web.context.ContextLoader initWebApplicationContext
FATAL: Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'xxxWebService': Invocation of init method failed; nested exception is java.lang.NoSuchMethodError: portName
Caused by: java.lang.NoSuchMethodError: portName
at org.apache.cxf.jaxws.support.JaxWsImplementorInfo.getEndpointName(JaxWsImplementorInfo.java:154)
at org.apache.cxf.jaxws.support.JaxWsServiceConfiguration.getEndpointName(JaxWsServiceConfiguration.java:89)
at org.apache.cxf.service.factory.ReflectionServiceFactoryBean.getEndpointName(ReflectionServiceFactoryBean.java:983)
at org.apache.cxf.frontend.AbstractEndpointFactory.createEndpoint(AbstractEndpointFactory.java:91)
at org.apache.cxf.frontend.ServerFactoryBean.create(ServerFactoryBean.java:107)
at org.apache.cxf.jaxws.JaxWsServerFactoryBean.create(JaxWsServerFactoryBean.java:147)
at jrockit.reflect.VirtualNativeMethodInvoker.invoke(Ljava.lang.Object;[Ljava.lang.Object;)Ljava.lang.Object;(Unknown Source)
at java.lang.reflect.Method.invoke(Ljava.lang.Object;[Ljava.lang.Object;I)Ljava.lang.Object;(Unknown Source)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeCustomInitMethod(AbstractAutowireCapableBeanFactory.java:1214)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1179)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1145)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:427)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:251)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:144)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:248)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:160)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:279)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:360)
at org.springframework.web.context.ContextLoader.createWebApplicationContext(ContextLoader.java:241)
at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:184)
at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:49)


從 Stack Trace 來分析,Spring 在 ContextLoaderListener 進行載入配置檔案時出錯了。
通過檢視 JaxWsImplementorInfo.java:154 的源程式
[code]
portName = wsAnnotations.get(x).portName();
[/code]
可以知道 wsAnnotations 的定義為
[code]
private List<WebService> wsAnnotations = new ArrayList<WebService>(2);
[/code]
得到List裡的物件型別為WebService,並得到全稱為javax.jws.WebService
通過 JarClassFind 來搜尋,可以得到 Apache CXF 中的 geronimo-ws-metadata_2.0_spec-1.1.1.jar 包含有這個類
而 WebLogic 9.2 中的則有兩個 .jar 檔案包含有此類:
No.1
Jar Package:%WLS_HOME%/server/lib/api.jar
No.2
Jar Package:%WLS_HOME%/server/lib/weblogic.jar
經過對比,WebLogic 9.2 裡的兩個 .jar 檔案裡帶個類是一樣的,況且,正常啟動時 WebLogic 也只是將 weblogic.jar 加到 CLASSPATH 中。

問題猜測應當是由於這兩類的版本不一樣導致的,拆出來 jad 一下,WebLogic 中的 javax.jws.WebService.java 程式碼為:
[code]
package javax.jws;

import java.lang.annotation.Annotation;

public interface WebService
extends Annotation
{

public abstract String name();

public abstract String targetNamespace();

public abstract String serviceName();

public abstract String wsdlLocation();

public abstract String endpointInterface();
}
[/code]

拆出 Apache CXF 裡的 javax.jws.WebService jad 一下,程式碼為:
[code]
package javax.jws;

import java.lang.annotation.Annotation;

public interface WebService
extends Annotation
{

public abstract String name();

public abstract String targetNamespace();

public abstract String serviceName();

public abstract String wsdlLocation();

public abstract String endpointInterface();

public abstract String portName();
}
[/code]
這下就完全明白了,是 Apache CXF 期望載入的 javax.jws.WebService 註解與 Weblogic 中 javax.jws.WebService 註解版本不一致導致的。

OK,現在首先想到的是在 weblogic.xml 設定 prefer-web-inf-classes 為 true 來提前載入了。如下:
[code]
<?xml version="1.0" encoding="UTF-8"?>
<weblogic-web-app>
<container-descriptor>
<prefer-web-inf-classes>true</prefer-web-inf-classes>
</container-descriptor>
</weblogic-web-app>
[/code]

總以為能夠象往常一樣,這樣可以了,但是將 prefer-web-inf-classes 設定為 true 後,卻發生了另一個異常了:
[code]
FATAL: Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'xxxWebService': Invocation of init method failed; nested exception is java.lang.LinkageError: Class javax/xml/namespace/QName violates loader constraints
Caused by: java.lang.LinkageError: Class javax/xml/namespace/QName violates loader constraints
at com.sun.xml.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl.<clinit>(RuntimeBuiltinLeafInfoImpl.java:186)
at com.sun.xml.bind.v2.model.impl.RuntimeTypeInfoSetImpl.<init>(RuntimeTypeInfoSetImpl.java:25)
at com.sun.xml.bind.v2.model.impl.RuntimeModelBuilder.createTypeInfoSet(RuntimeModelBuilder.java:84)
at com.sun.xml.bind.v2.model.impl.RuntimeModelBuilder.createTypeInfoSet(RuntimeModelBuilder.java:41)
at com.sun.xml.bind.v2.model.impl.ModelBuilder.<init>(ModelBuilder.java:104)
at com.sun.xml.bind.v2.model.impl.RuntimeModelBuilder.<init>(RuntimeModelBuilder.java:49)
at com.sun.xml.bind.v2.runtime.JAXBContextImpl.getTypeInfoSet(JAXBContextImpl.java:372)
at com.sun.xml.bind.v2.runtime.JAXBContextImpl.<init>(JAXBContextImpl.java:236)
at com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:76)
at com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:55)
at jrockit.reflect.InitialMethodInvoker.invoke(Ljava.lang.Object;[Ljava.lang.Object;)Ljava.lang.Object;(Unknown Source)
at java.lang.reflect.Method.invoke(Ljava.lang.Object;[Ljava.lang.Object;I)Ljava.lang.Object;(Unknown Source)
at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:210)
at javax.xml.bind.ContextFinder.find(ContextFinder.java:366)
at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:574)
at org.apache.cxf.jaxb.JAXBDataBinding.createJAXBContext(JAXBDataBinding.java:377)
at org.apache.cxf.jaxb.JAXBDataBinding.initialize(JAXBDataBinding.java:182)
at org.apache.cxf.service.factory.ReflectionServiceFactoryBean.buildServiceFromClass(ReflectionServiceFactoryBean.java:244)
at org.apache.cxf.service.factory.ReflectionServiceFactoryBean.initializeServiceModel(ReflectionServiceFactoryBean.java:272)
at org.apache.cxf.service.factory.ReflectionServiceFactoryBean.create(ReflectionServiceFactoryBean.java:146)
at org.apache.cxf.jaxws.support.JaxWsServiceFactoryBean.create(JaxWsServiceFactoryBean.java:89)
at org.apache.cxf.frontend.AbstractEndpointFactory.createEndpoint(AbstractEndpointFactory.java:83)
at org.apache.cxf.frontend.ServerFactoryBean.create(ServerFactoryBean.java:107)
at org.apache.cxf.jaxws.JaxWsServerFactoryBean.create(JaxWsServerFactoryBean.java:147)
[/code]
面對這個 java.lang.LinkageError: Class javax/xml/namespace/QName 錯誤,自然又聯想到了版本不一致的問題。
繼續 JarClassFind ,在 Apache CXF 裡找到 stax-api-1.0.1.jar 包含有這個類。
No.1
Jar Package:%WLS_HOME%/server/lib/api.jar
No.2
Jar Package:%WLS_HOME%/server/lib/weblogic.jar
No.3
Jar Package:%WLS_HOME%/server/lib/webserviceclient+ssl.jar
No.4
Jar Package:%WLS_HOME%/server/lib/webserviceclient.jar
No.5
Jar Package:%WLS_HOME%/server/lib/xbean.jar
同樣與 WebLogic 啟動時有關的仍是 weblogic.jar ,怪異的是 WebLogic 中居然有三個版本的 javax/xml/namespace/QName 這個類,真是比較糟糕的事情。
api.jar、weblogic.jar 與 webserviceclient+ssl.jar、webserviceclient.jar 和 xbean.jar 中各是一個版本。

但是通過 jad 發現 weblogic 裡的 javax/xml/namespace/QName 與 Apache CXF 裡的是一樣的,比較奇怪了。。。
再進行認真地比較發現,檔案的時間不同,再直接進行對比 .class 檔案,還是有兩處有不同之處。Apache CXF 的時間晚於 WebLogic 裡的類的時間,於是只能懷疑後者引起類在載入時發生了 java.lang.LinkageError 錯誤。
經查JDK文件:LinkageError 的子類指示一個類在一定程度上依賴於另一個類;但是,在編譯前一個類之後,後一個類發生了不相容的改變。

於是真相大明瞭?因為 Apache CXF 裡的類的時間晚於 WebLogic 裡的類的時間,而且類的二進位制內容也發生了改變,於是原來 WebLogic 裡的其它類由於我們通過了 prefer-web-inf-classes 設定為 true 後,而引用到了現在的 Apache CXF 裡的類了,就發生了 java.lang.LinkageError 錯誤。

由於設定 prefer-web-inf-classes 設定為 true 後,提前載入了應用程式中 lib 目錄下的 .jar 檔案,而發生了這個問題,於是就嘗試去掉 prefer-web-inf-classes 的設定,直接在 WebLogic 的啟動指令碼中增加第一次異常時相關的 jar 到 CLASSPATH 中,即 geronimo-ws-metadata_2.0_spec-1.1.1.jar 檔案,只提前載入這個 jar 檔案。
[code]
set CLASSPATH_CXF=X:/xyz/geronimo-ws-metadata_2.0_spec-1.1.1.jar
set CLASSPATH=%CLASSPATH_CXF%;%CLASSPATH%;%MEDREC_WEBLOGIC_CLASSPATH%
[/code]
於是,再次進行啟動,這下則可以正常啟動了。

為了支援 Aegis ,除了必需的之外,還需要再增加如下 jar 包:
- jaxen.jar
- jdom.jar
- stax-utils.jar

相關文章