基于Java的WebService实践

背景

在很多Java项目中,仍然会使用基于WebService的数据传输接口,而webservice实际上就是http+xml。而在实践过程中,主要需要注意接收和发送的格式。

实践

被调用接口

如果处于被调用方,则需要声明出一个WebService服务,也就是生成、发布一个WSDL文件。

什么是WSDL?

WSDL – WebService Description Language – Web服务描述语言。

  • 通过XML形式说明服务在什么地方-地址。
  • 通过XML形式说明服务提供什么样的方法 – 如何调用。

将接口声明为一个ws服务

在Java中可以依靠javax.jws包中的相关注解来实现一个WebService接口

1
2
3
4
5
6
7
8
9
@WebService
@SOAPBinding(style = SOAPBinding.Style.RPC)
public interface testWebservice {

@WebMethod
public ResponseRoot test(@WebParam(name = "item") Item item,
@WebParam(name = "MessageHeader") MessageHeaderRoot header) ;

}
  1. 使用 @WebService 注解标注该接口为一个WebService接口,可以修改服务名、端口名、命名空间等信息,如
    @WebService(targetNamespace = "http://ujn.cn/",serviceName = "UserService", portName = "UserPort")
  2. 使用 @SOAPBinding 注解标注绑定的形式,根据绑定的不同的形式,客户端使用不同的调用约定进行调用。
    1. Document Wrapped(默认使用方式)
    2. Document Bare
    3. RPC
  3. 使用 @WebMethod 方法标注为一个WebService中的一个方法服务
  4. 最后实现接口中的方法即可,如果需要返回信息,直接封装为一个对象即可,如上面代码中的ResponseRoot对象。最后会解析出一个对象。

添加XML对象

在上面的代码中可以看到不管是接受还是返回,都是面向对象的,然而最后都需要形成为一个XML文档,那么,是如何将这些对象解析为有一整个XML对象的呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@XmlRootElement(name = "MessageHeader")
public class MessageHeaderRoot implements Serializable {

private static final long serialVersionUID = 1848365930921924635L;

@XmlElement(name = "GUID")
private String guid;
@XmlElement(name = "SEND_DATE")
private String sendDate;
@XmlTransient
public String getGuid() {
return guid;
}

public void setGuid(String guid) {
this.guid = guid;
}

@XmlTransient
public String getSendDate() {
return sendDate;
}

public void setSendDate(String sendDate) {
this.sendDate = sendDate;
}
}
  1. 使用 @XmlRootElement 注解标注在对象上,说明此对象是一个可解析为XML节点的对象。
  2. 复杂对象必须继承 Serializable 接口,进行序列化。
  3. 使用 @XmlElement(name = "GUID") 注解,声明出其中的子节点和子节点的名称。
  4. 在get方法上添加 @XmlTransient 注解,否则会报对象节点重复的错误。
  5. 对对象通过以上的配置,可以完整的XML类型的数据映射到对象上,从而实现进一步的处理。

形成可接受的XML格式

1
2
3
4
5
6
7
8
9
10
11
12
13
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:web="http://webService.test.com/">
<soapenv:Header/>
<soapenv:Body>
<web:addPersonAndUnit>
<item>
<!--内部对象体:-->
</item>
<MessageHeader>
<!--内部对象体:-->
</MessageHeader>
</web:addPersonAndUnit>
</soapenv:Body>
</soapenv:Envelope>

可以使用SOAP-UI 这个工具进行生成,使用免费版,添加SOAP服务,后点击方法的request则会自动生成格式。

接口调用

在调用其他服务的接口时,对方法等没有特殊的要求,只需要遵守对方特定的接口格式即可。在接口调用时,同样有多种调用的方式,在此处我使用xml格式拼接,http服务直接调用的方式。

XML格式拼接

1
2
3
4
5
6
7
ResQuestionBean resQuestionBean = new ResQuestionBean();
resQuestionBean.setQuestionCode(questionCode);
resQuestionBean.setQuestionStatus(statusName);
resQuestionBean.setUploader(personCode);
resQuestionBean.setUploaderTime(DateUtil.format(new Date()));
StringBuilder resultXml = new StringBuilder();
resultXml.append(XmlUtils.objectToXML(resQuestionBean));
  1. 声明出一个返回对象 resQuestionBean ,对对象中的数据进行装载。
  2. 使用objectToXML方法将对象转换为XML字符串。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static String objectToXML(Object object) throws JAXBException {
JAXBContext context = JAXBContext.newInstance(object.getClass());
Marshaller m = context.createMarshaller();
// 设置格式化输出
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
m.setProperty(Marshaller.JAXB_FRAGMENT, true);
// 不进行转义字符的处理
m.setProperty(CharacterEscapeHandler.class.getName(), new CharacterEscapeHandler() {
@Override
public void escape(char[] ch, int start,int length, boolean isAttVal, Writer writer) throws IOException {
writer.write(ch, start, length);
}
});
Writer w = new StringWriter();
m.marshal(object, w);
return w.toString();
}

通过HTTP调用方法,推送数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private synchronized String doSend(String address, String invokeMethod, 
String guid, String recvSys,
String invokeDate, String dataXml) throws IOException {
//enum 选择系统,地址url http post方式
StringBuffer strBuf = null;
URL url = new URL(address);
HttpURLConnection huc = (HttpURLConnection) url.openConnection();
huc.setRequestMethod("POST");
//设置数据格式
huc.setRequestProperty("content-type", "text/xml;charset=utf-8");
huc.setConnectTimeout(10000);
huc.setUseCaches(false);
huc.setReadTimeout(10000);
huc.setDoInput(true);
huc.setDoOutput(true);
huc.setInstanceFollowRedirects(false);
DataOutputStream dos = new DataOutputStream(huc.getOutputStream());
dos.write(buildSendXml(invokeMethod, guid, recvSys, invokeDate, dataXml).getBytes("utf-8"));

dos.flush();
BufferedReader reader = new BufferedReader(new InputStreamReader(huc.getInputStream(), "utf-8"));
String line = null;
strBuf = new StringBuffer();
while ((line = reader.readLine()) != null) {
strBuf.append(line);
}
dos.close();
reader.close();
return strBuf.toString();
}
  1. 设置地址、调用方式等http的基本信息后,使用write方法将数据写入到请求中。
  2. 使用reader读取返回数据,并返回、解析,形成最终数据后返回给前台做最终的处理

最后XML整体拼接

在 buildSendXml 方法中,将第一步格式化为XML的Object进行拼接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private String buildSendXml(String invokeMethod, String guid, 
String invokeDate,
String data, String itemTag, String methodTag) {
StringBuffer sb = new StringBuffer();
sb.append("<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:urn=\"urn:sap-com:document:sap:rfc:functions\">" +
"<soapenv:Header/>" +
"<soapenv:Body>" +
"<" + methodTag + ":" + invokeMethod + ">" +
" <MessageHeader>" +
" <GUID>" + guid + "</GUID>" +
" <SEND_DATE>" + invokeDate + "</SEND_DATE>" +
" </MessageHeader>" +
" <" + itemTag + ">");
sb.append(data);
sb.append(" <" + itemTag + ">" +
"</urn:" + invokeMethod + "> " +
" </soapenv:Body>" +
"</soapenv:Envelope>");
return sb.toString();
}
  1. 接口无法联通后的再次重试问题
  2. 此篇文章写于WebService接口联合调试前,可能存在问题与风险未被发现。如果有请与我联系并进行规避。
  3. 如果有更好、更便捷的接口编写方案,欢迎一起讨论。
-------------本文结束感谢您的阅读-------------

本文标题:基于Java的WebService实践

文章作者:NanYin

发布时间:2020年08月01日 - 15:08

最后更新:2020年08月02日 - 10:08

原始链接:https://nanyiniu.github.io/2020/08/01/%E5%9F%BA%E4%BA%8EWebService%E7%9A%84Java%E5%BC%80%E5%8F%91%E5%AE%9E%E8%B7%B5/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。