java - 使用 JAXB 数据绑定(bind)的基于 CXF WSDL 的通用服务器

标签 java wsdl cxf

我正在寻找一种解决方案,将 CXF 作为 Web 服务实现的提供者集成到应用程序中。应用程序应该能够基于提供的 WSDL 文件以动态方式实现 Web 服务(这意味着 SEI 类不可用)。由于应用程序通过自己的 servlet 管理 http 请求和 url 映射,因此使用标准 CXF servlet 发布端点是不可行的。另外,我想使用 JAXB 数据绑定(bind)。理想情况下,CXF 应该调用我的 Object invoke(String oper, Object...args) 来进行 Web 服务的实际处理。总体而言,它应该看起来像动态客户端,但针对服务器部分实现。

我已经设法使代码几乎可以工作,但遇到了一些我不明白的事情 - 稍后再讨论。

首先,我将 WSDL 读入字符串并创建其定义。定义已预先缓存在 wsdlManager 中,以便可以通过唯一引用进行访问。然后我创建 JaxWS 动态客户端并获取 CXF 为其生成的 JAXB 数据绑定(bind)。

        WSDLManager wsdlManager = serviceBus.getExtension(WSDLManager.class);
        WSDLFactory wsdlFactory = wsdlManager.getWSDLFactory();

        // Reader
        WSDLReader reader = wsdlFactory.newWSDLReader();
        reader.setFeature("javax.wsdl.verbose", true);
        reader.setFeature("javax.wsdl.importDocuments", true);

        Definition def = reader.readWSDL(null, TypeCast.getInputSource(wsdl)); // wsdl is a String containing wsdl definition

        // Precache definition using listenerRef as unique identifier
        wsdlManager.addDefinition(listenerRef, def);

        String[] compileOptions = null;

        // Create JAXWS dynamic client using precached address
        Client client = createClient(listenerRef, simpleDataBinding, allowElementReferences, compileOptions);

        if (Logger.isDebugEnabled()) 
           Logger.debug("WebServiceProcessor.initServiceListener: service client is created succefully: " + listenerName);

        EndpointInfo cei = client.getEndpoint().getEndpointInfo();

        // Use JAXB generated databinding
        DataBinding db = client.getEndpoint().getService().getDataBinding();

        if (Logger.isDebugEnabled()) 
           Logger.debug("WebServiceProcessor.initServiceListener: databinding: " + db);

动态客户端的创建很简单

   public Client createClient(String serviceDescription, boolean simpleDataBinding, boolean allowElementReferences, String[] schemaOptions) {
       ClassLoader old = Thread.currentThread().getContextClassLoader();

       try {
          ParentClassLoader dynaLoader = new ParentClassLoader();

          JaxWsDynamicClientFactory dynamicClientFactory = JaxWsDynamicClientFactory.newInstance(serviceBus);
          dynamicClientFactory.setSimpleBindingEnabled(simpleDataBinding);
          dynamicClientFactory.setAllowElementReferences(allowElementReferences);

          if (schemaOptions != null) {
             dynamicClientFactory.setSchemaCompilerOptions(schemaOptions);
          }

          return dynamicClientFactory.createClient(serviceDescription, dynaLoader);

       } catch (Throwable ex) {
          Logger.error("WebServiceProcessor.createClient: exception is caught: " + ex, ex);
          return null;

       } finally {
          Thread.currentThread().setContextClassLoader(old);
       }
   }

接下来,为了创建服务器内容,声明了一些帮助器类

   protected class MyWSDLServiceFactory extends WSDLServiceFactory {

       public MyWSDLServiceFactory(Bus b, Definition d) {
          super(b, d);
       }

       @Override
       public Service create() {
          Service svc = super.create();

          // Post init
          initializeDefaultInterceptors();
          initializeDataBindings(); 

          return svc;
       }
   }

   public class MyInvoker extends AbstractInvoker {

     protected final Object implementor = new Object();

     public MyInvoker() {
     }

     @Override
     public Object getServiceObject(Exchange context) {
        return implementor;
     }

     protected void throwable() throws Exception {

     }

     @Override
     public Object invoke(Exchange exchange, Object o) {

        List<Object> params = null;
        if (o instanceof List) {
            params = CastUtils.cast((List<?>)o);
        } else if (o != null) {
            params = new MessageContentsList(o);
        }

        if (Logger.isTraceEnabled()) {
           for (Object arg : params)
              Logger.trace("MyInvoker.invoke: arg: " + arg);
        }

        // Method holding declararions of throwable exceptions 
        Method m = null;
        try {
           m = MsyInvoker.class.getMethod("throwable");
        } catch (NoSuchMethodException ex) {
           // Strange
        }

        return invoke(exchange, null, m, params);
     }

     @Override
     protected Object performInvocation(Exchange exchange, Object serviceObject, Method m, Object[] paramArray) throws Exception {
        Message inMessage = exchange.getInMessage();
        BindingOperationInfo bop = exchange.getBindingOperationInfo();

        String oper = bop.getName().getLocalPart();

        // Process request
        return processWebListenerRequest(oper, paramArray);
     }
   }

   protected class MyDestinationFactory implements DestinationFactory {

     protected final Set<String> prefixes = Collections.unmodifiableSet(new HashSet<String> (Arrays.asList("http://", "https://")));

     @Override
     public Destination getDestination(EndpointInfo ei, Bus bus) throws IOException {
         return new MyDestination(ei, ei.getAddress());
     }

     @Override
     public Set<String> getUriPrefixes() {
         return prefixes;
     }

     @Override
     public List<String> getTransportIds() {
         return null;
     }
   }

   protected class MyDestination extends ServletDestination {

     public MyDestination(EndpointInfo ei, String path) throws IOException {
        super(serviceBus, null, ei, path, false);

        // Disable async support
        isServlet3 = false;
     }

     @Override
     protected void setupMessage(final Message inMessage, final ServletConfig config, final ServletContext context, final HttpServletRequest req, final HttpServletResponse resp) throws IOException {
        super.setupMessage(inMessage, config, context, req, resp);
     }

     @Override
     protected String getBasePath(String contextPath) throws IOException {
         if (endpointInfo.getAddress() == null) {
             return "";
         }
         return endpointInfo.getAddress();
     }
   }

然后我们准备创建服务器:

        MyWSDLServiceFactory sf = new MyWSDLServiceFactory(serviceBus, def);
        sf.setAllowElementRefs(allowElementReferences);
        sf.setDataBinding(db);

        Service svc = sf.create();

        // Clear cached definition
        wsdlManager.removeDefinition(def);

        svc.setInvoker(new MyInvoker());

        // Create endpoints

        for (ServiceInfo inf : svc.getServiceInfos()) {
           for (EndpointInfo ei : inf.getEndpoints()) {
              if (ei.getName().equals(cei.getName())) {

                 if (Logger.isDebugEnabled()) 
                    Logger.debug("WebServiceProcessor.initServiceListener: endpoint: " + ei.getName());

                 String addr = "/" + listenerRef;

                 try {
                     ei.setAddress(addr);
                     JaxWsEndpointImpl ep = new JaxWsEndpointImpl(serviceBus, svc, ei);

                     svc.getEndpoints().put(ei.getName(), ep);

                     ep.addHandlerInterceptors();
                     ep.getInInterceptors().add(new SoapUtil.SoapInLogger());

                     BindingFactoryManager bfm = serviceBus.getExtension(BindingFactoryManager.class);

                     // tried this but no effect
                     // ei.getBinding().setProperty("soap.force.doclit.bare", Boolean.TRUE);

                     String bindingId = ei.getBinding().getBindingId();

                     if (Logger.isDebugEnabled()) 
                        Logger.debug("WebServiceProcessor.initServiceListener: binding id: " + bindingId);

                     BindingFactory bindingFactory = bfm.getBindingFactory(bindingId);

                     Server server = new ServerImpl(serviceBus, ep, new MyDestinationFactory(), bindingFactory);

                     if (Logger.isDebugEnabled()) 
                        Logger.debug("WebServiceProcessor.initServiceListener: starting server: " + ei.getName());

                     server.start();

                     if (Logger.isDebugEnabled()) 
                        Logger.debug("WebServiceProcessor.initServiceListener: server is started: " + server.isStarted());

                     // Set reference
                     listeners.put(listenerRef, server); // Our map to keep web server listeners
                 } catch (EndpointException e) {
                     throw new ServiceConstructionException(e);
                 }
              }
           }
        }

服务器调用看起来像

     String address = "/" + listenerRef;
     Server server = listeners.get(listenerRef); // Find our server listener in a map

     if (server != null) {
        Endpoint ep = server.getEndpoint();
        EndpointInfo ei = ep.getEndpointInfo();

        if (Logger.isDebugEnabled())
          Logger.debug("WebServiceProcessor.invoke: endpoint: " + listenerName);

        try {
           AbstractHTTPDestination dest = (AbstractHTTPDestination) server.getDestination();
           AsyncContext asyncCtx = requestContext.getAsyncContext();

           HttpServletRequest req = (HttpServletRequest) asyncCtx.getRequest();
           HttpServletResponse resp = (HttpServletResponse) asyncCtx.getResponse();
           ServletContext sctx = req.getServletContext();
           ServletConfig scfg = null; 

           if (Logger.isDebugEnabled())
             Logger.debug("WebServiceProcessor.invoke: destination resolved successfully: " + listenerName);

           // Trigger CXF processing
           dest.invoke(scfg, sctx, req, resp);

           if (Logger.isDebugEnabled())
             Logger.debug("WebServiceProcessor.invoke: endpoint processed successfully: " + listenerName);

        } catch (Exception ex) {
           Logger.error("WebServiceProcessor.invoke: exception is caught: " + ex, ex);
        }
     }

正如我已经提到的,该解决方案几乎有效,我尝试使用 CXF 3.3 和我以 http://www.dneonline.com/calculator.asmx?WSDL 为例的一个 WSDL 来测试它。 。我设法使用 SoapUI 调用该服务并获得响应。但现在是奇怪的部分。当我使用标准请求调用网络服务时

<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
   <soap:Header/>
   <soap:Body>
   <Add xmlns="http://tempuri.org/">
       <intA>1</intA><intB>2</intB> 
   </Add>
   </soap:Body>
</soap:Envelope>

由于解码错误而失败

org.apache.cxf.interceptor.Fault: Unmarshalling Error: unexpected element (uri:"http://tempuri.org/", local:"intA"). Expected elements are <{http://tempuri.org/}Add>,<{http://tempuri.org/}AddResponse>,<{http://tempuri.org/}Divide>,<{http://tempuri.org/}DivideResponse>,<{http://tempuri.org/}Multiply>,<{http://tempuri.org/}MultiplyResponse>,<{http://tempuri.org/}Subtract>,<{http://tempuri.org/}SubtractResponse> 
    at org.apache.cxf.jaxb.JAXBEncoderDecoder.unmarshall(JAXBEncoderDecoder.java:932) ~[cxf-rt-databinding-jaxb-3.3.6.jar:3.3.6]
    at org.apache.cxf.jaxb.JAXBEncoderDecoder.unmarshall(JAXBEncoderDecoder.java:738) ~[cxf-rt-databinding-jaxb-3.3.6.jar:3.3.6]
    at org.apache.cxf.jaxb.io.DataReaderImpl.read(DataReaderImpl.java:170) ~[cxf-rt-databinding-jaxb-3.3.6.jar:3.3.6]
    at org.apache.cxf.wsdl.interceptors.DocLiteralInInterceptor.getPara(DocLiteralInInterceptor.java:325) ~[cxf-rt-wsdl-3.3.6.jar:3.3.6]
    at org.apache.cxf.wsdl.interceptors.DocLiteralInInterceptor.handleMessage(DocLiteralInInterceptor.java:127) ~[cxf-rt-wsdl-3.3.6.jar:3.3.6]

但它成功通过了验证

<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
   <soap:Header/>
   <soap:Body>
   <Add xmlns="http://tempuri.org/">
       <Add>
       <intA>1</intA><intB>2</intB> 
       </Add>
   </Add>
   </soap:Body>
</soap:Envelope>

但在本例中,传递给 MyInvoker 的参数是一个包含两个 null 元素的数组。尽管如此,它还是生成了格式正确的响应(除了计算值错误,因为输入参数为空)响应

<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
   <soap:Body>
      <ns1:AddResponse xmlns:ns1="http://tempuri.org/">
         <AddResult xmlns="http://tempuri.org/">0</AddResult>
      </ns1:AddResponse>
   </soap:Body>
</soap:Envelope>

所以问题是 - 解码以下有效请求可能会出现什么问题?

<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
   <soap:Header/>
   <soap:Body>
   <Add xmlns="http://tempuri.org/">
       <intA>1</intA><intB>2</intB> 
   </Add>
   </soap:Body>
</soap:Envelope>

我已经使用经过测试的 WSDL 检查了 CXF 动态客户端调用,我从中借用了 JAXB 数据绑定(bind),并且在调用此服务时它生成了完全相同的请求,但由于某种原因似乎无法对其进行解码。

另一个问题,我认为它与第一个问题相关,是为什么在第二个请求的情况下,中的未编码参数为空?对下一步该去哪里有什么建议吗?

提前致谢

最佳答案

这是我自己问题的答案。如果从动态客户端重用 CXF 服务实例并添加一些服务器部分拦截器,事情就会变得正确:

        Service svc = client.getEndpoint().getService();

        // Server part interceptors
        svc.getInInterceptors().add(new ServiceInvokerInterceptor());
        svc.getInInterceptors().add(new OutgoingChainInterceptor());
        svc.getInInterceptors().add(new OneWayProcessorInterceptor());

关于java - 使用 JAXB 数据绑定(bind)的基于 CXF WSDL 的通用服务器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61156797/

相关文章:

xml - 在 SOAPUI 中加载 WSDL 时出错 [****..XML.xsd] : FileNotFoundException

java - WebClient - Jira Rest api,出现意外结果

Maven cxf 插件错误

java - 具有多个证书的 CXF SSL 安全 Web 服务客户端

java - 由于某种原因无法打印输出

java - 如何读取 .class 文件的完全限定名称

ruby - 使用 savon 的 SOAP 消息标记名称中的大小写问题

java - Maven Web 服务 WSDL 生成不起作用

java - 无法在服务器端处理基于 cometd 的线程

java - 从字符串中提取子字符串