¿Cómo hacer una llamada al servicio web SOAP desde la clase Java?

116

Soy relativamente nuevo en el mundo de los servicios web y mi investigación parece haberme confundido más que iluminarme, mi problema es que me dieron una biblioteca (jar) que tengo que ampliar con algunas funciones de servicio web.

Esta biblioteca se compartirá con otros desarrolladores, y entre las clases en el jar habrá clases que tengan un método que llame a un servicio web (que esencialmente establece un atributo de la clase, hace algo de lógica comercial, como almacenar el objeto en una base de datos, etc y devuelve el objeto con esas modificaciones). Quiero que la llamada a este servicio sea lo más simple posible, con suerte, tan simple para que el desarrollador que usa la clase solo tenga que hacerlo.

Car c = new Car("Blue");
c.webmethod();

He estado estudiando JAX-WS para usar en el servidor pero me parece que no necesito crear un wsimporten el servidor ni wsimporten el cliente, ya que sé que ambos tienen las clases, solo necesito algo de interacción entre clases compartido tanto en el servidor como en el cliente. ¿Cómo crees que tiene sentido hacer el servicio web y la llamada en la clase?

jpz
fuente
Tu pregunta es un poco confusa. El método que desea crear (1) obtendrá el objeto del servicio web; (2) trabajar un poco con el objeto; y (3) publicarlo de nuevo en el servicio web. ¿Es asi?
acdcjunior
No, el objeto se creará en el cliente, se enviará al ws en la llamada, el ws establecerá una variable, por ejemplo currentTime, hará algo de lógica empresarial como almacenarlo en una base de datos y luego enviará el objeto de vuelta al cliente con el currentTime ahora configurado. Espero haberme explicado un poco mejor. Gracias.
jpz

Respuestas:

273

Entiendo que su problema se reduce a cómo llamar a un servicio web SOAP (JAX-WS) desde Java y obtener su objeto de retorno . En ese caso, tiene dos enfoques posibles:

  1. Genere las clases de Java wsimporty utilícelas; o
  2. Cree un cliente SOAP que:
    1. Serializa los parámetros del servicio en XML;
    2. Llama al método web mediante manipulación HTTP; y
    3. Analice la respuesta XML devuelta en un objeto.


Sobre el primer enfoque (uso wsimport):

Veo que ya tiene las clases de negocio de los servicios (entidades u otras), y es un hecho que wsimportgenera un nuevo conjunto de clases (que de alguna manera son duplicados de las clases que ya tiene).

Sin embargo, me temo que en este escenario solo puedes:

  • Adapte (edite) el wsimportcódigo generado para que use sus clases de negocios (esto es difícil y de alguna manera no vale la pena; tenga en cuenta que cada vez que cambie el WSDL, tendrá que regenerar y readaptar el código); o
  • Ríndete y usa las wsimportclases generadas. (En esta solución, su código comercial podría "usar" las clases generadas como un servicio desde otra capa arquitectónica).

Acerca del segundo enfoque (cree su cliente SOAP personalizado):

Para implementar el segundo enfoque, deberá:

  1. Haz la llamada:
    • Utilice el marco SAAJ (SOAP con API de archivos adjuntos para Java) (consulte a continuación, se envía con Java SE 1.6 o superior) para realizar las llamadas; o
    • También puedes hacerlo a través java.net.HttpUrlconnection(y algo de java.iomanipulación).
  2. Convierta los objetos en XML y viceversa:
    • Utilice un marco OXM (Object to XML Mapping) como JAXB para serializar / deserializar el XML desde / hacia objetos
    • O, si es necesario, cree / analice manualmente el XML (esta puede ser la mejor solución si el objeto recibido es solo un poco diferente del enviado).

Crear un cliente SOAP usando Classic java.net.HttpUrlConnectionno es tan difícil (pero tampoco tan simple), y puedes encontrar en este enlace un muy buen código de inicio.

Te recomiendo que uses el marco SAAJ:

SOAP con API de adjuntos para Java (SAAJ) se utiliza principalmente para tratar directamente con los mensajes de solicitud / respuesta de SOAP que ocurren detrás de escena en cualquier API de servicio web. Permite a los desarrolladores enviar y recibir mensajes de jabón directamente en lugar de utilizar JAX-WS.

Vea a continuación un ejemplo funcional (¡ejecútelo!) De una llamada de servicio web SOAP usando SAAJ. Llama a este servicio web .

import javax.xml.soap.*;

public class SOAPClientSAAJ {

    // SAAJ - SOAP Client Testing
    public static void main(String args[]) {
        /*
            The example below requests from the Web Service at:
             https://www.w3schools.com/xml/tempconvert.asmx?op=CelsiusToFahrenheit


            To call other WS, change the parameters below, which are:
             - the SOAP Endpoint URL (that is, where the service is responding from)
             - the SOAP Action

            Also change the contents of the method createSoapEnvelope() in this class. It constructs
             the inner part of the SOAP envelope that is actually sent.
         */
        String soapEndpointUrl = "https://www.w3schools.com/xml/tempconvert.asmx";
        String soapAction = "https://www.w3schools.com/xml/CelsiusToFahrenheit";

        callSoapWebService(soapEndpointUrl, soapAction);
    }

    private static void createSoapEnvelope(SOAPMessage soapMessage) throws SOAPException {
        SOAPPart soapPart = soapMessage.getSOAPPart();

        String myNamespace = "myNamespace";
        String myNamespaceURI = "https://www.w3schools.com/xml/";

        // SOAP Envelope
        SOAPEnvelope envelope = soapPart.getEnvelope();
        envelope.addNamespaceDeclaration(myNamespace, myNamespaceURI);

            /*
            Constructed SOAP Request Message:
            <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:myNamespace="https://www.w3schools.com/xml/">
                <SOAP-ENV:Header/>
                <SOAP-ENV:Body>
                    <myNamespace:CelsiusToFahrenheit>
                        <myNamespace:Celsius>100</myNamespace:Celsius>
                    </myNamespace:CelsiusToFahrenheit>
                </SOAP-ENV:Body>
            </SOAP-ENV:Envelope>
            */

        // SOAP Body
        SOAPBody soapBody = envelope.getBody();
        SOAPElement soapBodyElem = soapBody.addChildElement("CelsiusToFahrenheit", myNamespace);
        SOAPElement soapBodyElem1 = soapBodyElem.addChildElement("Celsius", myNamespace);
        soapBodyElem1.addTextNode("100");
    }

    private static void callSoapWebService(String soapEndpointUrl, String soapAction) {
        try {
            // Create SOAP Connection
            SOAPConnectionFactory soapConnectionFactory = SOAPConnectionFactory.newInstance();
            SOAPConnection soapConnection = soapConnectionFactory.createConnection();

            // Send SOAP Message to SOAP Server
            SOAPMessage soapResponse = soapConnection.call(createSOAPRequest(soapAction), soapEndpointUrl);

            // Print the SOAP Response
            System.out.println("Response SOAP Message:");
            soapResponse.writeTo(System.out);
            System.out.println();

            soapConnection.close();
        } catch (Exception e) {
            System.err.println("\nError occurred while sending SOAP Request to Server!\nMake sure you have the correct endpoint URL and SOAPAction!\n");
            e.printStackTrace();
        }
    }

    private static SOAPMessage createSOAPRequest(String soapAction) throws Exception {
        MessageFactory messageFactory = MessageFactory.newInstance();
        SOAPMessage soapMessage = messageFactory.createMessage();

        createSoapEnvelope(soapMessage);

        MimeHeaders headers = soapMessage.getMimeHeaders();
        headers.addHeader("SOAPAction", soapAction);

        soapMessage.saveChanges();

        /* Print the request message, just for debugging purposes */
        System.out.println("Request SOAP Message:");
        soapMessage.writeTo(System.out);
        System.out.println("\n");

        return soapMessage;
    }

}

Acerca del uso de JAXB para serializar / deserializar, es muy fácil encontrar información al respecto. Puede comenzar aquí: http://www.mkyong.com/java/jaxb-hello-world-example/ .

acdcjunior
fuente
¿Cómo configuro la versión de jabón usando el método mencionado anteriormente?
Rehecho el
Pude usar su método y funcionó cuando usé su URI, pero para mi propia solicitud SOAP obtengo una respuesta en la que ninguno de los valores se muestra como se esperaba, es decir <xsd:element name="Incident_Number" type="xsd:string"/>. Como puede ver, el elemento está cerrado y no se genera información desde el WS.
Martin Erlic
El GetInfoByCityes 503Service Unavailable, parece. :(
Brad Turek
@BradTurek D * mn! Yo simplemente lo reemplazó. ¡Gracias por hacérmelo saber! Buscaré otro y lo cambiaré en un momento.
acdcjunior
1
Para el transeúnte: si el código anterior (el ejemplo de punto final del servicio web SOAP) deja de funcionar o comienza a dar errores (como 500, 503, etc.), hágamelo saber para que pueda solucionarlo.
acdcjunior
3

O simplemente use wsdl2java de Apache CXF para generar objetos que pueda usar.

Está incluido en el paquete binario que puede descargar desde su sitio web. Simplemente puede ejecutar un comando como este:

$ ./wsdl2java -p com.mynamespace.for.the.api.objects -autoNameResolution http://www.someurl.com/DefaultWebService?wsdl

Utiliza el WSDL para generar objetos, que se puede utilizar como este (nombres de objeto también se agarraron por el WSDL, por lo que el suyo será un poco diferente):

DefaultWebService defaultWebService = new DefaultWebService();
String res = defaultWebService.getDefaultWebServiceHttpSoap11Endpoint().login("webservice","dadsadasdasd");
System.out.println(res);

Incluso hay un complemento de Maven que genera las fuentes: https://cxf.apache.org/docs/maven-cxf-codegen-plugin-wsdl-to-java.html

Nota: Si genera fuentes utilizando CXF e IDEA, es posible que desee ver esto: https://stackoverflow.com/a/46812593/840315

szab.kel
fuente
1
Tengo más de 30 wsdl en mi aplicación. Mientras preparaba recursos para solo 1 wsdl (que tiene 5 soapActions), mi IDE de Eclipse se colgó y generó alrededor de más de 100 MB de clases / objetos.
Manmohan_singh
-1

Encontré una forma alternativa mucho más sencilla de generar mensajes de jabón. Dado un objeto de persona:

import com.fasterxml.jackson.annotation.JsonInclude;

@JsonInclude(JsonInclude.Include.NON_NULL)
public class Person {
  private String name;
  private int age;
  private String address; //setter and getters below
}

A continuación se muestra un generador de mensajes de jabón simple:

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;

@Slf4j
public class SoapGenerator {

  protected static final ObjectMapper XML_MAPPER = new XmlMapper()
      .enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)
      .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
      .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
      .registerModule(new JavaTimeModule());

  private static final String SOAP_BODY_OPEN = "<soap:Body>";
  private static final String SOAP_BODY_CLOSE = "</soap:Body>";
  private static final String SOAP_ENVELOPE_OPEN = "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">";
  private static final String SOAP_ENVELOPE_CLOSE = "</soap:Envelope>";

  public static String soapWrap(String xml) {
    return SOAP_ENVELOPE_OPEN + SOAP_BODY_OPEN + xml + SOAP_BODY_CLOSE + SOAP_ENVELOPE_CLOSE;
  }

  public static String soapUnwrap(String xml) {
    return StringUtils.substringBetween(xml, SOAP_BODY_OPEN, SOAP_BODY_CLOSE);
  }
}

Puede utilizar por:

 public static void main(String[] args) throws Exception{
        Person p = new Person();
        p.setName("Test");
        p.setAge(12);

        String xml = SoapGenerator.soapWrap(XML_MAPPER.writeValueAsString(p));
        log.info("Generated String");
        log.info(xml);
      }
mel3kings
fuente