Analysis of RPC and WebService

Posted by horseatingweeds on Fri, 10 May 2019 16:33:10 +0200

Although SpringCloud and Dubbo(x) are the main technologies in the popular RPC technology, some more traditional technologies cannot be avoided if interface calls are made.I happened to use WebService's technology the other day when making interface calls (Sections 8,9 are real development), which is exactly what I wrote here.

1. RPC-related Basics

1.1 What is RPC

----| RPC(Remote Procedure Call), a remote procedure call, is a protocol that requests services from a remote computer program over a network without needing to know the underlying network technology.(from Baidu Encyclopedia)

----| RPC allows a program to call a process or function in another address space, usually on another machine that shares the network, without explicitly coding the details of the remote call.(From the CSDN blog: https://blog.csdn.net/mindfloating/article/details/39473807)

Characteristics of 1.2 RPC

From the above concepts, the basic features of RPC can be roughly summarized as follows:

  • Transmitted over a network
  • Cross-terminal, cross-platform
  • Request-Response Based
  • Call procedures only, not details

Basic principles of 1.3 RPC

At the bottom level, RPC actually transfers streams from one computer to another, whether based on transport protocols (http, tcp, udp, etc.) and network IO(bio, nio).

1.4 Common RPC Call Technologies

  • SpringCloud (Spring, Socket-based, SOA architecture distributed framework)
  • Dubbo(x) (Alibaba, Socket-based, SOA architecture distributed framework)
  • WebService (cross-language, based on SOAP protocol, take xml or json data)
  • Hessian (cross-language, binary data based on Binary-RPC protocol)
  • HttpClient (usually used for RESTful style calls, cross-language, http and json based)
  • jdk native (HttpURLConnection)

Reference material: https://www.cnblogs.com/cainiao-Shun666/p/9181903.html

Since this article mainly introduces WebService, we don't introduce other calling technologies. Small partners who need it can get related blogs and information on the blog platform.

 

2. What is a WebService

Web service is a platform-independent, low-coupled, self-contained, programmable web-based application that can be described, published, discovered, coordinated, and configured using open XML (a subset of the standard common markup language) standards to develop distributed, interoperable applications.(from Baidu Encyclopedia)

Simply put: WebService is a cross-language and cross-operating system remote call technology.

There is a good paragraph in a blog, which is taken out for your little friend to read:

There are many ways to understand a Web service. Apparently, a Web Service is an application that exposes an API that can be invoked through the Web, that is, it can be invoked programmatically through the Web.We call the application that calls this WebService a client, and the application that provides this WebService a server.At a deeper level, WebService is a new platform for building interoperable distributed applications, a platform, and a set of standards.It defines how applications can interoperate on the Web. You can write Web services in any language you like and on any platform you like, as long as we can query and access them through the Web Service standard.(From blog: https://www.cnblogs.com/xdp-gacl/p/4048937.html)

3. Why use WebService

  • Platform Language Independence - WebService is based on SOAP protocol, and all published services have corresponding xml(wsdl), which enables cross-platform, cross-language support
  • Secure Communication - WebService walks http requests without firewall restrictions
  • Functional Reuse - By using WebService s, you can expose shared services and achieve functional reuse

In fact, a Web Service can be viewed as a separate function for external use.

4. Core of WebService

4.2 SOAP

  • SOAP(Simple Object Access Protocal), or Simple Object Access Protocol, describes the format in which information is delivered.
  • SOAP is a simple xml-based protocol and specification that enables applications to exchange information over http.
  • SOAP can be simply understood as http + xml.

In fact, when WebService sends requests and receives results through the http protocol, the content of the requests and results sent is encapsulated in xml format, and some specific http headers are added to explain the content format of http messages, which are the SOAP protocol.

4.2 WSDL

  • WSDL(Web Services Description Language), or Web Services Description Language, describes a Web Service.
  • One sentence summary: WSDL is an xml-based "instruction book" that describes a Web Service and its methods, parameters, and return values.
  • There are rules to follow when designing WSDL as xml: XML can be easily parsed by the DOM, and tags and attributes can be easily read to understand how the corresponding service is used.
  • Each Web Service must have its own WSDL, which can be used by developers to parse the source code of the corresponding language on different language platforms.

4.3 UDDI

The purpose of UDDI is to establish standards for e-commerce; UDDI is a set of Web-based, distributed, information registry implementation standards for Web Services, as well as a set of implementation standards that enable enterprises to register their own Web Services so that other enterprises can discover access protocols.

5. When can I use WebService

  • Provide uniform service access interface (no language restrictions)
  • Inter-system service publishing and invocation
  • Distributed (usable but inconvenient, of course, SpringCloud and Dubbo(x))
  • Services that don't require very high performance

6. How to use WebService in Java

  • jdk's native WebService approach (base)
  • CXF framework using Apache (common)
  • Using Apache's axis framework (multilingual, heavyweight)
  • Use XFire framework (lost)

This article uses native JWS and Apache's CXF framework to show how to use WebService, respectively

7. How to publish a WebService service

In order to use the same project as the following CXF framework, the Provider-Demo project is built directly with Maven here

7.1 Create a service provider's Maven project

Create a service provider's Maven project, which is packaged as war because it uses Spring to integrate with CXF

 

7.2 Creating a JWS-based Web Service

7.2.1 Create the JWSProvider class with the @WebService annotation

import javax.jws.WebService;

/**
 * JWS-based WebService Provider
 * @Title JWSProvider
 * @author LinkedBear
 */
@WebService
public class JWSProvider {
   
}

7.2.2 Writing service source

import javax.jws.WebService;
import javax.xml.ws.Endpoint;

/**
 * JWS-based WebService Provider
 * @Title JWSProvider
 * @author LinkedBear
 */
@WebService
public class JWSProvider {
    /**
     * Service function effect: append a random number to the name passed in
     * @param name
     * @return
     */
    public String getRandomCode(String name) {
        System.out.println("Be based on JWS Of WebService Services: getRandomCode Called.");
        return name + Math.random();
    }
    
    public static void main(String[] args) throws Exception {
        System.out.println("Start Publishing WebService Service.");
        Endpoint.publish("http://localhost/getRandomCode", new JWSProvider());
        System.out.println("WebService Service published successfully.");
    }
}

7.2.3 Execute main method, console print successfully prompts to start JWS-based service

Browser Input http://localhost/getRandomCode?wsdl You can then see the WSDL description for the service and the service was published successfully.

7.3 Creating a CXF-based Web Service

7.3.1 Adding CXF dependency to pom.xml

<properties>
	<spring.version>5.0.4.RELEASE</spring.version>
	<cxf.version>3.2.6</cxf.version>
</properties>

<dependencies>
    <!-- Import CXF Dependency on -->
	<dependency>
		<groupId>org.apache.cxf</groupId>
		<artifactId>cxf-core</artifactId>
		<version>${cxf.version}</version>
	</dependency>
	<dependency>
		<groupId>org.apache.cxf</groupId>
		<artifactId>cxf-rt-frontend-jaxws</artifactId>
		<version>${cxf.version}</version>
	</dependency>
	<dependency>
		<groupId>org.apache.cxf</groupId>
		<artifactId>cxf-rt-transports-http</artifactId>
		<version>${cxf.version}</version>
	</dependency>
	<dependency>
		<groupId>org.apache.cxf</groupId>
		<artifactId>cxf-rt-transports-http-jetty</artifactId>
		<version>${cxf.version}</version>
	</dependency>

	<!--Import Spring Dependency on -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-core</artifactId>
		<version>${spring.version}</version>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-beans</artifactId>
		<version>${spring.version}</version>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context</artifactId>
		<version>${spring.version}</version>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-expression</artifactId>
		<version>${spring.version}</version>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context-support</artifactId>
		<version>${spring.version}</version>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-webmvc</artifactId>
		<version>${spring.version}</version>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-aspects</artifactId>
		<version>${spring.version}</version>
	</dependency>
</dependencies>

7.3.2 Servlet for configuring CXF in web.xml

<!-- CXF Of Servlet -->
<servlet>
    <servlet-name>cxf</servlet-name>
    <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
    <init-param>
        <param-name>config-location</param-name>
        <param-value>classpath:applicationContext-cxf.xml</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>cxf</servlet-name>
    <url-pattern>/webservice/*</url-pattern>
</servlet-mapping>

7.3.3 Create applicationContext-cxf.xml in the src/main-resource directory

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:jaxws="http://cxf.apache.org/jaxws"
	xmlns:soap="http://cxf.apache.org/bindings/soap"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
                        http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://cxf.apache.org/bindings/soap 
                        http://cxf.apache.org/schemas/configuration/soap.xsd
                        http://cxf.apache.org/jaxws 
                        http://cxf.apache.org/schemas/jaxws.xsd">
	<!-- Import jar In Package cxf configuration file -->
	<import resource="classpath:META-INF/cxf/cxf.xml" />
	<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

</beans>

7.3.4 Creating service provider interfaces and implementation classes

Note that the @WebService annotation is added to the interface!

import javax.jws.WebService;

/**
 * Service Provider Interface Based on CXF
 * @Title CXFProvider
 * @author LinkedBear
 */
@WebService
public interface CXFProvider {
    String getNameHashCode(String name);
}

Implementation class:

/**
 * Service Providers Based on CXF
 * @Title CXFProvider
 * @author LinkedBear
 */
public class CXFProviderImpl implements CXFProvider {
    /**
     * Service function effect: append hashCode after passed in name
     * @param name
     * @return
     */
    @Override
    public String getNameHashCode(String name) {
        System.out.println("Be based on CXF Of WebService Services: getNameHashCode Called.");
        return name + name.hashCode();
    }
}

7.3.5 Registering services in applicationContext-cxf

<!-- Objects for registering services -->
<bean id="cxfProvider" class="com.linkedbear.webservice.cxf.CXFProviderImpl"/>
<!-- Registration Services -->
<jaxws:server id="nameHashCodeServer" address="/nameHashCodeServer">
    <jaxws:serviceBean>
        <ref bean="cxfProvider"/>
    </jaxws:serviceBean>
</jaxws:server>

7.3.6 Deploy the project to Tomcat and start the server

Be careful!If Spring's dependencies are not fully reported, an error will be reported at startup!

Browser Input http://localhost:8080/WebService-Provider-Demo/webservice/nameHashCodeServer?wsdl You can then see the WSDL description for the service and the service was published successfully.

8. How to invoke the WebService service

Create a Maven project for the service caller first.

Since the caller is only responsible for using the service, it is sufficient to package it as jar.

Also import CXF and Spring dependencies.

 

Once the service is published, the next step is how to invoke the service.

The following three sections describe how to invoke a WebService service

8.1 Use the wsimport command that comes with jdk to generate local source code for JWS-based calls

In the jdk installation directory, find the bin directory, in addition to the common commands like javac, javap, javadoc, which we are familiar with everyday, there is also a wsimport command:

With this command, the specified WSDL can be converted to a local source + compiled byte code file.

8.1.1 wsimport usage

Common command parameters are as follows:

  1. -d <directory> Generate class file in specified directory
  2. -clientjar <jarfile> generates jar files in the current directory, and-D <directory> generates jar files in the specified directory
  3. -s <directory> Generate java source files in the specified directory
  4. -p <pkg> Specify the package structure of the generated file
  5. -keep Keep java source files while generating class files or jar packages

The most common command line statements are:

wsimport –s . <wsdl>

By. we mean that the source code is generated in the current folder

 

We take out the WSDL of the JMS-based Web Service just written and use the wsimport command to generate the local source code as follows:

http://localhost/getRandomCode?wsdl

8.1.2 Copy parsed.java into the project

8.1.3 Calling services using JMS

As you can see from the generated source code, GetRandomCode.java is the calling class for the service and must be used if you want to use the service.

But if you want to create an object of this class to invoke it directly, you'll just throw it away:

How can this class not have a corresponding method?

Since you think this class is the name of the service we published, can it be analogous to the Method class in Java reflection?

Now that the Method class represents a method, it must have an execution object before it can call it.

 

Correct invocation:

public class JWSConsumer {
    public static void main(String[] args) throws Exception {
        //Create the WebService service object first
        JWSProviderService webservice = new JWSProviderService();
        //Re-create the provider object for the service
        JWSProvider provider = webservice.getJWSProviderPort();
        //After that, you can invoke the service
        String code = provider.getRandomCode("LinkedBear");
        System.out.println(code);
    }
}

8.2 Calling services using the CXF framework

Using the CXF framework, generate your own source code in a cost-effective manner using CXF.

8.2.1 wsdl2java usage

The parsing command provided by CXF is wsdl2java.

When we started using wsimport, it worked even without entering the jdk directory because we configured the environment variables for PATH.

If you want to use the wsdl2java command of CXF anywhere, you also need to configure environment variables.However, considering that the frequency of this command is so low, it is not configured to go directly to the CXF directory.

Download the CXF framework package from the official website, and after unzipping, you can find the wsdl2java.bat from the bin directory.

Common command parameters for the wsdl2java command are as follows:

  1. -p Specify the generated client package name
  2. -d Specify the generated client build directory
  3. --compile Specifies that compilation is required
  4. -c Specify the directory generated by the compilation
  5. -client Specifies the generation of the client call class, that is, the class containing the main method call client method

 

By executing the wsdl2java command, WSDL can be converted to.Java (no.class)

8.2.2 Copy parsed.java into the project

We just need the only interface, not all the source files.

However, after deleting other source code, the interface will fail because there is a @XmlSeeAlso annotation on the interface that requires the class object to be passed into the ObjectFactory. Instead of deleting the Factory, simply leave a pair of empty curly braces.

8.2.3 Injecting services locally

Spring is also used by service callers because when CXF works with Spring, only interface files can be left and Spring's service injection can be used for remote calls.

In the src/main/resource directory, create the applicationContext.xml file and inject the CXF service into Spring's container (the principle is that Spring creates a proxy object for this interface and remotely invokes the service-side interface through the proxy object).

<jaxws:client id="consumer" 
                address="http://localhost:8080/WebService-Provider-Demo/webservice/nameHashCodeServer" 
                serviceClass="com.linkedbear.webservice.cxf.CXFProvider"/>

8.2.4 Calling services using CXF

public class App {
    public static void main(String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        CXFProvider provider = (CXFProvider) ctx.getBean("consumer");
        String nameHashCode = provider.getNameHashCode("LinkedBear");
        System.out.println(nameHashCode);
    }
}

8.3 Calling services without generating local source

In the first two ways, the biggest disadvantage is the need to generate local source code, which takes a lot of time and effort. In case the server changes the access rules of the interface, you will have to regenerate it and then modify the business code.

Then we thought: Is there a way to call a WebService service without generating local source code?

Yes?Using the HttpClient with the SOAP protocol, it is possible to call the WebService service without generating local source code.

8.3.1 Why can services also be successfully invoked using HttpClient?

We say that WebService s are based on the SOAP protocol, and we use local source to send requests, which are actually SOAP-based POST requests, and receive responses that are also SOAP-based responses.

So, if we construct our own POST requests based on the SOAP protocol, will the service return the results normally?Certainly!

However, the only downside is that you construct your own source code and need to parse the response volume after you get the response.

8.3.2 Dependency to import HttpClient

With HttpClient, you need to import the dependencies of the HttpClient (be careful not to make mistakes).

<dependency>
	<groupId>commons-httpclient</groupId>
	<artifactId>commons-httpclient</artifactId>
	<version>3.1</version>
</dependency>

8.3.3 Request Body xml format for SOAP protocol (streamlined)

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <soap:Body>
        <[method] xmlns="[namaspace]">
            <[args]>[text]</[args]>
        </[method]>
    </soap:Body>
</soap:Envelope>

In the format above, requests identified in square brackets as specific WebService s.

Just a simple chestnut:

url is http://localhost/getRandomCode?wsdl

The namespace inside is self-specified, not necessarily a section of the requested path.

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <soap:Body>
        <getRandomCode xmlns="http://webservice.linkedbear.com/">
            <name>LinkedBear</name>
        </getRandomCode>
    </soap:Body>
</soap:Envelope>

8.3.4 Modify Service Provider Source

To enable Web Services to support SOAP POST request access, you need to add a comment identifier to each parameter to indicate that it can be used by SOAP requests.

The modified source code is as follows (note the note above the parameter):

@WebService
public class JWSProvider {
    public String getRandomCode(
                @WebParam(name="name",targetNamespace="http://webservice.linkedbear.com/") String name
            ) {
        System.out.println("Be based on JWS Of WebService Services: getRandomCode Called.");
        return name + Math.random();
    }
    
    public static void main(String[] args) throws Exception {
        Endpoint.publish("http://localhost/getRandomCode", new JWSProvider());
    }
}

8.3.5 Use HttpClient to send POST requests and invoke WebService services

public class App {
    public static void main(String[] args) throws Exception {
        String url = "http://localhost/getRandomCode?wsdl";
        StringBuilder sb = new StringBuilder();
        sb.append("<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">");
        sb.append("  <soap:Body>");
        sb.append("    <getRandomCode xmlns=\"http://webservice.linkedbear.com/\">");
        sb.append("      <name>LinkedBear</name>");
        sb.append("    </getRandomCode>");
        sb.append("  </soap:Body>");
        sb.append("</soap:Envelope>");
        PostMethod postMethod = new PostMethod(url);
        byte[] bytes = sb.toString().getBytes("utf-8");
        InputStream inputStream = new ByteArrayInputStream(bytes, 0, bytes.length);
        RequestEntity requestEntity = new InputStreamRequestEntity(inputStream, bytes.length, "text/xml;charset=UTF-8");
        postMethod.setRequestEntity(requestEntity);
        
        HttpClient httpClient = new HttpClient();
        httpClient.executeMethod(postMethod);
        String soapResponseData = postMethod.getResponseBodyAsString();
        System.out.println(soapResponseData);
    }
}

Request results (The response volume does not really have a line break symbol, it just goes out on one line.):

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><ns2:getRandomCodeResponse xmlns:ns2="http://jws.webservice.linkedbear.com/"><return>LinkedBear0.0681470650599495</return></ns2:getRandomCodeResponse></soap:Body></soap:Envelope>

8.3.6 Extracting Response Data

We can use Dom4j to extract the data of the responder entirely, but Dom4j can only pick up one layer at a time, which is too much effort.I recommend that you use Jsoup for xml conversion and extraction.

The jar for importing Jsoup is as follows:

<dependency>
	<groupId>org.jsoup</groupId>
	<artifactId>jsoup</artifactId>
	<version>1.11.3</version>
</dependency>

Then append the following to the source code to output only the desired return result.

Document document = Jsoup.parse(soapResponseData);
String text = document.getElementsByTag("return").text();
System.out.println(text);
//Output results:
//LinkedBear0.0681470650599495

9. [Additional] Manufacture generic SOAP-based Web Service request tools

 

It was already done, but for later convenience, pack a set of tools.This makes it easy to call later.

Principles of design: conform to the Open-Closed Principle as much as possible.

9.1 Design Basic Request Tool Class

The most basic tool class is to extract and encapsulate the SOAP protocol request body data just listed above into a Map.

The initial packaging is as follows:

public class WebServiceUtil {
    public static void invokeWebService(Map<String, Object> map) throws Exception {
        String url = (String) map.get("url");
        StringBuilder sb = new StringBuilder();
        sb.append("<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">");
        sb.append("  <soap:Body>");
        //Incoming method and namespace
        sb.append("    <" + map.get("method") + " xmlns=\"" + map.get("namespace") + "\">");
        //Dynamic construction parameters and values
        Map<String, String> argsMap = (Map<String, String>) map.get("argsMap");
        for (Map.Entry<String, String> entry : argsMap.entrySet()) {
            sb.append("      <" + entry.getKey() + ">" + entry.getValue() +"</" + entry.getKey() + ">");
        }
        sb.append("    </" + map.get("method") + ">");
        sb.append("  </soap:Body>");
        sb.append("</soap:Envelope>");
        PostMethod postMethod = new PostMethod(url);
        byte[] bytes = sb.toString().getBytes("utf-8");
        InputStream inputStream = new ByteArrayInputStream(bytes, 0, bytes.length);
        RequestEntity requestEntity = new InputStreamRequestEntity(inputStream, bytes.length, "text/xml;charset=UTF-8");
        postMethod.setRequestEntity(requestEntity);
        
        HttpClient httpClient = new HttpClient();
        httpClient.executeMethod(postMethod);
        String soapResponseData = postMethod.getResponseBodyAsString();
        
        //Extract data from the specified tag in the response body
        Document document = Jsoup.parse(soapResponseData);
        List<String> returnTagList = (List<String>) map.get("returnTagList");
        for (String returnTag : returnTagList) {
            Elements tags = document.getElementsByTag(returnTag);
            for (Element tag : tags) {
                System.out.println(tag.text());
            }
        }
    }
    
    private WebServiceUtil() {
    }
}

9.2 Second degree encapsulation: parameters are encapsulated as data structures with null validation

There are many inappropriate encapsulations in the above:

  1. The key value of the parameter is magic and can be modified at any time.
  2. Null values may be taken each time the get value from the map is taken;
  3. Elements in a collection taken from a map may also be null.

To address these issues, you need to construct a data structure for the WebServiceSoapBean to encapsulate the data that appears in the request.

The second-degree encapsulated source code is as follows (commons-lang is added to make the non-empty decision more convenient):

public class WebServiceSoapBean {
    private String url;
    private String method;
    private String namespace;
    private Map<String, String> argsMap;
    private List<String> returnTagList;
    //getter, setter
}

public class WebServiceUtil {
    public static void invokeWebService(WebServiceSoapBean bean) throws Exception {
        checkBeanIsComplete(bean);
        
        String url = bean.getUrl();
        StringBuilder sb = new StringBuilder();
        sb.append("<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">");
        sb.append("  <soap:Body>");
        //Incoming method and namespace
        sb.append("    <" + bean.getMethod() + " xmlns=\"" + bean.getNamespace() + "\">");
        //Dynamic construction parameters and values
        Map<String, String> argsMap = bean.getArgsMap();
        for (Map.Entry<String, String> entry : argsMap.entrySet()) {
            sb.append("      <" + entry.getKey() + ">" + entry.getValue() +"</" + entry.getKey() + ">");
        }
        sb.append("    </" + bean.getMethod() + ">");
        sb.append("  </soap:Body>");
        sb.append("</soap:Envelope>");
        PostMethod postMethod = new PostMethod(url);
        byte[] bytes = sb.toString().getBytes("utf-8");
        InputStream inputStream = new ByteArrayInputStream(bytes, 0, bytes.length);
        RequestEntity requestEntity = new InputStreamRequestEntity(inputStream, bytes.length, "text/xml;charset=UTF-8");
        postMethod.setRequestEntity(requestEntity);
        
        HttpClient httpClient = new HttpClient();
        httpClient.executeMethod(postMethod);
        String soapResponseData = postMethod.getResponseBodyAsString();
        
        //Extract data from the specified tag in the response body
        Document document = Jsoup.parse(soapResponseData);
        List<String> returnTagList = bean.getReturnTagList();
        for (String returnTag : returnTagList) {
            Elements tags = document.getElementsByTag(returnTag);
            for (Element tag : tags) {
                System.out.println(tag.text());
            }
        }
    }
    
    private static void checkBeanIsComplete(WebServiceSoapBean bean) {
        if (StringUtils.isBlank(bean.getUrl())) {
            throw new NullPointerException("I'm sorry, url Empty!");
        }
        if (StringUtils.isBlank(bean.getMethod())) {
            throw new NullPointerException("I'm sorry, method Empty!");
        }
        if (StringUtils.isBlank(bean.getNamespace())) {
            throw new NullPointerException("I'm sorry, namespace Empty!");
        }
        if (Objects.isNull(bean.getArgsMap())) {
            throw new NullPointerException("Sorry, the parameter list is null!");
        }
        if (Objects.isNull(bean.getReturnTagList())) {
            throw new NullPointerException("Sorry, the response body label list is null!");
        }
    }

    private WebServiceUtil2() {
    }
}

9.3 3 3-degree packaging: optimizing response volume data printing

The above encapsulation is not a big problem for data processing, but the printed response data is messy.

The next step is to separate the data extraction of the response volume into a single method, and the extraction return type should be Map or json.

Continue adding fastjson dependencies (JSONObject is more powerful and ordered than Map, with the underlying LinkedHashMap implementation).

The three-degree encapsulated source code is as follows (there is only one new method added):

//New method added:
private static List<JSONObject> parseResponseXmlToJson(String xml, List<String> returnTagList) {
    //Verify before converting: If there is no label at the root, simply empty the pointer
    if (returnTagList.isEmpty()) {
        throw new NullPointerException("Sorry, the Response Body Label Collection is empty!");
    }
    for (String tag : returnTagList) {
        //You can't just check tags, but also add angle brackets to prevent tags from crashing in response data
        if (!xml.contains("<" + tag + ">")) {
            throw new NullPointerException("Sorry, this tag is not in the response body:" + tag);
        }
    }
    
    Document document = Jsoup.parse(xml);
    //We've checked it before, so we can definitely take it out here
    Element parent = document.getElementsByTag(returnTagList.get(0)).get(0).parent();
    //Take peer, each peer is a json
    String tagName = parent.tagName();
    //Step back one level and look for this tagName to get all the data nodes
    Elements elements = parent.parent().getElementsByTag(tagName);
    List<JSONObject> list = new ArrayList<>(elements.size());
    for (Element element : elements) {
        JSONObject json = new JSONObject();
        for (String returnTag : returnTagList) {
            json.put(returnTag, element.getElementsByTag(returnTag).get(0).text());
        }
    }
    
    return list;
}

9.4 Four-degree packaging: template method mode

The current design has roughly what a generic tool class should have. Is that over?

Requirements: Personalize custom Web Service requests, including additional parameters, additional checks, additional extraction rules, and so on???

That generic tool class is not enough and needs to be extended vertically.

In object-oriented thinking, the implementation of vertical expansion is inheritance and polymorphism.

 

Then let's analyze: What is the difference between a generic tool class and a personalized request class?How do we separate commonalities?How to achieve personality?

This requires deeper generic extraction using the template method pattern.

Now that you use the template method pattern, you can no longer be a static tool class, but a vertically extended class inheritance system.

The final encapsulated template class is as follows (explaining a problem: this class is not designed as an abstract class because ordinary WebService s can instantiate objects directly through this class and invoke service interfaces instead of creating an anonymous internal class each time, even without special requirements):

public class WebServiceTemplateUtil {
    public void invokeWebService(WebServiceSoapBean bean) throws Exception {
        checkBeanIsComplete(bean);
        
        String url = bean.getUrl();
        StringBuilder sb = new StringBuilder();
        sb.append("<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">");
        //Template method entry point: Requirements to join the head er section allow subclasses to override this method
        doAddSoapHead(sb, bean);
        sb.append("  <soap:Body>");
        //Incoming method and namespace
        sb.append("    <" + bean.getMethod() + " xmlns=\"" + bean.getNamespace() + "\">");
        //Dynamic construction parameters and values
        Map<String, String> argsMap = bean.getArgsMap();
        for (Map.Entry<String, String> entry : argsMap.entrySet()) {
            sb.append("      <" + entry.getKey() + ">" + entry.getValue() +"</" + entry.getKey() + ">");
        }
        sb.append("    </" + bean.getMethod() + ">");
        sb.append("  </soap:Body>");
        sb.append("</soap:Envelope>");
        PostMethod postMethod = new PostMethod(url);
        byte[] bytes = sb.toString().getBytes("utf-8");
        InputStream inputStream = new ByteArrayInputStream(bytes, 0, bytes.length);
        RequestEntity requestEntity = new InputStreamRequestEntity(inputStream, bytes.length, "text/xml;charset=UTF-8");
        postMethod.setRequestEntity(requestEntity);
        
        HttpClient httpClient = new HttpClient();
        httpClient.executeMethod(postMethod);
        String soapResponseData = postMethod.getResponseBodyAsString();
        
        //Extract data from the specified tag in the response body
        List<JSONObject> jsons = parseResponseXmlToJson(soapResponseData, bean.getReturnTagList());
        for (JSONObject json : jsons) {
            System.out.println(json.toJSONString());
        }
    }
    
    //This method can be overridden if there is a need to join the head section
    protected void doAddSoapHead(StringBuilder sb, WebServiceSoapBean bean) {
    }
    //This method can be overridden if there is a need for additional data validation
    protected void doExtraRequestCheck(WebServiceSoapBean bean) {
    }
    
    private List<JSONObject> parseResponseXmlToJson(String xml, List<String> returnTagList) {
        //Verify before converting: If there is no label at the root, simply empty the pointer
        if (returnTagList.isEmpty()) {
            throw new NullPointerException("Sorry, the Response Body Label Collection is empty!");
        }
        for (String tag : returnTagList) {
            //You can't just check tags, but also add angle brackets to prevent tags from crashing in response data
            if (!xml.contains("<" + tag + ">")) {
                throw new NullPointerException("Sorry, this tag is not in the response body:" + tag);
            }
        }
        
        Document document = Jsoup.parse(xml);
        //We've checked it before, so we can definitely take it out here
        Element parent = document.getElementsByTag(returnTagList.get(0)).get(0).parent();
        //Take peer, each peer is a json
        String tagName = parent.tagName();
        //Step back one level and look for this tagName to get all the data nodes
        Elements elements = parent.parent().getElementsByTag(tagName);
        List<JSONObject> list = new ArrayList<>(elements.size());
        for (Element element : elements) {
            JSONObject json = new JSONObject();
            for (String returnTag : returnTagList) {
                json.put(returnTag, element.getElementsByTag(returnTag).get(0).text());
            }
        }
        
        return list;
    }
    
    private void checkBeanIsComplete(WebServiceSoapBean bean) {
        if (StringUtils.isBlank(bean.getUrl())) {
            throw new NullPointerException("I'm sorry, url Empty!");
        }
        if (StringUtils.isBlank(bean.getMethod())) {
            throw new NullPointerException("I'm sorry, method Empty!");
        }
        if (StringUtils.isBlank(bean.getNamespace())) {
            throw new NullPointerException("I'm sorry, namespace Empty!");
        }
        if (Objects.isNull(bean.getArgsMap())) {
            throw new NullPointerException("Sorry, the parameter list is null!");
        }
        if (Objects.isNull(bean.getReturnTagList())) {
            throw new NullPointerException("Sorry, the response body label list is null!");
        }
        
        //Template Method Mode Start Point: Requirements for additional data checks allow subclasses to override this method
        doExtraRequestCheck(bean);
    }
}

If additional methods are needed for subclasses, you can override the template methods to achieve this goal.

This meets the Open-Close Principle - "Open to Extension, Close to Modification".

(finished)

Topics: xml Spring Apache JSON