之前项目需要与客户方进行接口交接,选择了webservice接口。这里记录下如何创建服务端,客户端。
服务端
关于发布webservice,其实主要使用了javax.xml.ws.Endpoint类提供的静态方法publish进行发布,但形式可以使用多种方式,比如:
①通过一个类的main方法+publish方法
②BS项目中的使用Servlet3.0提供的@WebListener注解将实现了ServletContextListener接口的WebServicePublishListener类标注为一个Listener
③通过servlet的init()方法,且在注解中@webService设置loadOnStartup=0:@WebServlet(value="",loadOnStartup=0)意思就是启动服务器是首先启动。
在项目中我主要采用的是第三种方式,通过init()在初始化servlet的时候就发布webService接口。
定义一个主类,里面是对外公布的public接口方法:
package test;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.xml.ws.Endpoint;
@WebServlet(value="",loadOnStartup=0)
public class publishWS extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public publishWS() {
super();
// TODO Auto-generated constructor stub
}
public void init() throws ServletException {
//自定义服务器端接口地址,如果是本地的话,ip需要自己查,后面的路径是自己
//来定义,这里最好做成xml配置文件来取,部署的时候方便
String Address = "http://192.168.103.145:9527/Service/Contract/WebService";
//publish第二个参数是我们的实现类
Endpoint.publish(Address , new mainInterface());
System.out.println("webService接口发布成功!");
}
}
主要对外接口方法,需要注解@WebService
package test;
import javax.jws.WebService;
@WebService
public class mainInterface {
public String func1(String xx...) {
return
}
public String func2(String xx ...) {
return
}
public String func3(String xx...) {
return
}
}
package test;
import javax.jws.HandlerChain;
import javax.jws.WebService;
import org.json.JSONException;
@WebService
@HandlerChain(file="handler-chain.xml")
public interface mainInterface {
public String func1;
public String func2;
public String func3;
}
在创建一个类切实现这个接口 mainImplement.class
package test;
import javax.jws.HandlerChain;
import javax.jws.WebService;
import org.json.JSONException;
@WebService
@HandlerChain(file="handler-chain.xml")
public class mainImplatement implements mainInterface {
@Override
public String func1() {
// TODO Auto-generated method stub
}
@Override
public String func2() {
// TODO Auto-generated method stub
}
@Override
public String func3() {
// TODO Auto-generated method stub
}
}
test.validateAuthHeader
validateAuthHeader.class
package test;
import java.util.Iterator;
import java.util.Set;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPFault;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPMessage;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
public class validateAuthHeader implements SOAPHandler {
@Override
public boolean handleFault(SOAPMessageContext context) {
// TODO Auto-generated method stub
return false;
}
@Override
public void close(MessageContext context) {
// TODO Auto-generated method stub
}
@Override
public Set getHeaders() {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean handleMessage(SOAPMessageContext context) {
// TODO Auto-generated method stub
//判断消息是请求还是响应
Boolean output = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
Boolean result = false;
SOAPMessage message = context.getMessage();
if(!output) {
result = validateSuccess(message);
if(!result) {
validateFail(message);
}
}
//System.out.println(output ? "服务端响应:" : "服务端接收:");
try {
message.writeTo(System.out);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("\r\n");
return result;
}
//成功验证
private boolean validateSuccess(SOAPMessage message) {
boolean result = false;
try {
SOAPEnvelope envelop = message.getSOAPPart().getEnvelope();
SOAPHeader header = envelop.getHeader();
if(header != null){
Iterator iterator = header.getChildElements(new QName("http://www.tmp.com/auth", "auth"));
SOAPElement auth = null;
if(iterator.hasNext()){
//获取auth
auth = (SOAPElement)iterator.next();
//获取username
Iterator it = auth.getChildElements(new QName("http://www.tmp.com/auth", "username"));
SOAPElement username = null;
if(it.hasNext()){
username = (SOAPElement)it.next();
}
//获取password
it = auth.getChildElements(new QName("http://www.tmp.com/auth", "password"));
SOAPElement password = null;
if(it.hasNext()){
password = (SOAPElement)it.next();
}
//判断username和password是否符合要求
if(username != null && password != null && "admin".equals(username.getValue()) && "admin".equals(password.getValue())){
result = true;
}
}
}
} catch (SOAPException e) {
e.printStackTrace();
}
return result;
}
//失败验证
private void validateFail(SOAPMessage message) {
try {
SOAPEnvelope envelop = message.getSOAPPart().getEnvelope();
envelop.getHeader().detachNode();
envelop.addHeader();
envelop.getBody().detachNode();
SOAPBody body = envelop.addBody();
SOAPFault fault = body.getFault();
if (fault == null) {
fault = body.addFault();
}
fault.setFaultString("用户名密码验证失败 请重新输入");
message.saveChanges();
} catch (SOAPException e) {
e.printStackTrace();
}
}
}
添加上服务器端的安全验证后,那么客户端的soap请求头中必须包含如下:
admin
admin
在浏览器中输入url地址:
查看接口的报文格式:
这里我使用的soapUI这款软件,加载带wsdl的报文地址,可以看到接口的方法及它request请求的soap格式,可以用来测试接口:
wsimport -d '指定生成代码的路径' -keep '对方的webservice地址+?wsdl'
个别参数含义:
-d : 表示输出的目录,目录必须事先存在,否则导出失败
-keep : 表示生成客户端执行类的源代码
-p : 定义客户端生成类的包名称
-s : 指定客户端执行类的源文件存放目录
-b : 指定jaxws/jaxb绑定文件或额外的schemas
-verbose : verbose表示详细信息
-extension : 使用扩展来支持SOAP1.2
这里再记录下,当时对方的接口需要权限认证,就是要求用户名,密码,但是我生成的代码里面没有设置这类的方法,后面查询了很多资料,需要重写指定方法:
//这里的xxx 类是根据wsdl自动解析生成的
xxx xx= new xxx();
xx.setHandlerResolver(new HandlerResolver() {
@Override
public List getHandlerChain(PortInfo arg0) {
List handlerList = new ArrayList();
// 添加认证信息
handlerList.add(new setHeader());
return handlerList;
}
});
setHeader.class
package test;
import java.util.Set;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPHeaderElement;
import javax.xml.soap.SOAPMessage;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
public class setHeader implements SOAPHandler {
@Override
public void close(MessageContext arg0) {
// TODO Auto-generated method stub
}
@Override
public boolean handleFault(SOAPMessageContext arg0) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean handleMessage(SOAPMessageContext ctx) {
// TODO Auto-generated method stub
// 出站,即客户端发出请求前,添加表头信息
Boolean request_p = (Boolean) ctx.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (request_p) {
try {
SOAPMessage msg = ctx.getMessage();
SOAPEnvelope env = msg.getSOAPPart().getEnvelope();
SOAPHeader hdr = env.getHeader();
if (hdr == null)
hdr = env.addHeader();
// 添加认证信息头
QName name = new QName(
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
"Security", "wsse");
SOAPHeaderElement header = hdr.addHeaderElement(name);
SOAPElement UsernameToken = header.addChildElement("UsernameToken", "wsse");
SOAPElement userElement = UsernameToken.addChildElement("Username", "wsse");
userElement.addTextNode("你的用户名");
SOAPElement passElement = UsernameToken.addChildElement("Password", "wsse");
passElement.addTextNode("你的密码");
msg.saveChanges();
// 把SOAP消息输出到System.out,即控制台
//msg.writeTo(System.out);
return true;
} catch (Exception e) {
e.printStackTrace();
}
}
return false;
}
@Override
public Set getHeaders() {
// TODO Auto-generated method stub
return null;
}
}
其余的就是根据实际业务来进行编码。对于webservcie还有很多方法可以实现,这里我只是简单记录下自己在项目中使用到的几点。但是我感觉这种根据别人的wsdl地址来生成代码很受限制,如果对方更改了webservice地址或者接口 方法我都需要重新切生成一遍,可能是我不清楚或者还没找到合适的方式来适应这种,都是这种笨办法重新生成。
参考资料:
初识webservice
添加安全认证