客户端相比服务端,基本没啥配置。引入jar包就好。我这里为了看效果,是新建了maven工程来做客户端。调用另一个webservice服务端maven工程.
环境:jdk1.7+maven3.3.9+tomcat7
框架:spring+cxf/spring+axis(这里不是axis2,是axis)
1.引入依赖 pom.xml,这里把两种框架的依赖一次到位。就不分开引入了。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.zhanglfgroupId>
<artifactId>cxfClientTestartifactId>
<packaging>warpackaging>
<version>0.0.1-SNAPSHOTversion>
<name>cxfClientTest Maven Webappname>
<url>http://maven.apache.orgurl>
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>3.8.1version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-coreartifactId>
<version>4.1.7.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-beansartifactId>
<version>4.1.7.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-txartifactId>
<version>4.1.7.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>4.1.7.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-context-supportartifactId>
<version>4.1.7.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webartifactId>
<version>4.1.7.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>4.1.7.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>4.1.7.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aspectsartifactId>
<version>4.1.7.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>4.1.7.RELEASEversion>
dependency>
<dependency>
<groupId>org.apache.cxfgroupId>
<artifactId>cxf-coreartifactId>
<version>3.1.5version>
dependency>
<dependency>
<groupId>org.apache.cxfgroupId>
<artifactId>cxf-rt-frontend-jaxwsartifactId>
<version>3.1.5version>
dependency>
<dependency>
<groupId>org.apache.cxfgroupId>
<artifactId>cxf-rt-transports-httpartifactId>
<version>3.1.5version>
dependency>
<dependency>
<groupId>org.apache.axisgroupId>
<artifactId>axisartifactId>
<version>1.4version>
dependency>
<dependency>
<groupId>org.apache.axisgroupId>
<artifactId>axis-jaxrpcartifactId>
<version>1.4version>
dependency>
<dependency>
<groupId>axisgroupId>
<artifactId>axis-wsdl4jartifactId>
<version>1.5.1version>
dependency>
<dependency>
<groupId>commons-discoverygroupId>
<artifactId>commons-discoveryartifactId>
<version>0.2version>
dependency>
<dependency>
<groupId>javax.mailgroupId>
<artifactId>mailartifactId>
<version>1.4.7version>
dependency>
dependencies>
<build>
<finalName>cxfClientfinalName>
build>
project>
2.cxf和axis都没有spring配置。直接进入业务代码CxfClientTest.java
package com.zhanglf;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory;
public class CxfClientTest {
public static void main(String[] args) throws Exception {
JaxWsDynamicClientFactory factory = JaxWsDynamicClientFactory.newInstance();
//通过wsdl服务描述文件创建客户端工厂。
Client client = factory.createClient("http://localhost:8080/springCXFWebserviceDemo01/service/HelloWorldService?wsdl");
//尝试使用不带?wsdl的地址
//Client client = factory.createClient("http://localhost:8080/springCXFWebserviceDemo01/service/HelloWorldService");
//invoke(
//String operationName:要调用的方法名
//Object... params):方法的入参。可以是多个。
Object[] objs = client.invoke("sayHello", "阿福");
//invoke方法是默认返回Object[]数组。取出数组的第一位值得值就是返回值。
System.out.println(objs[0].toString());
}
}
直接Run As Java Application.可以看到在控制台打出访问服务端的代码。
拓展:这里的客户端只是传了一个简单的人名,正常传入的是个String类型的xml报文。然后服务端接收报文,进行报文解析,并对信息进行crud操作。并将执行结果以报文形式发送到客户端。客户端在进行报文解析。判断执行情况以便进行下一步操作。下面我们用axis框架进行稍微接近业务的代码开发。
axis客户端代码和cxf都在一个maven工程里。我把工程结构贴出来供参考
AxisClientTest.java的code,所有涉及的点我都在代码里说了。解释的文字比较多,代码并不多。
package com.zhanglf;
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.rpc.ParameterMode;
import org.apache.axis.client.Call;
import org.apache.axis.client.Service;
import org.apache.axis.encoding.XMLType;
/**
*
说明:address="http://localhost:8080/HelloWorld/services/user?wsdl"
* http://localhost:8080/HelloWorld:工程访问路径,
* /services:此路径在web.xml里面配置cxf拦截器前置访问路径
* /user:此路径在服务端的applicationContext.xml里配置的,要暴露给外部调用的接口,address:请求路径
*
* @author Administrator
*
*/
public class AxisClientTest {
public static void main(String[] args) throws Exception {
// namespaceURI
// 为:一般可以认为是中的targetNamespace的值,最好还是标签下的
// xmlns:tns的值为准。
// 也是webservice接口的类上注解的targetNamespace,效果如同spring中的requestMapping,用来区分请求定位到具体的那个接口。
// 这里的命名空间对应的是接口上面的targetNamespace,而不是实现类上面的。故通过wsdl中的
// 里面的targetNamespace是不准的,应该以
// namespace为准或者标签下的 xmlns:tns的值为准
String nameSpaceURI = "com.serviceTargetName";
// 指出service所在URL,这个有没有?wsdl均能正确访问。。。,这里区别于cxf,cxf是不能少这个?wsdl
String publishUrl = "http://localhost:8080/springCXFWebserviceDemo01/service/HelloWorldService?wsdl";
// 创建一个服务(service)调用(call)
Service service = new Service();
// 通过service创建call对象
Call call = (Call) service.createCall();
// 设置webservice接口地址
call.setTargetEndpointAddress(new URL(publishUrl));
// 你需要远程调用的方法new QName(定位类的targetnamespace,定位方法的方法名)
/**
* call.setOperationName(new QName("serviceTargetName", "sayHello"));
* 方法中的QName方法的入参说明: new QName( String
* namespaceURI-定位接口的命名空间:接口注解targetnamespace的值或者wsdl文件
*
call.setOperationName(new QName(nameSpaceURI, "sayHello"));
// 方法参数,如果没有参数请无视。这里如果接口没有用@WebParam(name = "name"),则会报错:Unmarshalling
// Error: 意外的元素 (uri:"", local:"name")。所需元素为<{}arg0>
call.addParameter("parameterName", XMLType.XSD_STRING, ParameterMode.IN);
// call.addParameter(new QName(soapaction,"xxxx"), XMLType.XSD_STRING,
// ParameterMode.IN);
// 设置返回类型,一般用string接收
call.setReturnType(XMLType.XSD_STRING);
// 给方法传递参数,并且调用方法
String name = "zhanglifeng";
String temp = getXml(name);
// 这里的obj{}是放入几个入参,完全由service提供的接口方法的入参决定,且顺序和你存放的顺序一致!一般入参为String类型的xml报文,回参也是xml报文。
Object[] obj = new Object[] { temp };
String result = (String) call.invoke(obj);
System.out.println(result);
}
private static String getXml(String name) {
StringBuffer sb = new StringBuffer(
"");
sb.append("" );
sb.append("" + name + "");
sb.append("");
return sb.toString();
}
}
运行方法:Run As Java Application.客户端的请求的入参报文如下:
<userBean>
<userName>" + 入参:人名 + userName>
userBean>
下面我把调用服务端的方法也贴出来,这样更好理解。昨天已经贴出了服务端的目录结构,我就把其中的HelloWorldImpl.java中的sayHello方法改一下。code如下:
package com.zlf.impl;
import javax.jws.WebService;
import org.springframework.stereotype.Component;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import com.util.XMLUtils;
import com.zlf.HelloWorld;
/**
* 由于实现类和接口不在同一个包中。所以要加上targetNamespace属性。
* 另外,这里的endpointInterface是实现类对应接口的全路径
* @author Administrator
*/
@WebService(targetNamespace="com.serviceTargetName",endpointInterface="com.zlf.HelloWorld")
@Component("HelloWord")//spring注入用
public class HelloWorldImpl implements HelloWorld {
@Override
public String sayHello(String str) {
String username="aaa";
Document document = XMLUtils.parse(str);
//首先接口开发肯定是双发都知道此方法要接受的报文格式的。我们获取报文中人名对应的节点即可。
Node node = document.getElementsByTagName("userName").item(0);
if(node !=null){
username=node.getTextContent();
}
return "你好,"+username+" 你已成功访问了webservice服务端!" ;
}
}
XMLUtils.java工具类我也贴出来,这个也比较常用。
package com.util;
import java.io.IOException;
import java.io.StringReader;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* 解析器
* @author Administrator
*
*/
public class XMLUtils {
private final static DocumentBuilder createDocumentBuilder(){
DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();
DocumentBuilder builder=null;
try {
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
builder=factory.newDocumentBuilder();
} catch (ParserConfigurationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return builder;
}
public final static Document parse(InputSource in){
try {
return createDocumentBuilder().parse(in);
} catch (SAXException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
public final static Document parse(String xml){
return parse(new InputSource(new StringReader(xml)));
}
}
这样就完成了客户端对服务端的调用。毕竟是初步入门。对以后的开发还是有所裨益。
客户端传入参数,执行String result = (String) call.invoke(new Object[] { “zhanglifeng”})后,实际发送给服务端的是一个soap底层协议自动生成的入参报文。
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:com="com.serviceTargetName">
<soapenv:Header/>
<soapenv:Body>
<com:sayHello>
<parameterName>zhanglifengparameterName>
com:sayHello>
soapenv:Body>
soapenv:Envelope>
服务端接收这个报文后自动解析,并把入参赋值给方法sayHello(String str)的入参str.经过处理数据返回给客户端,也是soap底层自动生成的回参报文
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:sayHelloResponse xmlns:ns2="com.serviceTargetName">
<return>你好,zhanglifeng 你已成功访问了webservice服务端!return>
ns2:sayHelloResponse>
soap:Body>
soap:Envelope>
客户端自动把从返回报文中取出返回值,并付值给String result。这样底层已经处理过了报文。还有一种情况就是如果客户端传过来的参数本身就是xml时,底层封装参数的问题。
如果客户端的invoke()方法入参
String xml="
<userBean><userName>zhanglifenguserName>userBean>"
底层自动为xml加上:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:com="com.serviceTargetName">
<soapenv:Header/>
<soapenv:Body>
<com:sayHello>
<parameterName>zhanglifeng ]]>parameterName>
com:sayHello>
soapenv:Body>
soapenv:Envelope>
知道这个原理,在使用SoupUI进行调用webservice接口时就可以使用
这种格式进行调用。soupui调用的入参如下图