<pre name="code" class="java">
webservice定义了一种不同系统之间功能调用的标准,也就是RPC调用。在j2ee中,ejb提供了这种分布式的RPC,原理是用jndi发布ejb远程无状态bean,客户端得到bean的引用完成方法调用,这种方式对于同是java系统的调用非常的方便,但对于异构系统的访问就非常的不便,特别是在互联网中,基于ejb调用的sockect连接可能无法穿过企业防火墙以完成方法的调用。
webservice提供了一种标准的不同系统之间的调用,基于soap协议,底层可以选用http传输协议(可以穿过防火墙),而且中间信息传递是 xml格式的,消息体的打包和解析十分的方便,可以说ws就是基本于xml、http的远程RPC调用。
java下常用的ws框架有axis2、xfire等,xfire已经升级为cxf,相对axis来说要,使用要简单得多,而且cxf与spring集成非常的紧密,下面我以具体实例来建立ws服务器,并写出了在cxf,axis以及php中的调用示例,本文中的示例可直接应用于生产环境。
首先写出ws的接口,这里要用到外观模式。
1
2
3
4
5
6
7
@WebService
public interface IWebServiceFacade {
public String sayHello ( @WebParam ( name = "uname" ) String name );
public User getFirstUserByParent ( @WebParam ( name = "userParam" ) User u );
public @WebResult ( partName = "users" ) List getAllUsers ();
public int add ( @WebParam ( name = "num1" ) String number1 , @WebParam ( name = "num2" ) int number2 );
}
为了让示例具有通用性,我定义了一些典型的方法,比如:方法接收对象类型参数,返加对象类型结果,返回集合类型结果,有两个参数的情况等等。
该接口的实现代码如下(注意,bizService由spring注入):
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
@WebService ( endpointInterface = "com.my.webservice.IWebServiceFacade" )
public class WebServiceFacadeImpl implements IWebServiceFacade {
//本地业务接口
private IBizService bizService ;
@Override
public String sayHello ( String name ) {
return "Hello," + name ;
}
public int add ( String number1 , int number2 ) {
return ( Integer . parseInt ( number1 ) + number2 );
}
@Override
public List getAllUsers () {
//此处应调用 bizService方法完成,作为示例,我直接返回模拟数据
List users = new ArrayList ();
users . add ( new User ( 1 , "张三" ));
users . add ( new User ( 2 , "李四" ));
users . add ( new User ( 3 , "王五" ));
return users ;
}
@Override
public User getFirstUserByParent ( User u ) {
//此处应调用 bizService方法完成,作为示例,我直接返回模拟数据
System . out . println ( "example param user ,name is" + u . getUserName () + ", birth is:" + u . getBirthday (). toLocaleString ());
return new User ( 1 , u . getUserName () + "--admin" );
}
}
至此,服务器端的代码已经完成了,但还需要一定的配置,在服务器上的配置代码片断如下(web.xml):
web.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<servlet>
<servlet-name> CXFServlet</servlet-name>
<servlet-class> org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
<load-on-startup> 1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name> CXFServlet</servlet-name>
<url-pattern> /services/*</url-pattern>
</servlet-mapping>
<listener>
<listener-class> org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name> contextConfigLocation</param-name>
<param-value> /WEB-INF/classes/app_ws_server.xml</param-value>
</context-param>
app_ws_server.xml中(这是一个spring配置文件),关键配置代码如下:
app_ws_server.xml
1
2
3
4
5
<import resource= "classpath:META-INF/cxf/cxf.xml" />
<import resource= "classpath:META-INF/cxf/cxf-extension-soap.xml" />
<import resource= "classpath:META-INF/cxf/cxf-servlet.xml" />
<jaxws:endpoint id= "myWebService" address= "/myWebService" implementor= "com.my.webservice.WebServiceFacadeImpl" >
</jaxws:endpoint>
重新启动服务器,如果一切正常的话,在浏览器地址栏中输入http://localhost:8088/web_service_server/services/myWebService?wsdl 就可以看到wsdl文件,有了wsdl,其它客户端的调用将不存在问题。
下面,我将写出不同客户端对上面的ws调用代码
最为简单的是cxf自己的客户端调用方式,先写一篇spring配置文件,将要调用的客户端写入:
ws-client.xml
1
2
3
4
5
6
7
8
<bean id= "wsClient" class= "com.my.webservice.IWebServiceFacade"
factory-bean= "wsClientProxy" factory-method= "create" />
<bean id= "wsClientProxy" class= "org.apache.cxf.jaxws.JaxWsProxyFactoryBean" >
<property name= "serviceClass" value= "com.my.webservice.IWebServiceFacade" />
<property name= "address"
value= "http://localhost:8088/web_service_server/services/myWebService" />
</bean>
cxf的测试代码如下,wsClient由测试框架自动注入,这种方式应该是在java下调用ws服务最简单的方法,和本地方法调用没有任何区别。
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
31
32
33
34
35
36
37
38
public class WebServiceTest extends
AbstractDependencyInjectionSpringContextTests {
private IWebServiceFacade wsClient ;
public void testSayHello () {
System . out . println ( "====================" );
String result = wsClient . sayHello ( "lovo" );
System . out . println ( result );
System . out . println ( "add:" + wsClient . add ( "5" , 3 ));
User u = wsClient . getFirstUserByParent ( new User ( 1 , "刘江华" ));
System . out . println ( u . getUserName ());
List < User > users = wsClient . getAllUsers ();
System . out . println ( "users List size: " + users . size ());
System . out . println ( users . get ( 0 ). getUserName () );
System . out . println ( users . get ( 0 ). getBirthday ());
}
@Override
protected String [] getConfigLocations () {
setAutowireMode ( AUTOWIRE_BY_NAME );
return new String []{ "classpath:ws-client.xml" };
}
public void setWsClient ( IWebServiceFacade wsClient ) {
this . wsClient = wsClient ;
}
}
鉴于axis使用比较广泛,我写出了相应的调用代码,axis下调用代码相对来说要繁琐些,特别要注意在传对象参数时需要call.setProperty(AxisEngine.PROP_DOMULTIREFS, false),关闭multirefs的生成(通过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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
String endpoint = "http://localhost:8088/web_service_server/services/myWebService" ;
Service service = new Service ();
//测试sayHello
Call call = ( Call ) service . createCall ();
call . setTargetEndpointAddress ( new java . net . URL ( endpoint ));
call . setOperationName ( new QName ( "http://webservice.my.com/" , "sayHello" ));
call . addParameter ( "uname" , XMLType . SOAP_STRING , ParameterMode . IN );
call . setReturnType ( XMLType . SOAP_STRING );
String result = ( String ) call . invoke ( new Object [] { "good" });
System . out . println ( result );
//测试add
call = ( Call ) service . createCall ();
call . setTargetEndpointAddress ( new java . net . URL ( endpoint ));
call . setOperationName ( new QName ( "http://webservice.my.com/" , "add" ));
call . addParameter ( "num1" , XMLType . SOAP_STRING , ParameterMode . IN );
call . addParameter ( "num2" , XMLType . SOAP_INT , ParameterMode . IN );
call . setReturnType ( XMLType . SOAP_INT );
//参数顺序和addParameter一致
int r = ( Integer ) call . invoke ( new Object [] { "5" , "3" });
System . out . println ( r );
//测试得到所有用户
call = ( Call ) service . createCall ();
call . setTargetEndpointAddress ( new java . net . URL ( endpoint ));
call . setOperationName ( new QName ( "http://webservice.my.com/" , "getAllUsers" ));
call . setReturnType ( org . apache . axis . encoding . XMLType . SOAP_VECTOR );
Vector v = ( Vector ) call . invoke ( new Object [] {});
for ( int i = 0 ; i < v . size (); i ++) {
Vector v2 = ( Vector ) v . get ( i );
for ( int j = 0 ; j < v2 . size (); j ++) {
System . out . print ( v2 . get ( j ) + " " );
}
System . out . println ();
}
//传入用户对象,得到一个用户
call = ( Call ) service . createCall ();
call . setTargetEndpointAddress ( new java . net . URL ( endpoint ));
call . setOperationName ( new QName ( "http://webservice.my.com/" , "getFirstUserByParent" ));
QName qn = new QName ( "http://webservice.my.com/" , "user" );
call . addParameter ( "userParam" , qn , ParameterMode . IN );
call . setProperty ( AxisEngine . PROP_DOMULTIREFS , false );
call . registerTypeMapping ( User . class , qn , new BeanSerializerFactory ( User . class , qn ), null );
call . setReturnType ( org . apache . axis . encoding . XMLType . SOAP_VECTOR );
User u = new User ( 1 , " 刘江华 " );
v = ( Vector ) call . invoke ( new Object [] { u });
System . out . println ( v );
最后,给出非java系统的一个客户端调用示例,php的调用代码如下(要注意,php默认情况下没有开启对soap的支持,需求修改php的配置文件):
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
<? php
$ws = "http://localhost:8088/web_service_server/services/myWebService?wsdl" ;
$client = new SoapClient ( $ws , array ( 'trace' => 1 , 'uri' => 'http://webservice.my.com/' ));
echo ( "sayHello ws call:" );
print_r ( $client -> sayHello ( array ( 'uname' => 'ljh' )));
echo ( "<br><br>" );
echo ( "add ws call: " );
print_r ( $client -> add ( array ( 'num1' => 5 , 'num2' => 3 )));
echo ( "<br><br>" );
echo ( "getAllUsers ws call: " );
print_r ( $client -> getAllUsers ());
echo ( "<br><br>" );
echo ( "getFirstUserByParent ws call: " );
$aUser = array ();
$aUser [ 'id' ] = 1 ;
$aUser [ 'userName' ] = '刘江华' ;
$aUser [ 'birthday' ] = date ( "Y-m-d" );
print_r ( $client -> getFirstUserByParent ( array ( 'userParam' => $aUser )));
?>
在客户端的调用中我们可以发现,php对ws的调用是相当的简单(请注意在构建SoapClient对象时传入了wsdl的路径,php就是根据wsdl来发送和接收服务器端数据的),正是由于这些原因,php在建站中被广泛使用(入门门槛比较低)。