以非包装器样式调用服务时出现 CXF 解组错误

CXF Unmarshalling Error when calling a service in non-wrapper style

我正在使用 CXF 从 WSDL 开发 Web 服务,我希望能够 运行 使用 JUnit 测试示例请求。

我的测试class创建一个模拟服务器,然后作为客户端测试服务器。

我不明白为什么我总是收到 Unmarshalling Error: unexpected element 异常。

我将问题隔离到一个最小的项目中,但即使这样也无济于事。我确信这个问题是我犯的一些愚蠢的错误...已经两天了,我找不到它...

错误信息是:org.apache.cxf.interceptor.Fault: Unmarshalling Error: unexpected element (uri:"http://my.project.service", local:"myProperty"). Expected elements are <{http://my.project.service}myObject>

这就像它期待 myObject 中的另一个 myObject 元素...所有内容都是使用 CXF 生成的,为什么它不能解组它只是编组了一些的东西几毫秒前?

我试过 soap 1.1 / 1.2,我试过不同版本的 CXF 到 3.0.5,我试过使用命名空间,但总是出现相同的错误。

这里是重现错误的最小项目的全部内容:

<?xml version="1.0" ?>
<definitions targetNamespace="http://my.project.service"
  xmlns="http://schemas.xmlsoap.org/wsdl/" 
  xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/"
  xmlns:tns="http://my.project.service">

  <types>
    <xsd:schema attributeFormDefault="qualified"
      elementFormDefault="qualified" targetNamespace="http://my.project.service"
      xmlns:tns="http://my.project.service" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <xsd:complexType name="MyObject">
        <xsd:sequence>
          <xsd:element name="myProperty" type="xsd:string" />
        </xsd:sequence>
      </xsd:complexType>
      <xsd:element name="MyOperationRequest">
        <xsd:complexType>
          <xsd:sequence>
            <xsd:element name="myObject" type="tns:MyObject" />
          </xsd:sequence>
        </xsd:complexType>
      </xsd:element>
      <xsd:element name="MyOperationResponse">
        <xsd:complexType>
          <xsd:sequence>
            <xsd:element name="myMessage" type="xsd:string" />
          </xsd:sequence>
        </xsd:complexType>
      </xsd:element>
    </xsd:schema>
  </types>
  <message name="MyOperationResponse">
    <part element="tns:MyOperationResponse" name="parameters" />
  </message>
  <message name="MyOperationRequest">
    <part element="tns:MyOperationRequest" name="parameters" />
  </message>
  <portType name="MyServicePortType">
    <operation name="MyOperation">
      <input message="tns:MyOperationRequest" name="MyOperationRequest" />
      <output message="tns:MyOperationResponse" name="MyOperationResponse" />
    </operation>
  </portType>
  <binding name="MyServiceBinding" type="tns:MyServicePortType">
    <soap12:binding style="document"
      transport="http://schemas.xmlsoap.org/soap/http" />
    <operation name="MyOperation">
      <soap12:operation soapAction="" style="document" />
      <input name="MyOperationRequest">
        <soap12:body parts="parameters" use="literal" />
      </input>
      <output name="MyOperationResponse">
        <soap12:body parts="parameters" use="literal" />
      </output>
    </operation>
  </binding>
  <service name="MyService">
    <port binding="tns:MyServiceBinding" name="MyServicePort">
      <soap12:address location="http://my-server:my-port/" />
    </port>
  </service>
</definitions>

pom.xml 依赖项:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <groupId>my.project</groupId>
  <artifactId>project</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>My project</name>
  <description>My project description</description>

  <dependencies>
    <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.1</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <scope>provided</scope>
      <version>2.4</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.cxf</groupId>
      <artifactId>cxf-rt-frontend-jaxws</artifactId>
      <version>2.5.1</version>
    </dependency>
    <dependency>
      <groupId>org.apache.cxf</groupId>
      <artifactId>cxf-rt-transports-http-jetty</artifactId>
      <version>2.5.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.0</version>
      </plugin>
      <plugin>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-codegen-plugin</artifactId>
        <version>2.5.1</version>
        <executions>
          <execution>
            <id>generate-sources</id>
            <phase>generate-sources</phase>
            <configuration>
              <sourceRoot>${project.build.directory}/generated-sources/java</sourceRoot>
              <wsdlOptions>
                <wsdlOption>
                  <wsdl>${project.basedir}/src/main/resources/wsdl/my-service.wsdl</wsdl>
                  <extraargs>
                    <extraarg>-p</extraarg>
                    <extraarg>http://my.project.service=my.project.service</extraarg>
                  </extraargs>
                </wsdlOption>
              </wsdlOptions>
            </configuration>
            <goals>
              <goal>wsdl2java</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

</project>

实施:

package my.project.service;

@javax.jws.WebService(serviceName = "MyService", portName = "MyServicePort",
    targetNamespace = "http://my.project.service",
    wsdlLocation = "src/main/resources/wsdl/my-service.wsdl",
    endpointInterface = "my.project.service.MyServicePortType")
public class MyServicePortTypeImpl implements MyServicePortType {

  public my.project.service.MyOperationResponse myOperation(MyOperationRequest parameters) {
    MyOperationResponse myOperationResponse = new MyOperationResponse();
    myOperationResponse.setMyMessage("YOUPI!");
    return myOperationResponse;
  }

}

测试class:

package my.project.test;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

import javax.xml.namespace.QName;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.endpoint.Server;
import org.apache.cxf.frontend.ServerFactoryBean;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

import my.project.service.MyService;
import my.project.service.MyServicePortTypeImpl;
import my.project.service.MyObject;
import my.project.service.ObjectFactory;
import my.project.service.MyServicePortType;
import my.project.service.MyOperationRequest;
import my.project.service.MyOperationResponse;

public class MyWebServiceClientTest {

  private static final Log LOG = LogFactory.getLog(MyWebServiceClientTest.class);

  private static Server myMockServer;

  private static final String MY_MOCK_SERVICE_ADDRESS = "http://localhost:9091/MyService";
  private static final String MY_WSDL_FILE_PATH = "classpath:wsdl/my-service.wsdl";
  private static final String MY_NAMESPACE = "http://my.project.service";
  private static final QName MY_SERVICE_QNAME = new QName(MY_NAMESPACE, "MyService");
  private static final QName MY_SERVICE_PORT_QNAME = new QName(MY_NAMESPACE, "MyServicePort");

  public static Server createMockServer(String mockWebServiceAddress, boolean logging) {
    ServerFactoryBean serverFactoryBean = new ServerFactoryBean();
    serverFactoryBean.setAddress(mockWebServiceAddress);
    serverFactoryBean.setServiceClass(MyServicePortTypeImpl.class);
    serverFactoryBean.setWsdlLocation(MY_WSDL_FILE_PATH);
    serverFactoryBean.setServiceName(MY_SERVICE_QNAME);
    serverFactoryBean.setEndpointName(MY_SERVICE_PORT_QNAME);
    Server server = serverFactoryBean.create();

    if (logging) {
      Endpoint endpoint = server.getEndpoint();
      LoggingInInterceptor loggingInInterceptor = new LoggingInInterceptor();
      loggingInInterceptor.setPrettyLogging(true);
      endpoint.getBinding().getInInterceptors().add(loggingInInterceptor);
      LoggingOutInterceptor loggingOutInterceptor = new LoggingOutInterceptor();
      loggingOutInterceptor.setPrettyLogging(true);
      endpoint.getBinding().getOutInterceptors().add(loggingOutInterceptor);
    }

    return server;
  }

  @BeforeClass
  public static void setUp() throws FileNotFoundException, IOException {
    myMockServer = createMockServer(MY_MOCK_SERVICE_ADDRESS, true);
    LOG.info("Starting my mock server on " + MY_MOCK_SERVICE_ADDRESS);
    myMockServer.start();
  }

  @AfterClass
  public static void tearDown() {
    try {
      if (myMockServer != null) {
        LOG.info("Stopping my mock server");
        myMockServer.stop();
      }
    } catch (Throwable t) {
      LOG.error("Could not stop my mock server: ", t);
    }
  }

  @Test
  public void testMedwsClientSOAPCallWorks() {

    URL wsdlURL = null;
    try {
      wsdlURL = new URL(MY_MOCK_SERVICE_ADDRESS + "?wsdl");
    } catch (MalformedURLException e) {
      LOG.error("Could not create the URL for MED WSDL", e);
      return;
    }
    MyService myService = new MyService(wsdlURL, MY_SERVICE_QNAME);
    MyServicePortType port = myService.getMyServicePort();  

    LOG.info("Invoking myOperation...");
    MyOperationRequest myOperationRequest = new MyOperationRequest();
    ObjectFactory objectFactory = new ObjectFactory();
    MyObject myObject = objectFactory.createMyObject();
    myObject.setMyProperty("Go go go!");
    myOperationRequest.setMyObject(myObject);
    MyOperationResponse myOperationResponse = port.myOperation(myOperationRequest);
    LOG.info(myOperationResponse.getMyMessage());

  }
}

跟踪:

31 juil. 2015 17:55:38 org.apache.cxf.service.factory.ReflectionServiceFactoryBean checkServiceClassAnnotations
ATTENTION: A JAX-WS Annotation was found on my.project.service.MyServicePortTypeImpl while using the Simple frontend.  For better results, use the JAX-WS frontend.
31 juil. 2015 17:55:38 org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromWSDL
INFO: Creating Service {http://my.project.service}MyService from WSDL: classpath:wsdl/my-service.wsdl
31 juil. 2015 17:55:39 org.apache.cxf.endpoint.ServerImpl initDestination
INFO: Setting the server's publish address to be http://localhost:9091/MyService
2015-07-31 17:55:39.054:INFO:oejs.Server:jetty-7.5.3.v20111011
2015-07-31 17:55:39.085:INFO:oejs.AbstractConnector:Started SelectChannelConnector@localhost:9091 STARTING
2015-07-31 17:55:39.101:INFO:oejsh.ContextHandler:started o.e.j.s.h.ContextHandler{,null}
2015-07-31 17:55:39 INFO  MyWebServiceClientTest:73 - Starting my mock server on http://localhost:9091/MyService
31 juil. 2015 17:55:39 org.apache.cxf.services.MyService.MyServicePort.MyServicePortType
INFO: Inbound Message
----------------------------
ID: 1
Address: http://localhost:9091/MyService?wsdl
Encoding: UTF-8
Http-Method: GET
Content-Type: text/xml
Headers: {Accept=[*/*], Cache-Control=[no-cache], connection=[keep-alive], content-type=[text/xml], Host=[localhost:9091], Pragma=[no-cache], User-Agent=[Apache CXF 2.5.1]}
--------------------------------------
31 juil. 2015 17:55:39 org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromWSDL
INFO: Creating Service {http://my.project.service}MyService from WSDL: http://localhost:9091/MyService?wsdl
2015-07-31 17:55:39 INFO  MyWebServiceClientTest:102 - Invoking myOperation...
31 juil. 2015 17:55:39 org.apache.cxf.services.MyService.MyServicePort.MyServicePortType
INFO: Inbound Message
----------------------------
ID: 2
Address: http://localhost:9091/MyService
Encoding: UTF-8
Http-Method: POST
Content-Type: application/soap+xml; charset=UTF-8
Headers: {Accept=[*/*], Cache-Control=[no-cache], connection=[keep-alive], Content-Length=[237], content-type=[application/soap+xml; charset=UTF-8], Host=[localhost:9091], Pragma=[no-cache], User-Agent=[Apache CXF 2.5.1]}
Payload: <?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
  <soap:Body>
    <MyOperationRequest xmlns="http://my.project.service">
      <myObject>
        <myProperty>Go go go!</myProperty>
      </myObject>
    </MyOperationRequest>
  </soap:Body>
</soap:Envelope>

--------------------------------------
31 juil. 2015 17:55:39 org.apache.cxf.phase.PhaseInterceptorChain doDefaultLogging
ATTENTION: Interceptor for {http://my.project.service}MyService#{http://my.project.service}MyOperation has thrown exception, unwinding now
org.apache.cxf.interceptor.Fault: Unmarshalling Error: unexpected element (uri:"http://my.project.service", local:"myProperty"). Expected elements are <{http://my.project.service}myObject> 
  at org.apache.cxf.jaxb.JAXBEncoderDecoder.unmarshall(JAXBEncoderDecoder.java:823)
  at org.apache.cxf.jaxb.JAXBEncoderDecoder.unmarshall(JAXBEncoderDecoder.java:644)
  at org.apache.cxf.jaxb.io.DataReaderImpl.read(DataReaderImpl.java:156)
  at org.apache.cxf.interceptor.DocLiteralInInterceptor.getPara(DocLiteralInInterceptor.java:260)
  at org.apache.cxf.interceptor.DocLiteralInInterceptor.handleMessage(DocLiteralInInterceptor.java:127)
  at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:263)
  at org.apache.cxf.transport.ChainInitiationObserver.onMessage(ChainInitiationObserver.java:123)
  at org.apache.cxf.transport.http_jetty.JettyHTTPDestination.serviceRequest(JettyHTTPDestination.java:323)
  at org.apache.cxf.transport.http_jetty.JettyHTTPDestination.doService(JettyHTTPDestination.java:289)
  at org.apache.cxf.transport.http_jetty.JettyHTTPHandler.handle(JettyHTTPHandler.java:72)
  at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:942)
  at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:878)
  at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:117)
  at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:250)
  at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:110)
  at org.eclipse.jetty.server.Server.handle(Server.java:345)
  at org.eclipse.jetty.server.HttpConnection.handleRequest(HttpConnection.java:441)
  at org.eclipse.jetty.server.HttpConnection$RequestHandler.content(HttpConnection.java:936)
  at org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:801)
  at org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:218)
  at org.eclipse.jetty.server.AsyncHttpConnection.handle(AsyncHttpConnection.java:52)
  at org.eclipse.jetty.io.nio.SelectChannelEndPoint.handle(SelectChannelEndPoint.java:586)
  at org.eclipse.jetty.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:44)
  at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:598)
  at org.eclipse.jetty.util.thread.QueuedThreadPool.run(QueuedThreadPool.java:533)
  at java.lang.Thread.run(Thread.java:662)
Caused by: javax.xml.bind.UnmarshalException
 - with linked exception:
[javax.xml.bind.UnmarshalException: unexpected element (uri:"http://my.project.service", local:"myProperty"). Expected elements are <{http://my.project.service}myObject>]
  at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallerImpl.handleStreamException(UnmarshallerImpl.java:434)
  at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:371)
  at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal(UnmarshallerImpl.java:348)
  at org.apache.cxf.jaxb.JAXBEncoderDecoder.doUnmarshal(JAXBEncoderDecoder.java:784)
  at org.apache.cxf.jaxb.JAXBEncoderDecoder.access0(JAXBEncoderDecoder.java:96)
  at org.apache.cxf.jaxb.JAXBEncoderDecoder.run(JAXBEncoderDecoder.java:812)
  at java.security.AccessController.doPrivileged(Native Method)
  at org.apache.cxf.jaxb.JAXBEncoderDecoder.unmarshall(JAXBEncoderDecoder.java:810)
  ... 25 more
Caused by: javax.xml.bind.UnmarshalException: unexpected element (uri:"http://my.project.service", local:"myProperty"). Expected elements are <{http://my.project.service}myObject>
  at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallingContext.handleEvent(UnmarshallingContext.java:662)
  at com.sun.xml.bind.v2.runtime.unmarshaller.Loader.reportError(Loader.java:258)
  at com.sun.xml.bind.v2.runtime.unmarshaller.Loader.reportError(Loader.java:253)
  at com.sun.xml.bind.v2.runtime.unmarshaller.Loader.reportUnexpectedChildElement(Loader.java:120)
  at com.sun.xml.bind.v2.runtime.unmarshaller.Loader.childElement(Loader.java:105)
  at com.sun.xml.bind.v2.runtime.unmarshaller.StructureLoader.childElement(StructureLoader.java:251)
  at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallingContext._startElement(UnmarshallingContext.java:498)
  at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallingContext.startElement(UnmarshallingContext.java:480)
  at com.sun.xml.bind.v2.runtime.unmarshaller.StAXStreamConnector.handleStartElement(StAXStreamConnector.java:247)
  at com.sun.xml.bind.v2.runtime.unmarshaller.StAXStreamConnector.bridge(StAXStreamConnector.java:181)
  at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:369)
  ... 31 more
Caused by: javax.xml.bind.UnmarshalException: unexpected element (uri:"http://my.project.service", local:"myProperty"). Expected elements are <{http://my.project.service}myObject>
  ... 42 more
31 juil. 2015 17:55:39 org.apache.cxf.binding.soap.interceptor.Soap12FaultOutInterceptor$Soap12FaultOutInterceptorInternal handleMessage
INFO: class org.apache.cxf.binding.soap.interceptor.Soap12FaultOutInterceptor$Soap12FaultOutInterceptorInternalapplication/soap+xml
2015-07-31 17:55:39 INFO  MyWebServiceClientTest:81 - Stopping my mock server
2015-07-31 17:55:39.367:INFO:oejsh.ContextHandler:stopped o.e.j.s.h.ContextHandler{,null}

对不起,我很模糊,当我知道更多的时候,我愿意改进这个问题...

感谢您的宝贵时间。

编辑 2015-08-18:在 wsdl 中,如果我将元素 MyOperationRequest 重命名为 MyOperation(操作的名称), CXF 将启用包装样式,测试将通过。

我的问题是我无法更改 wsdl,我需要关闭使用包装器样式的模拟服务。

编辑 2015-08-21:将 wsdl 样式设置为 rpc 可以让我绕过这个问题,但这种解决方案在我的情况下是不可接受的。也许我可以更改 wsdl,但请求和响应结构不能更改。

我正在删除我的解决方案(将样式设置为 rpc)。

我认为错误在wsdl中,这是我的理解:

包装样式假定操作名称与包装元素名称相匹配,但在我的 wsdl 中并非如此。

非wrapper风格假定wsdl风格是rpc,但在我的wsdl中不是这样

为了与两种包装器样式中的一种兼容,我认为我必须更改 wsdl。将 wsdl 样式设置为 rpc 是不可接受的,因为响应的结构会发生变化。

所以我的解决方案是更改操作的名称,我需要更新 Java 中的实现方法的名称,但请求和响应消息将保持不变。

<portType name="MyServicePortType">
  <operation name="MyOperationRequest">
    <input message="tns:MyOperationRequest" name="MyOperationRequest" />
    <output message="tns:MyOperationResponse" name="MyOperationResponse" />
  </operation>
</portType>
<binding name="MyServiceBinding" type="tns:MyServicePortType">
  <soap12:binding style="rpc"
    transport="http://schemas.xmlsoap.org/soap/http" />
  <operation name="MyOperationRequest">
    <soap12:operation soapAction="" style="document" />
    [...]
  </operation>
</binding>

对我有帮助的文章:https://myarch.com/wrappernon-wrapper-web-service-styles-things-you-need-to-know/