需求
最近,接触到了一个java对接C#的项目,使用WebService技术开发。项目已经快告一段落了,经过这几个月接触和使用。我有了一个清晰的认识,之前也调研了互联网上大部分实现的通讯,他们的优缺点,我都有一定的了解,我认为的最好解决方案是 wsimport生成的代码,让我们看看这几种方式的优缺点吧。
WebService是什么
WebService是一种跨编程语言和跨操作系统平台的远程调用技术。
WebService使用场景
WebService可用于调用第三方系统API。
WebService创建方式
引入依赖
<dependencies>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-spring-boot-starter-jaxws</artifactId>
<version>3.5.3</version>
<!-- <exclusions>-->
<!-- <exclusion>-->
<!-- <groupId>org.apache.cxf</groupId>-->
<!-- <artifactId>cxf-rt-frontend-jaxws</artifactId>-->
<!-- </exclusion>-->
<!-- </exclusions>-->
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>3.5.3</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http-jetty</artifactId>
<version>3.5.3</version>
</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软件测试
放入url
http://127.0.0.1:7777/WebService_Server/jdk?wsdl
就可以测试
客户端调用(不建议)
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.
* <p>
* 特别支持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>
<!--Optional:-->
<arg0>
<!--Optional:-->
<guid>123</guid>
<!--Optional:-->
<name>true</name>
</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 接口调用超时时间