WebService的调用本质:
(1)客户端把需要调用的参数,转换为XML文档片段(SOAP消息)。
(2)客户端通过网络把XML文档片段传给远程服务器。
(3)服务器接受XML文档片段。
(4)服务器解析XML文档片段,提取其中的数据,并把数据转换为调用所需的参数。
(5)服务器执行方法。
(6)得到方法返回值,服务器把方法返回值,转换为XML文档片段(SOAP消息)。
(7)服务器通过网络把XML文档片段传给远程客户端。
(8)客户端接收XML文档片段。
(9)客户端解析XML文档片段,提取其中的数据,并把数据转换为调用返回值。
WebService的三个技术基础:
- WSDL
Web Service接口
1.types(标准的Schema)
2.2N的message
3.portType - N个operation
Web Service实现
1.binding元素 - N个更详细的operation
2.service - 指定Web Service的服务器地址。
- SOAP
Header
Header是可选的。由程序员控制添加。
Body
Body元素总是默认的。Body元素里可有两种情况,
- 当WebService交互正确时,Body元素里的内容由WSDL控制。
- 当WebService交互出错时,Body元素的内容将是Fault子元素。
Web Service急需解决的问题,如何进行权限控制?
解决思路是:服务器端要求input消息总是携带有用户名、密码消息,
--如果没有用户名、密码信息,直接拒绝调用。
如果不用CXF等框架,SOAP消息的生成、解析都是由程序员负责的,
无论是添加用户名、密码信息,还是提取用户名、密码信息,都可有程序员的代码完成。
如果用CXF等框架,SOAP消息的生成、解析都是CXF等框架负责来完成度的。
==================
拦截器
为了让程序员能访问、并修改CXF框架所生成的SOAP消息,CXF提供了拦截器。
服务器端添加拦截器
(1)获取Endpoint的publish方法返回值。
(2)调用该方法的返回值的getInInterceptor、getOutInterceptor方法来
获取In、out拦截器列表,接下来就可以添加拦截器了。
客户端添加拦截器:
(1)调用ClientProxy的getClient方法,调用该方法以远程WebService的代理为参数。
(2)调用Client对象的getInInterceptor、getOutInterceptor方法来
获取In、Out拦截器列表,接下来就可以添加拦截器了。
对于sayHi操作:
传入消息
<?xml version="1.0" ?>
<S:Envelopexmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:sayHixmlns:ns2="http://ws.cxf.fkjava.org/">
<text>孙悟空</text>
</ns2:sayHi>
</S:Body>
</S:Envelope>
传出消息
<soap:Envelopexmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:sayHiResponse xmlns:ns2="http://ws.cxf.fkjava.org/">
<return>孙悟空,您好现在时间是:Thu Jan03 17:58:39 CST 2013</return>
</ns2:sayHiResponse>
</soap:Body>
</soap:Envelope>
对于getAllCats操作:
传入消息
<?xml version="1.0" ?>
<S:Envelopexmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:getAllCats xmlns:ns2="http://ws.cxf.fkjava.org/"/>
</S:Body>
</S:Envelope>
传出消息
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:getAllCatsResponse xmlns:ns2="http://ws.cxf.fkjava.org/">
<return>
<entries>
<key>第4个</key>
<value>
<color>黑白色</color>
<id>4</id>
<name>熊猫</name>
</value>
</entries>
<entries>
<key>第3个</key>
<value>
<color>白色</color>
<id>3</id>
<name>kitty</name>
</value>
</entries>
<entries>
<key>第1个</key>
<value>
<color>橙色</color>
<id>1</id>
<name>garfield</name>
</value>
</entries>
<entries>
<key>第2个</key>
<value>
<color>蓝色</color>
<id>2</id>
<name>机器猫</name>
</value>
</entries>
</return>
</ns2:getAllCatsResponse>
</soap:Body>
</soap:Envelope>
-------------------------------------------
SOAP消息--个人小补充:
根元素是Envelope
Header
默认情况,Header元素不是强制出现的。
Header元素有程序员控制添加,主要用于
携带一些额外的信息,比如用户名、密码
信息
Body
1.如果调用正确。Body元素的内容应该
遵守WSDL所要求的格式。
2.如果调用错误。Body元素的内容就是
Fault子元素。
Soap消息格式:
sop
-------------------------------------------
自定义拦截器:
需要实现Interceptor接口。实际上,我们一般会继承AbstractPhaseInterceptor.
给添加权限拦截器
客户端
Client.java
public class Client
{
publicstatic void main(String[] args)
{
UserServiceWsfactory = new UserServiceWs();
UserServiceus = factory.getUserServiceImplPort();
//为客户端添加拦截器
org.apache.cxf.endpoint.Clientclient = ClientProxy.getClient(us);
client.getOutInterceptors().add(newAddHeaderInterceptor("crazyit.org"
,"leegang"));
List<User>users = us.getUsers();
System.out.println(users);
StringMapmap = (StringMap)us.proUsers(users);
List<Entry>entries = map.getEntries();
for(int i = 0 ; i < entries.size() ; i++ )
{
System.out.println(entries.get(i).getKey()
+"--->" + entries.get(i).getValue());
}
}
}
客户端拦截器
AddHeaderInterceptor.java
public class AddHeaderInterceptor
extendsAbstractPhaseInterceptor<SoapMessage>
{
privateString name;
privateString pass;
publicAddHeaderInterceptor(String name, String pass)
{
//子类总会调用父类的构造器,
//使用super显式调用父类指定的构造器。
//指定该拦截器在哪个阶段被激发
super(Phase.PREPARE_SEND);
this.name= name;
this.pass= pass;
}
//处理消息
publicvoid handleMessage(SoapMessage message)
{
//获取SOAP消息的全部头(即soap:Header元素全部子元素)
List<Header>headers = message.getHeaders();
//创建Document对象。
Documentdoc = DOMUtils.createDocument();
//创建了两个元素
ElementuserId = doc.createElement("userId");
ElementuserPass = doc.createElement("userPass");
userId.setTextContent(name);
userPass.setTextContent(pass);
//创建AuthHeader元素
Elementauth = doc.createElement("AuthHeader");
auth.appendChild(userId);
auth.appendChild(userPass);
QNameqname = new QName("crazyit");
SoapHeaderauthHeader = new SoapHeader(qname , auth);
//添加了Header
headers.add(authHeader);
/**
也就是向<soap:Header.../>元素中添加了如下元素:
<AuthHeader>
<userId>crazyit</userId>
<userPass>crazyit.org</userPass>
</AuthHeader>
*/
}
}
服务器端
Servlet.java
public class Server
{
publicstatic void main(String[] args)
{
/*
用Java EE规范提供的方式来暴露服务
*/
UserServiceus = new UserServiceImpl();
Stringaddress = "http://localhost:9999/crazyit";
EndpointImplep = (EndpointImpl)Endpoint.publish(address , us);
//下面方法就可以添加拦截器(CXF提供的拦截器)
ep.getOutInterceptors()
.add(newLoggingOutInterceptor());
ep.getInInterceptors()
.add(newLoggingInInterceptor());
//添加授权拦截器
ep.getInInterceptors()
.add(newAuthIntercetpr());
}
}
服务端拦截器
AuthIntercetpr.java
public class AuthIntercetpr
extendsAbstractPhaseInterceptor<SoapMessage>
{
publicAuthIntercetpr()
{
//子类总会调用父类的构造器,
//使用super显式调用父类指定的构造器。
//指定该拦截器在哪个阶段被激发
super(Phase.PRE_INVOKE);
}
//重写父类的方法,用于处理消息
publicvoid handleMessage(SoapMessage message)
{
//获取SOAP消息的全部头(即soap:Header元素全部子元素)
List<Header>headers = message.getHeaders();
//如果soap:Header元素不包含子元素
if(headers== null || headers.size() < 1)
{
thrownew Fault(new SOAPException("SOAP消息头格式不对哦!"));
}
for(Headerheader : headers)
{
SoapHeadersoapHeader = (SoapHeader)header;
//取出SOAP头对应的Element对象
Elementelement = (Element)soapHeader.getObject();
//只处理AuthHeader元素(其他元素可能是与授权无关的SOAP头)
if(element.getTagName().equals("AuthHeader"))
{
if(checkSOAPHeader(element))
{
return;
}
}
}
//遍历了所有SOAP头都找不到AuthHeader元素
thrownew Fault(new SOAPException("SOAP消息头格式不对哦!"));
}
//此处就是用JAXP的XML解析
publicboolean checkSOAPHeader(Element element)
{
NodeListuserIdList = element.getElementsByTagName("userId");
NodeListuserPassList = element.getElementsByTagName("userPass");
if(userIdList == null
||userPassList == null
||userIdList.getLength() != 1
||userPassList.getLength() != 1 )
{
thrownew Fault(new SOAPException("SOAP消息头格式不对哦!"));
}
else
{
StringuserId = userIdList.item(0).getTextContent();
StringuserPass = userPassList.item(0).getTextContent();
//正常情况下,此处应该查询数据库
//,判断该用户名、密码是否被授权调用该服务。
if(userId.equals("crazyit.org")
&&userPass.equals("leegang"))
{
returntrue;
}
else
{
thrownew Fault(new SOAPException("您没有被授权调用该服务!"));
}
}
}
}
CXF需要的jar包(如果是javaWeb应用的话,不需要前7个):
CXF与Spring的整合
可以在传统的Java EE应用的基础上添加一层Web Service层。
我们的Java EE应用就可以对外暴露成Web Service ,这样就可以允许任何平台、任何语言编写的程序来调用这个J2EE应用。
在传统SSH项目基础上增加WS的步骤:
1.复制CXF的jar包。
2.在web.xml配置CXF核心控制器 :org.apache.cxf.transport.servlet.CXFServlet
3.在spring配置文件中导入CXF提供的Schema ,XML配置。在spring配置文件中导入
cxf.xml,
cxf-extension-soap.xml,
cxf-servlet.xml
4.在Spring配置文件中使用jaxws:endpoint元素来暴露WS。
5.如果要添加拦截器。在jaxws:endpoint元素里添加
inInterceptors,outInterceptors子元素。
CXF与Spring另一种整合:
1. 让Action依赖远程的Web Service的接口。
2. 复制CXF的JAR包。(最核心的6个)
3. 在Spring配置文件中导入CXF提供的Schema,XML配置文件。
4. 在Spring配置文件中使用jaxws:client元素来配置远程Web Service代理。
5. 如果要添加拦截器。在jaxws:client元素里添加inInterceptors,outInterceptors子元素。
WebService接口和实现类示例:
接口
FirstWs.java
@WebService
public interface FirstWs
{
//定义几个方法,每个方法将被暴露成一个Web Service操作
StringsayHi(String text);
StringsayHiToUser(User user);
List<User>getUserList();
}
实现类
FirstWsImpl.java
@WebService(endpointInterface ="org.crazyit.cxfapp.service.FirstWs"
,serviceName= "FirstWsService")
public class FirstWsImpl implements FirstWs
{
//以一个HashMap来模拟数据库(只要该WebService不关闭,该Map里的数据一直存在)
Map<Integer,User> users = new HashMap<Integer, User>();
//实现Web Service的3个操作(方法)
publicString sayHi(String name)
{
System.out.println("调用sayHi方法");
return"Hello," + name;
}
publicString sayHiToUser(User user)
{
System.out.println("调用sayHiToUser方法");
users.put(user.getId(),user);
returnuser.getName() + ",欢迎使用CXF";
}
publicList<User> getUserList()
{
System.out.println("调用getUserList方法" +users);
List<User>result = new ArrayList<User>();
for(Object obj : users.values() )
{
result.add((User)obj);
}
returnresult;
}
}