Como fazer uma chamada de serviço da Web SOAP da classe Java?

116

Sou relativamente novo no mundo dos webservices e minha pesquisa parece ter me confundido mais do que me esclarecido. Meu problema é que recebi uma biblioteca (jar) que preciso estender com algumas funcionalidades do webservice.

Esta biblioteca será compartilhada com outros desenvolvedores, e entre as classes no jar estarão as classes que têm um método que chama um serviço da web (que essencialmente define um atributo da classe, faz alguma lógica de negócios, como armazenar o objeto em um banco de dados, etc e envia de volta o objeto com essas modificações). Quero tornar a chamada para este serviço o mais simples possível, com sorte o mais simples possível, de forma que o desenvolvedor que usa a classe só precise fazer.

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

Tenho estudado JAX-WS para usar no servidor, mas me parece que não preciso criar um wsimportno servidor nem wsimportno cliente, pois sei que ambos possuem as classes, só preciso de alguma interação entre as classes compartilhado no servidor e no cliente. Como você acha que faz sentido fazer o serviço da Web e a chamada na aula?

jpz
fonte
Sua pergunta não está um pouco clara. O método que você deseja criar (1) obterá o objeto do serviço da web; (2) trabalhe um pouco com o objeto; e (3) postá-lo de volta no serviço da web. É isso?
acdcjunior
Não, o objeto será criado no cliente, será enviado para o ws na chamada, o ws definirá uma variável, por exemplo currentTime, fará alguma lógica de negócios como armazená-lo em um banco de dados e enviará o objeto de volta ao cliente com o currentTime agora definido. Espero ter me explicado um pouco melhor. Obrigado.
jpz de

Respostas:

273

Eu entendo que seu problema se resume a como chamar um serviço da web SOAP (JAX-WS) de Java e obter seu objeto de retorno . Nesse caso, você tem duas abordagens possíveis:

  1. Gere as classes Java por meio wsimportdelas e as use; ou
  2. Crie um cliente SOAP que:
    1. Serializa os parâmetros do serviço para XML;
    2. Chama o método da web por meio da manipulação de HTTP; e
    3. Analise a resposta XML de retorno em um objeto.


Sobre a primeira abordagem (usando wsimport):

Vejo que você já tem as classes de negócios de serviços (entidades ou outras), e é um fato que o wsimportgera um novo conjunto de classes (que são de alguma forma duplicatas das classes que você já tem).

Infelizmente, neste cenário, você só pode:

  • Adapte (edite) o wsimportcódigo gerado para que ele use suas classes de negócios (isso é difícil e de alguma forma não vale a pena - tenha em mente que toda vez que o WSDL mudar, você terá que regenerar e readaptar o código); ou
  • Desista e use as wsimportclasses geradas. (Nesta solução, seu código de negócios poderia "usar" as classes geradas como um serviço de outra camada de arquitetura.)

Sobre a segunda abordagem (crie seu cliente SOAP personalizado):

Para implementar a segunda abordagem, você terá que:

  1. Faça a chamada:
    • Use a estrutura SAAJ (SOAP com API de anexos para Java) (veja abaixo, é fornecido com Java SE 1.6 ou superior) para fazer as chamadas; ou
    • Você também pode fazer isso por meio java.net.HttpUrlconnection(e algum java.iomanuseio).
  2. Transforme os objetos em XML e vice-versa:
    • Use uma estrutura OXM (Object to XML Mapping), como JAXB, para serializar / desserializar o XML de / para objetos
    • Ou, se necessário, crie / analise manualmente o XML (esta pode ser a melhor solução se o objeto recebido for apenas um pouco diferente do enviado).

Criar um cliente SOAP usando o clássico java.net.HttpUrlConnectionnão é tão difícil (mas também não é tão simples), e você pode encontrar neste link um código inicial muito bom.

Eu recomendo que você use a estrutura SAAJ:

SOAP com API de anexos para Java (SAAJ) é usado principalmente para lidar diretamente com mensagens de solicitação / resposta SOAP que acontecem nos bastidores em qualquer API de serviço da web. Ele permite que os desenvolvedores enviem e recebam diretamente mensagens soap em vez de usar JAX-WS.

Veja abaixo um exemplo funcional (execute-o!) De uma chamada de serviço da web SOAP usando SAAJ. Ele chama esse serviço da 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;
    }

}

Sobre o uso de JAXB para serializar / desserializar, é muito fácil encontrar informações sobre ele. Você pode começar aqui: http://www.mkyong.com/java/jaxb-hello-world-example/ .

acdcjunior
fonte
Como faço para definir a versão do sabão usando o método mencionado acima?
refeito em
Eu era capaz de usar o seu método e funcionou quando usei o seu URI mas para minha própria solicitação SOAP I obter uma resposta em que nenhum dos valores são apresentados como esperado, ou seja <xsd:element name="Incident_Number" type="xsd:string"/>. Como você pode ver, o elemento é fechado e nenhuma informação é gerada a partir do WS.
Martin Erlic
O GetInfoByCityé 503Service Unavailable, parece. :(
Brad Turek
@BradTurek D * mn! Eu apenas substituí. Obrigado por me avisar! Vou encontrar outro e mudar para ele em breve.
acdcjunior
1
Para o passante: Se o código acima (o ponto de extremidade do SOAP Web Service de exemplo) parar de funcionar ou começar a dar erros (como 500, 503, etc), avise-me para que eu possa corrigir.
acdcjunior
3

Ou apenas use wsdl2java do Apache CXF para gerar objetos que você pode usar.

Ele está incluído no pacote binário que você pode baixar no site deles. Você pode simplesmente executar um comando como este:

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

Ele usa o WSDL para gerar objetos, que você pode usar como este (nomes de objetos também são agarrados a partir do WSDL, então o seu vai ser um pouco diferente):

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

Existe até um plug-in Maven que gera as fontes: https://cxf.apache.org/docs/maven-cxf-codegen-plugin-wsdl-to-java.html

Nota: Se você gerar fontes usando CXF e IDEA, você pode querer dar uma olhada em: https://stackoverflow.com/a/46812593/840315

szab.kel
fonte
1
Tenho mais de 30 wsdl em meu aplicativo. Enquanto preparava recursos para apenas 1 wsdl (que tem 5 soapActions), meu Eclipse IDE travou e gerou cerca de 100+ MB de classes / objetos.
Manmohan_singh
-1

Encontrei uma forma alternativa muito mais simples de gerar mensagem de sabão. Dado um Objeto Pessoa:

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
}

Abaixo está um gerador de mensagem de sabão simples:

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);
  }
}

Você pode usar 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
fonte