某SpringMVC项目原本为一个HTTP的WEB服务项目,之后想在该项目中添加WebService支持,使该项目同时提供HTTP服务和WebService服务。其中WebService服务通过 /ws/**
地址拦截。
通过配置让SpringMVC支持WebService。
首先通过Maven引入必要依赖包。
通过配置Web.xml使Spring框架具备WebService特性,这里通过添加Servlet(这里使用CXFServlet)实现。假设SpringMVC本身的DispatcherServlet已经启用,则在第2启动顺序添加CXFServlet。并添加servlet-mapping匹配请求。
配置如下
<context-param>
<param-name>patchConfigLocationparam-name>
<param-value>
/WEB-INF/applicationServlet.xml
/WEB-INF/webservice.xml
<param-value>
context-param>
<servlet>
<servlet-name>wsservlet-name>
<servlet-class>org.apache.cxf.trasport.servlet.CXFServletservlet-class>
<load-on-startup>2load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>wsservlet-name>
<url-pattern>/ws/**url-pattern>
servlet-mapping>
将webservice的接口配置单独分离出来。配置如下:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<import resource="classpath:META-INF/cxf/cxf.xml" />
<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
<jaxws:endpoint id="ticketDecodeAuthService"
implementorClass="com.xxx.apps.web.ws.server.decode.XXXServiceImpl"
address="/ticketDecodeAuth" />
beans>
对应上文声明的接口文档在写在相应的位置上(如本文例子则写在com.xxx.apps.web.ws.server.decode包中)
代码如下:
@WebService
@SOAPBinding(style = Style.RPC)
public interface XXXService {
public WSReturn getAuth(String userName, String password) throws Exception;
}
接口实现类:
@WebService
@SOAPBinding(style = Style.RPC)
@SuppressWarnings("deprecation")
public class XXXServiceImpl implements XXXService {
private static final Logger LOGGER = Logger.getLogger(XXXServiceImpl.class);
@Override
public WSReturn getAuth(String userName, String password) throws Exception {
// WSReturn 是自定义的通用接口返回包装,可以用别的
WSReturn res = new WSReturn();
// TODO : your code here
return res;
}
}
启动SpringMVC项目,根据配置文件定义,接口地址类似:http://ip:port/项目名/ws/**
若本例配置则有如下接口可以查看:
http://ip:port/项目名/ws
这也是客户端调用时候的地址
http://ip:port/项目名/ws/XXXService?wsdl
这里可以看到端口的规范定义
通过CXF的动态代理方式编写,以反射方式将class直接引入可以实现统一调用方法。这样该Client即可调用任意接口。
代码如下:
/**
* webservice服务客户端
* @author WSY
*
*/
public class WSClient {
private static Logger logger = LoggerFactory.getLogger(WSClient.class);
/**
* 调用代理
* @param cls 服务接口代理
* @param method 方法名
* @param wsdl wsdl地址
* @param params 参数Object[]
* @return
* @throws Exception
*/
@SuppressWarnings("rawtypes")
public static WSReturn invoke(Class cls,String method,String wsdl, Object[] params) throws Exception{
synchronized(WSClient.class){
logger.info("[WSClient invoking] - class:"+cls.getName()+"; method:"+method+"; wsdl:"+
wsdl+"; params:"+getParams(params));
JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
factory.getInInterceptors().add(new LoggingInInterceptor());
factory.getOutInterceptors().add(new LoggingOutInterceptor());
factory.setServiceClass(cls);
factory.setAddress(wsdl);
Object cInstance = factory.create();
Method invokeMethod = null;
for(Method m : cls.getDeclaredMethods()){
if(m.getName().equalsIgnoreCase(method)){
invokeMethod = m;
break;
}
}
if(invokeMethod == null)
throw new Exception("ERROR:method not found");
WSReturn res = (WSReturn) invokeMethod.invoke(cInstance, params);
return res;
}
private static String getParams(Object[] params){
StringBuilder sb = new StringBuilder("{");
for(Object b : params){
sb.append(b).append(",");
}
if(sb.length()==1)
return "{}";
else
return sb.substring(0,sb.length()-1)+"}";
}
}
}
写个Ant脚本将一些必要的Java类和定义的Interface(不要打实现类)打成包。本文中将Client代码也写在了Service端了,所以将WSClient也一并打包进去。这样在编写对应的客户端时候,仅需专注于功能实现即可。
"1.0"?>
"tws-interfaces" default="jar" basedir=".">
"coredir" location="." />
"classdir" location="${basedir}/target/classes" />
"jar" description="Build the jars for core">
"${coredir}/webservice-interfaces-1.0.jar" />
"${coredir}/webservice-interfaces-1.0.jar">
"${classdir}">
"**/com/xxx/apps/web/ws/server/**/*Service.class" />
"**/com/xxx/apps/web/ws/server/tokenservice/**/*.class" />
"**/com/xxx/apps/web/ws/server/WSReturn.class"/>
"**/com/xxx/apps/comm/ResultState.class"/>
"**/com/xxx/apps/web/ws/server/wsclient/WSClient.class"/>
"**/com/xxx/apps/comm/RespResult.class"/>
"**/com/xxx/apps/web/ws/server/**/*Impl.class" />
"../xxxclient/lib" file="./webservice-interfaces-1.0.jar">
首先通过Maven引入必要依赖包。
commons-codec.commons-codec
最重要的:引入server端打包好的jar包,里边有WSClient和必要的接口
<dependency>
<groupId>com.xxxgroupId>
<artifactId>xxxserverartifactId>
<version>1.0version>
<scope>systemscope>
<systemPath>${project.basedir}/lib/webservice-interfaces-1.0.jarsystemPath>
dependency>
通过直接调用jar包中的WSClient即可调用远程WebService接口。
调用示例代码如下:
/** 这里将调用注释复制过来
* 调用代理
* @param cls 服务接口代理
* @param method 方法名
* @param wsdl wsdl地址
* @param params 参数Object[]
* @return
* @throws Exception
*/
WSReturn res= WSClient.invoke(XXXService.class
, "getAuth"
,endpoints.get(XXXService.class.getName())
, new Object[]{"admin","admin"});
if(token.getStatusId() == ResultState.SUCESS){
tokenValue = (String) token.getMap().get("token");
} else {
logger.error("获取token失败:"+token.getMsg());
}
虽然在项目中看不到,但是这些xml文件在cxf的jar包中。
<import resource="classpath:META-INF/cxf/cxf.xml" />
<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
如服务端的 XXXService.java
在 com.xxx.web.ws.server
中,则在客户端的XXXService.java类也应该在相同的路径即: com.xxx.web.ws.server
。 所以为方便起见,用Ant直接打包比较方便,不容易错。
本例中调用WSClient,通过反射机制调用,共用一个Factory,因此在并发时候容易出现问题,需要在WSClient中加锁