使用 JAXB 数据绑定的基于通用 CXF WSDL 的服务器

Generic CXF WSDL-based Server using JAXB databinding

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

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

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

        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 和我作为示例的一个 WSDL 对其进行测试 http://www.dneonline.com/calculator.asmx?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 的参数是一个包含两个空元素的数组。尽管如此,它还是生成了正确格式的(除了计算值是错误的,因为输入参数为空)响应

<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 数据绑定,并且它在调用此服务时生成完全相同的请求,但由于某种原因似乎无法解组它.

另外一个问题,我觉得跟第一个有关,为什么第二个请求的时候unmarshalled参数是null?对接下来要看的地方有什么建议吗?

提前致谢

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

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

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