JAX-WS = Cuando Apache CXF está instalado, “roba” la implementación predeterminada de JDK JAX-WS, ¿cómo resolverlo?

Tengo un problema extraño.

  1. Usando wsimport, generé código JAX-WS als de un WSDL (en un proyecto eclipse java dedicado). Esto funciona bien en JDK6 sin dependencias externas (ejecutándose en Eclipse)

  2. Tengo un segundo proyecto donde una vez utilicé Apache CXF. Si copio el Código descrito en 1.) en este proyecto, de repente, el JDK no ejecuta las cosas de JAX-WS (archivos que he generado), sino Apache CXF.

¿Cómo puedo evitar que Apache CXF “ejecute” las cosas de JAX-WS? (El problema es que CXF no puede ejecutar el código …). Además, no entiendo completamente cómo Apache CXF descubre estas clases. ¿No los registré?

¡Muchas gracias! Markus

Apache CXF ( cxf-rt-frontend-jaxws-*.jar para ser precisos) se registra a sí mismo como un proveedor JAX-WS en la JVM. Dentro del JAR antes mencionado hay un archivo llamado: /META-INF/services/javax.xml.ws.spi.Provider con los siguientes contenidos:

 org.apache.cxf.jaxws.spi.ProviderImpl 

Si ahora observa javax.xml.ws.spi.FactoryFinder#find método javax.xml.ws.spi.FactoryFinder#find , descubrirá que JDK busca en CLASSPATH la presencia del archivo javax.xml.ws.spi.Provider y vuelve a la implementación Sun predeterminada si no está disponible. Entonces tienes dos opciones para forzar el repliegue:

  • elimine cxf-rt-frontend-jaxws-*.jar de CLASSPATH

  • o anula el archivo javax.xml.ws.spi.Provider provisto por CXF para señalar la ubicación alternativa

La segunda opción es en realidad un poco más fácil. Simplemente crea:

 /src/main/resources/META-INF/services/javax.xml.ws.spi.Provider 

archivo (suponiendo que esté utilizando Maven) con los siguientes contenidos:

 org.apache.cxf.jaxws.spi.ProviderImpl 

Eso es todo, probado con javax.xml.ws.Endpoint#publish .

Para la puesta en práctica por defecto:

 com.sun.xml.internal.ws.spi.ProviderImpl 

dentro de /src/main/resources/META-INF/services/javax.xml.ws.spi.Provider

Probé con el otro y simplemente no pude hacerlo funcionar, así que para configurar CXF si no estaba configurado en CXF, simplemente anulo el delegado dentro del servicio.

  try { loc = this.getClass().getResource(wsdlResource); QName qName = new QName( wsTargetNamespace, wsName ); service = new YourWS(loc, qName); Field delegateField = Service.class.getDeclaredField("delegate"); //ALLOW CXF SPECIFIC SERVICE DELEGATE ONLY! delegateField.setAccessible(true); ServiceDelegate previousDelegate = (ServiceDelegate) delegateField.get(service); if (!previousDelegate.getClass().getName().contains("cxf")) { ServiceDelegate serviceDelegate = ((Provider) Class.forName("org.apache.cxf.jaxws.spi.ProviderImpl").newInstance()) .createServiceDelegate(loc, qName, service.getClass()); log.info("The " + getClass().getSimpleName() + " delegate is changed from " + "[" + previousDelegate + "] to [" + serviceDelegate + "]"); delegateField.set(service, serviceDelegate); } port = service.getYourWSSoap(); 

Los mecanismos de búsqueda estándar no parecen funcionar bien en OSGi (*).

Hay dos formas en que he trabajado forzando al servicio a elegir la implementación CXF de javax.xml.ws.spi.Provider :

  • el enfoque de establecer delegate por reflexión dado en la respuesta de EpicPandaForce a esta pregunta ( https://stackoverflow.com/a/31892305/109079 )

  • llamando al nivel más bajo JaxWsProxyFactoryBean ; esto parece evitar todas las llamadas al javax.xml.ws.spi.FactoryFinder incluido con Java, que es la raíz del problema

Aquí hay un ejemplo de este último, para codificadores menos intrépidos que prefieren no cambiar de forma reflectiva los campos privados:

 JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean(); factory.getClientFactoryBean().getServiceFactory().setWsdlURL(WinRmService.WSDL_LOCATION); factory.setServiceName(WinRmService.SERVICE); factory.setEndpointName(WinRmService.WinRmPort); // factory.setFeatures(...); // if required Service winrm = factory.create(WinRm.class); Client client = ClientProxy.getClient(winrm); 

Un par de notas:

  • Puede ser necesario pasar una URL como la anterior, en lugar de la simple factory.setWsdlURL(String) si el WSDL es un recurso en la ruta de clase (evite el bundle://... no resuelto bundle://... URLs para los elementos classpath)

  • Es posible que necesite paquetes adicionales para características (como el direccionamiento)


(*) En cuanto a por qué los mecanismos de búsqueda no funcionan en la mayoría de los contenedores OSGi, echa un vistazo a este poco desagradable en FactoryFinder Oracle Java:

 private static final String OSGI_SERVICE_LOADER_CLASS_NAME = "com.sun.org.glassfish.hk2.osgiresourcelocator.ServiceLoader"; private static boolean isOsgi() { try { Class.forName(OSGI_SERVICE_LOADER_CLASS_NAME); return true; } catch (ClassNotFoundException ignored) { } return false; } 

OSGi = Glassfish? Fishy de hecho!

Tuve un problema similar. En mi caso, tuve que usar org.apache.cxf.jaxws.spi.ProviderImpl para cosas JAX-WS (creación de puntos finales de servicio web, etc.) y com.sun.xml.internal.ws.spi.ProviderImpl para publicar puntos finales en com.sun.net.httpserver.HttpsServer .

Pude resolver esto creando mi propio proveedor que extiende javax.xml.ws.spi.Provider y lo usa en lugar del predeterminado.

 package provider; import java.net.URL; import java.util.List; import javax.xml.namespace.QName; import javax.xml.transform.Source; import javax.xml.ws.Endpoint; import javax.xml.ws.EndpointReference; import javax.xml.ws.WebServiceFeature; import javax.xml.ws.spi.Provider; import javax.xml.ws.spi.ServiceDelegate; import javax.xml.ws.wsaddressing.W3CEndpointReference; import org.w3c.dom.Element; public class MyProvider extends Provider { @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public ServiceDelegate createServiceDelegate(URL wsdlDocumentLocation, QName serviceName, Class serviceClass) { try { return ((Provider) Class.forName("org.apache.cxf.jaxws.spi.ProviderImpl").newInstance()).createServiceDelegate(wsdlDocumentLocation, serviceName, serviceClass.getClass()); } catch (Exception e) { e.printStackTrace(); } return null; } @Override public Endpoint createEndpoint(String bindingId, Object implementor) { try { return ((Provider) Class.forName("com.sun.xml.internal.ws.spi.ProviderImpl").newInstance()).createEndpoint(bindingId, implementor); } catch (Exception e) { e.printStackTrace(); } return null; } @Override public Endpoint createAndPublishEndpoint(String address, Object implementor) { try { return ((Provider) Class.forName("com.sun.xml.internal.ws.spi.ProviderImpl").newInstance()).createAndPublishEndpoint(address, implementor); } catch (Exception e) { e.printStackTrace(); } return null; } @Override public EndpointReference readEndpointReference(Source eprInfoset) { try { return ((Provider) Class.forName("org.apache.cxf.jaxws.spi.ProviderImpl").newInstance()).readEndpointReference(eprInfoset); } catch (Exception e) { e.printStackTrace(); } return null; } @Override public  T getPort(EndpointReference endpointReference, Class serviceEndpointInterface, WebServiceFeature... features) { try { return ((Provider) Class.forName("org.apache.cxf.jaxws.spi.ProviderImpl").newInstance()).getPort(endpointReference, serviceEndpointInterface, features); } catch (Exception e) { e.printStackTrace(); } return null; } @Override public W3CEndpointReference createW3CEndpointReference(String address, QName serviceName, QName portName, List metadata, String wsdlDocumentLocation, List referenceParameters) { try { return ((Provider) Class.forName("org.apache.cxf.jaxws.spi.ProviderImpl").newInstance()).createW3CEndpointReference(address, serviceName, portName, metadata, wsdlDocumentLocation, referenceParameters); } catch (Exception e) { e.printStackTrace(); } return null; } } 

Entonces simplemente crea:

 /src/main/resources/META-INF/services/javax.xml.ws.spi.Provider 

archivo (suponiendo que esté utilizando Maven) con los siguientes contenidos:

 package.MyProvider