WebService最优方案选择

需求

最近,接触到了一个java对接C#的项目,使用WebService技术开发。项目已经快告一段落了,经过这几个月接触和使用。我有了一个清晰的认识,之前也调研了互联网上大部分实现的通讯,他们的优缺点,我都有一定的了解,我认为的最好解决方案是 wsimport生成的代码,让我们看看这几种方式的优缺点吧。

WebService是什么

WebService是一种跨编程语言和跨操作系统平台的远程调用技术。

WebService使用场景

WebService可用于调用第三方系统API。

WebService创建方式

引入依赖

    <dependencies>
        <dependency>
            <groupId>org.apache.cxfgroupId>
            <artifactId>cxf-spring-boot-starter-jaxwsartifactId>
            <version>3.5.3version>






        dependency>
        <dependency>
            <groupId>org.apache.cxfgroupId>
            <artifactId>cxf-rt-transports-httpartifactId>
            <version>3.5.3version>
        dependency>

        <dependency>
            <groupId>org.apache.cxfgroupId>
            <artifactId>cxf-rt-transports-http-jettyartifactId>
            <version>3.5.3version>
        dependency>


    dependencies>

创建bean

package com.hikktn;
 
import javax.jws.WebMethod;
import javax.jws.WebService;
 
@WebService
public interface IUserService {
 
    @WebMethod
    //返回一个字符串
    String getStr(String str);
}

实现接口

@WebService
public class UserServiceImpl implements IUserService{
 
    @WebMethod
    @Override
    public String getStr(String str) {
        String str2 = null;
        try {
            str2 = new String("处理后的字符串:".getBytes(),"UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
        return str2 + str;
    }
}

第一种服务端

import org.apache.cxf.interceptor.Interceptor;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.jaxws.EndpointImpl;
import org.apache.cxf.message.Message;

import javax.xml.ws.Endpoint;
import java.util.List;

public class TestServer {

    private static String PATH = "http://127.0.0.1:7777/WebService_Server/jdk";

    //浏览器输入以上地址访问不到,但是http://127.0.0.1:7777/WebService_Server/jdk?wsdl这个地址可以查看到xml文件
    //相对路径随便写
    public static void main(String[] args) {
        //获取一个EndPoint实例
        EndpointImpl endpoint = (EndpointImpl) Endpoint.publish(PATH, new UserServiceImpl());
        //服务器端获取输入拦截器
        List<Interceptor<? extends Message>> inInterceptors = endpoint.getInInterceptors();
        //添加接受信息日志拦截器
        inInterceptors.add(new LoggingInInterceptor());
        System.out.println("成功发布!");
    }
}

第二种服务端

import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.jaxws.JaxWsServerFactoryBean;

/**
 * @author killian
 * @since 2022-11-04 14:54
 */
public class TestServer2 {

    public static void main(String[] args) {

        //以下是编译wsdl文件的另一种方法,这种方法使用CXF的JaxWsServerFactoryBean类中的方法创建
        System.out.println("启动服务器...");
        //创建地址
        String address = "http://127.0.0.1:7777/WebService_Server/cxf";
        //创建一个服务器工厂实例
        JaxWsServerFactoryBean jaxWsServerFactoryBean = new JaxWsServerFactoryBean();
        //设置暴露地址
        jaxWsServerFactoryBean.setAddress(address);
        //设置接口类
        jaxWsServerFactoryBean.setServiceClass(IUserService.class);
        //设置实现类
        jaxWsServerFactoryBean.setServiceBean(uSerService);
        //添加日志拦截器
        jaxWsServerFactoryBean.getInInterceptors().add(new LoggingInInterceptor());
        //创建服务器
        jaxWsServerFactoryBean.create();
        System.out.println("服务器成功启动!");
    }

}

启动服务后,下载Soap软件测试
WebService最优方案选择_第1张图片

放入url
http://127.0.0.1:7777/WebService_Server/jdk?wsdl
就可以测试
WebService最优方案选择_第2张图片

客户端调用(不建议)

public class TestXml {

    public static void main(String[] args) {
        try {
            StringWriter writer = new StringWriter();
            JAXBContext jc = JAXBContext.newInstance(Student.class);
            Marshaller ms = jc.createMarshaller();
            Student st = new Student("zhang", "w", "h", 11);
            ms.marshal(st, writer);
            String xmlStr = writer.toString();
            System.out.println(xmlStr);
        } catch (JAXBException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

这种在网络上都是入门级别,玩玩demo可以。但是你要拿到项目中,就恼火了。
首先,这样的调用方式是完全不管命名空间的调用,而国外的超多API都是有很多嵌套的命名空间xml。
可以说这个是非常致命的缺点,无法生成有效的XML。

hutool调用WebService(简单场景)

具体大家看这篇博客,它有非常详细的教程。
Java使用Hutool调用WebService接口详解
优点:不用写HttpURLConnection通讯,简单的接口可以使用这个来开发。
缺点:代码繁多,没有更加优化的工具类,要去处理返回的XML,处理方式只有转换为Map,非常不利于后续维护和代码编写。

HttpURLConnection原生请求WebService(简单场景)

public class SoapSendUtil {

    /**
     * 默认连接超时
     */
    private static final int connectionTimeout = 60 * 1000;
    /**
     * 默认读取超时
     */
    private static final int readTimeout = 60 * 1000;

    /**
     * 发送请求
     *
     * @param SapPiUrl soap协议url
     * @param xmlStr   soap协议的xml
     * @param action   请求接口url
     * @return 响应报文
     * @throws IOException IO异常
     */
    public static String sendRequest(String SapPiUrl, String xmlStr, String action) throws IOException {
        //创建一个支持HTTP特定功能的URLConnection
        HttpURLConnection httpConn = null;
        //创建一个输出流对象
        OutputStream out = null;
        String returnXml = "";
        //打开链接
        httpConn = (HttpURLConnection) new URL(SapPiUrl).openConnection();
        //设置内容和字符格式
        httpConn.setRequestProperty("Content-Type", "text/xml; charset=utf-8");
        //设置请求和xml
        httpConn.setRequestProperty("SOAPAction", action);
        //设置请求为post亲求
        httpConn.setRequestMethod("POST");
        httpConn.setDoOutput(true);
        httpConn.setDoInput(true);
        httpConn.setConnectTimeout(connectionTimeout);
        httpConn.setReadTimeout(readTimeout);

        httpConn.connect();
        out = httpConn.getOutputStream(); // 获取输出流对象

        httpConn.getOutputStream().write(xmlStr.getBytes()); // 将要提交服务器的SOAP请求字符流写入输出流
        out.flush();
        out.close();
        int code = httpConn.getResponseCode(); // 用来获取服务器响应状态
        System.out.println(code);
        String tempString = null;
        StringBuffer sb1 = new StringBuffer();
        //如果响应状态为ok,就读取http内容
        if (code == HttpURLConnection.HTTP_OK) {
            BufferedReader reader = new BufferedReader(new InputStreamReader(httpConn.getInputStream(), StandardCharsets.UTF_8));
            while ((tempString = reader.readLine()) != null) {
                sb1.append(tempString);
            }
            if (null != reader) {
                reader.close();
            }
        } else {
            BufferedReader reader = new BufferedReader(new InputStreamReader(httpConn.getErrorStream(), StandardCharsets.UTF_8));
            // 一次读入一行,直到读入null为文件结束
            while ((tempString = reader.readLine()) != null) {
                sb1.append(tempString);
            }
            if (null != reader) {
                reader.close();
            }
        }
        // 响应报文
        returnXml = sb1.toString();
        return returnXml;
    }
}
import org.springframework.util.StringUtils;

import javax.xml.bind.*;
import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.namespace.QName;
import java.io.File;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.Collection;

/**
 * 使用Jaxb2.0实现XMLJava Object的Binder.
 * 

* 特别支持Root对象是List的情形. */ public class JaxbBinderUtil { /** * 多线程安全的Context. */ private JAXBContext jaxbContext; /** * @param types 所有需要序列化的Root对象的类型. */ public JaxbBinderUtil(Class... types) { try { jaxbContext = JAXBContext.newInstance(types); } catch (JAXBException e) { throw new RuntimeException(e); } } /** * Java Object->Xml. */ public String toXml(Object root, String encoding) { try { StringWriter writer = new StringWriter(); createMarshaller(encoding).marshal(root, writer); return writer.toString(); } catch (JAXBException e) { throw new RuntimeException(e); } } public String toXml(Object root) { try { StringWriter writer = new StringWriter(); createMarshaller(StandardCharsets.UTF_8.toString()).marshal(root, writer); return writer.toString(); } catch (JAXBException e) { throw new RuntimeException(e); } } /** * Java Object->Xml, 特别支持对Root Element是Collection的情形. */ public String toXml(Collection root, String rootName, String encoding) { try { CollectionWrapper wrapper = new CollectionWrapper(); wrapper.collection = root; JAXBElement wrapperElement = new JAXBElement(new QName(rootName), CollectionWrapper.class, wrapper); StringWriter writer = new StringWriter(); createMarshaller(encoding).marshal(wrapperElement, writer); return writer.toString(); } catch (JAXBException e) { throw new RuntimeException(e); } } /** * Xml->Java Object. */ public Object fromXml(String xml) { try { StringReader reader = new StringReader(xml); return createUnmarshaller().unmarshal(reader); } catch (JAXBException e) { throw new RuntimeException(e); } } /** * 创建Marshaller, 设定encoding(可为Null). */ public Marshaller createMarshaller(String encoding) { try { Marshaller marshaller = jaxbContext.createMarshaller(); // 去除XML头 marshaller.setProperty("com.sun.xml.bind.xmlDeclaration", Boolean.FALSE); // 指定是否使用换行和缩排对已编组 XML 数据进行格式化的属性名称 marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); if (StringUtils.isEmpty(encoding)) { marshaller.setProperty(Marshaller.JAXB_ENCODING, encoding); } return marshaller; } catch (JAXBException e) { throw new RuntimeException(e); } } /** * 创建UnMarshaller. */ public Unmarshaller createUnmarshaller() { try { return jaxbContext.createUnmarshaller(); } catch (JAXBException e) { throw new RuntimeException(e); } } /** * 封装Root Element 是 Collection的情况. */ public static class CollectionWrapper { @XmlAnyElement protected Collection collection; } public Object fromXML(String fileName) { return fromXML(new File(fileName)); } public Object fromXML(File file) { try { Unmarshaller unmarshaller = createUnmarshaller(); return unmarshaller.unmarshal(file); } catch (JAXBException e) { throw new RuntimeException(e); } } public Object fromXML(InputStream stream) { try { Unmarshaller unmarshaller = createUnmarshaller(); return unmarshaller.unmarshal(stream); } catch (JAXBException e) { throw new RuntimeException(e); } } }

这两个工具类也是由大佬提供,他们需要结合起来编写。
具体使用
对象转 soap_使用jaxb 实现对象与xml之间的转换
优点:解决了上面的返回对象只能转Map的问题
缺点:需要为每个接口的入参和结果集,封装一层SoapBody和SoapHeader,并且要在package-info.java编写命名空间。java版本低一点就没有package-info.java了。

什么事命名空间?

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://server.hikktn.com/">
   <soapenv:Header/>
   <soapenv:Body>
      <ser:getArticle>
         
         <arg0>
            
            <guid>123guid>
            
            <name>truename>
         arg0>
      ser:getArticle>
   soapenv:Body>
soapenv:Envelope>

xmlns后面跟着就是命令空间,它会为下面的xml标签加上一个小前缀。
package-info.java

@XmlSchema(xmlns = {
        @XmlNs(namespaceURI = "http://server.hikktn.com/", prefix = "ser"),
        @XmlNs(namespaceURI = "http://schemas.xmlsoap.org/soap/envelope/", prefix = "soapenv")
})
package com.eci.tbms.memoq.domain.vo;

import javax.xml.bind.annotation.XmlNs;
import javax.xml.bind.annotation.XmlSchema;

cxf方式

首先,我们需要去下载Apache CXF
CXF官方下载地址
然后解压,cmd打开对应路径。
该方案适用于json格式发送请求。
可以生成去除该字段类型的 客户端代码 ; 比如 JAXBElement 可以变成 String 类型
XML
复制代码

<jaxb:bindings version="2.0"    
               xmlns:jaxb="http://java.sun.com/xml/ns/jaxb">    
    <jaxb:bindings>    
        <jaxb:globalBindings generateElementProperty="false"/>    
    jaxb:bindings>    
jaxb:bindings>  

第二步 : 生成客户端代码
LaTeX
复制代码

wsimport -encoding utf-8 -b remove.xml -Xnocompile http://xxxxxxxx?WSDL

-encoding utf-8 表示 编码
-Xnocompile 表示生成java代码, 不加的话, 将会 只生成class 文件
-b 表示绑定指定文件
此时生成的客户端代码中 , 原先的JAXBElement 字段类型 , 就会变成 String 类型;
controller
在controller层使用,会因为各种webservice生成的代码问题,造成一部分问题。
优点:完美的解决了各种xml转换问题。用JSON格式传输。
缺点:就是API生成了所有的API,代码厚重。并不能直接调用,还是需要编写controller。

题外话

关于WebService的超时问题

请参照这篇博客:Future 接口调用超时时间

你可能感兴趣的:(JAVA,apache,java,开发语言)