如何在SpringMVC项目中部署WebService服务并打包生成客户端
场景
某SpringMVC项目原本为一个HTTP的WEB服务项目,之后想在该项目中添加WebService支持,使该项目同时提供HTTP服务和WebService服务。其中WebService服务通过 /ws/**
地址拦截。
配置
通过配置让SpringMVC支持WebService。
依赖
首先通过Maven引入必要依赖包。
- org.apache.cxf
- org.apache.neethi
- com.ibm.wsdl4j
- org.apache.XmlSchema
Web.xml
通过配置Web.xml使Spring框架具备WebService特性,这里通过添加Servlet(这里使用CXFServlet)实现。假设SpringMVC本身的DispatcherServlet已经启用,则在第2启动顺序添加CXFServlet。并添加servlet-mapping匹配请求。
配置如下
<!-- 在上下文中添加配置文件 --><context-param> <param-name>patchConfigLocation</param-name> <param-value> /WEB-INF/applicationServlet.xml /WEB-INF/webservice.xml <param-value></context-param><!-- 添加servlet --><servlet> <servlet-name>ws</servlet-name> <servlet-class>org.apache.cxf.trasport.servlet.CXFServlet</servlet-class> <load-on-startup>2</load-on-startup></servlet><servlet-mapping> <servlet-name>ws</servlet-name> <url-pattern>/ws/**</url-pattern></servlet-mapping>
webservice.xml
将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"> <!-- cxf必要配置 --> <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
某具体端口(XXXService)为例
这也是客户端调用时候的地址
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也一并打包进去。这样在编写对应的客户端时候,仅需专注于功能实现即可。
<?xml version="1.0"?><project name="tws-interfaces" default="jar" basedir="."> <!-- Give user a chance to override without editing this file or typing -D --> <property name="coredir" location="." /> <property name="classdir" location="${basedir}/target/classes" /> <target name="jar" description="Build the jars for core"> <delete file="${coredir}/webservice-interfaces-1.0.jar" /> <jar destfile="${coredir}/webservice-interfaces-1.0.jar"> <fileset dir="${classdir}"> <include name="**/com/xxx/apps/web/ws/server/**/*Service.class" /> <include name="**/com/xxx/apps/web/ws/server/tokenservice/**/*.class" /> <include name="**/com/xxx/apps/web/ws/server/WSReturn.class"/> <include name="**/com/xxx/apps/comm/ResultState.class"/> <include name="**/com/xxx/apps/web/ws/server/wsclient/WSClient.class"/> <include name="**/com/xxx/apps/comm/RespResult.class"/> <exclude name="**/com/xxx/apps/web/ws/server/**/*Impl.class" /> </fileset> </jar> <copy todir="../xxxclient/lib" file="./webservice-interfaces-1.0.jar"></copy> </target></project>
客户端项目实现
依赖
首先通过Maven引入必要依赖包。
- org.apache.cxf.cxf-rt-frontend-jaxws
- org.apache.cxf.cxf-rt-databinding-aegis
- org.apache.cxf.cxf-rt-transports-http
- org.apache.cxf.cxf-rt-transports-http-jetty
commons-codec.commons-codec
最重要的:引入server端打包好的jar包,里边有WSClient和必要的接口
<dependency> <groupId>com.xxx</groupId> <artifactId>xxxserver</artifactId> <version>1.0</version> <scope>system</scope> <systemPath>${project.basedir}/lib/webservice-interfaces-1.0.jar</systemPath> </dependency>
WSClient调用
通过直接调用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()); }
一些坑
一定要引入cxf的必要配置
虽然在项目中看不到,但是这些xml文件在cxf的jar包中。
<!-- cxf必要配置 --> <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" />
Interface的类路径一定要统一
如服务端的 XXXService.java
在 com.xxx.web.ws.server
中,则在客户端的XXXService.java类也应该在相同的路径即: com.xxx.web.ws.server
。 所以为方便起见,用Ant直接打包比较方便,不容易错。
客户端并发问题
本例中调用WSClient,通过反射机制调用,共用一个Factory,因此在并发时候容易出现问题,需要在WSClient中加锁
原文地址:https://blog.csdn.net/tzdwsy/article/details/51938786