需求:实现基于cxf框架的webservice
主要内容
- 实现基于cxf框架的webservice
- webservice发布多个服务
- 实现webservice的安全访问
开发环境
- jdk 1.8
- eclipse 2018-10-09版
- maven 3.5
- tomcat 8.5
调试工具
- 浏览器
- SoapUI接口调试工具(推荐**)
spring项目实现基于cxf框架的webservice
1、cxf jar包
<!-- cxf相关依赖 -->
<!-- cxf-core -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-core</artifactId>
<version>3.2.5</version>
</dependency>
<!-- cxf-rt-frontend-jaxws -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>3.2.5</version>
</dependency>
<!-- cxf-rt-transports-http -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>3.2.5</version>
</dependency>
<!-- cxf-rt-ws-security -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-ws-security</artifactId>
<version>3.2.5</version>
</dependency>
2、spring.xml相关配置
约束命名空间不能少
<!-- 下面是使用 cxf 配置的 webService -->
<!-- define web service provider -->
<!-- 配置第一个服务 -->
<!-- 要暴露给外部调用的接口,address:请求路径 -->
<jaxws:endpoint id="jdWebService"
implementor="com.zy.api.publish.route.jdWebServiceImpl"
address="/jdWebService">
<!-- 添加客服端请求拦截器 -->
<jaxws:inInterceptors>
<bean class="org.apache.cxf.interceptor.LoggingInInterceptor"></bean>
</jaxws:inInterceptors>
<!-- 添加服务器端响应拦截器 -->
<jaxws:outInterceptors>
<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor"></bean>
</jaxws:outInterceptors>
</jaxws:endpoint>
3、web.xml配置
<!-- webservice之cxf相关配置 -->
<servlet>
<servlet-name>CXF</servlet-name>
<servlet-class>org.apache.cxf.transport.servlet.CXFServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>CXF</servlet-name>
<url-pattern>/services/*</url-pattern>
</servlet-mapping>
4、接口类编写:
package com.zy.api.publish.route;
import javax.jws.WebParam;
import javax.jws.WebService;
/**
* @author TSY
*
* @Date: 2018-09-10
*
*
*/
@WebService(targetNamespace = "com.serviceTargetName")
public interface jdWebService {
public String waterPredictMinute(@WebParam(name = "jsonObj") String json);
public String waterPredictHour(@WebParam(name = "jsonObj") String json);
public String warning(@WebParam(name = "jsonObj") String json);
public String dispatcher(@WebParam(name = "jsonObj") String json);
public String closeLoopDispatcher(@WebParam(name = "jsonObj") String json);
public String closeLoopWater(@WebParam(name = "jsonObj") String json);
}
/////////////////// webservice开发说明 //////////////////////
/*
* @ 1.这里的@Webservice(targetNamespace="")的作用类似与spring的Controller层中的Controller("/helloworld"),用于定位
* @ 2.在要发布的服务接口类开头加上@WebService 在接口的实现类开头也加上@WebService 若两个类不在同一个包中
* 则还要在实现类上用targetNamespace指明目标命名空间。命名空间的值和接口上的值一样。
*
*/
5、实现类编写
import java.util.Date;
import java.util.List;
import java.util.Set;
import javax.jws.WebService;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.zy.core.enums.TypeEnum;
import com.zy.core.utils.DateUtil;
import com.zy.core.utils.FastJsonUtil;
import com.zy.core.utils.JacksonUtil;
import com.zy.system.entity.DispatcherScheme;
import com.zy.system.entity.PumpInfo;
import com.zy.system.entity.TempReviceData;
import com.zy.system.entity.WorkFlow;
import com.zy.system.service.DispatcherSchemeService;
import com.zy.system.service.PumpInfoService;
import com.zy.system.service.TempReciveDataService;
import com.zy.system.service.WorkFlowService;
import com.zy.system.websocket.CloseLoopDispatcherWebSocket;
import com.zy.system.websocket.JinhaiWebSocket;
import com.zy.system.websocket.WHGWebSocket;
import com.zy.system.websocket.WaterDemandWebSocket;
import com.zy.system.websocket.WorkFlowWebSocket;
/**
* @author TSY
*
* @Date: 2018-09-11
*
*/
/*
* 由于实现类和接口不在同一个包中。所以要加上targetNamespace属性。
* 另外,这里的endpointInterface是实现类对应接口的全路径
*
*/
@WebService(targetNamespace="com.serviceTargetName",endpointInterface="com.zy.api.publish.route.jdWebService")
@Component("jdWebService")//spring注入用
public class jdWebServiceImpl implements jdWebService{
@Autowired
private XxxService xxxService;
//ip地址+端口+工程名+webxml配置路径+spring配置的路径+?wsdl
//url=localhost:8080/xxxx/services/jdWebService?wsdl
@Override
public String closeLoopDispatcher(String json) {
//业务处理
return "success";
}
@Override
public String closeLoopWater(String json) {
//业务处理
return "success";
}
@Override
public String waterPredictMinute(String json) {
return "success";
}
@Override
public String waterPredictHour(String json) {
return "success";
}
@Override
public String warning(String json) {
return "success";
}
@Override
public String dispatcher(String json) {
return "success";
}
}
/* 以下是webService 的注解说明
* @param endpointInterface指定接入点接口:接口必须存在
* @param targetNamespace service 命名空间,一般为域名倒写
* @param serviceName: 对外发布的服务名,指定 Web Service 的服务名
* 称:wsdl:service。缺省值为 Java 类的简单名称 + Service。(字符串)
*/
6、验证接口是否发布成功
请求地址:
//ip地址+端口+工程名+webxml配置路径+spring配置的路径+?wsdl
//url=localhost:8080/xxx/services/jdWebService?wsdl
在浏览器中输入访问地址,出现wsdl文档说明,发布成功啦
7、客户端调用
- 最原始的http请求
- 通过生成客户端代码工具自动生成请求代码生成Webservice客户端的4种方法
-
纯java 调用,不使用任何框架https://blog.csdn.net/chrisjingu/article/details/52385632
-
通过框架调用(略)
http请求的调用方法如下:
- Dom4j解析返回的xml文件
- 需要拼装请求的soapxml
package com.zy.api.recieve.action;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* @author TSY
*
* @Date: 2018-09-25
*
*/
public class GiveWaterDemandService {
public static final String address = "http://xxxxx.nat123.cc:18837/?wsdl";//外网测试用
//public static final String address = "http://192.168.137.25:8000/?wsdl";//内网正式用
public static String giveWaterDemandToJD(String waterdemand) {
String result=null;
InputStream is =null;
try {
//第一步:创建服务地址
URL url = new URL(address);
//第二步:打开一个通向服务地址的连接
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
//第三步:设置参数
//3.1发送方式设置:POST必须大写
connection.setRequestMethod("POST");
//3.2设置数据格式:content-type
connection.setRequestProperty("content-type", "text/xml;charset=utf-8");
//3.3设置输入输出,因为默认新创建的connection没有读写权限,
connection.setDoInput(true);
connection.setDoOutput(true);
//第四步:组织SOAP数据,发送请求
String soapXML = getXML(waterdemand);
//将信息以流的方式发送出去
OutputStream os = connection.getOutputStream();
os.write(soapXML.getBytes());
//第五步:接收服务端响应,打印
int responseCode = connection.getResponseCode();
if(200 == responseCode){//表示服务端响应成功
//获取当前连接请求返回的数据流
is = connection.getInputStream();
/*
* dom4j解析返回的流信息
*/
result = ParseSoapXML.result(is);
System.out.println("推送确认的需水量数据给交大结果++++++++++++++++++++++++"+result);
}
os.close();
}catch( IOException e) {
System.out.println("异常--推送确认的需水量数据给交大结果++++++++++++++++++++++++"+ e.getMessage());
}finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
public static String getXML(String waterdemand){
String soapXML ="<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:tns=\"tns\">" +
"<soapenv:Header/>" +
"<soapenv:Body>" +
"<tns:waterdemand>" +
"<tns:data>"+
waterdemand
+ "</tns:data>" +
"</tns:waterdemand>" +
"</soapenv:Body>" +
"</soapenv:Envelope>"
;
return soapXML;
}
}
package com.zy.api.recieve.action;
import java.io.InputStream;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
/**
*
* @author Administrator
*
* dom4j解析需水量webservice返回的流信息
*/
public class ParseSoapXML {
public static String result(InputStream xml){
try {
SAXReader reader = new SAXReader();
Document doucment = reader.read(xml);
Element root = doucment.getRootElement();
Element bo = root.element("Body");
Element response = bo.element("waterdemandResponse");
Element re = response.element("waterdemandResult");
System.out.println(re.getText());
return re.getText();
} catch (DocumentException e) {
e.printStackTrace();
}
return "false";
}
}
webservice发布多个服务
很简单,将整个cxf相关的xml文件块,复制一份即可
然后,编写相关的接口和实现类
<!-- 下面是使用 cxf 配置的 webService -->
<!-- define web service provider -->
<!-- 配置第一个服务 -->
<!-- 要暴露给外部调用的接口,address:请求路径 -->
<jaxws:endpoint id="jdWebService"
implementor="com.zy.api.publish.route.jdWebServiceImpl"
address="/jdWebService">
<!-- 添加客服端请求拦截器 -->
<jaxws:inInterceptors>
<bean class="org.apache.cxf.interceptor.LoggingInInterceptor"></bean>
</jaxws:inInterceptors>
<!-- 添加服务器端响应拦截器 -->
<jaxws:outInterceptors>
<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor"></bean>
</jaxws:outInterceptors>
</jaxws:endpoint>
<!-- 配置第二个服务 -->
<jaxws:endpoint id="tempWebService"
implementor="com.zy.api.publish.route.egbWebServiceImpl"
address="/egb/thirdParty/toData">
<jaxws:inInterceptors> <!-- WS-Security拦截器 -->
<bean
class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
<constructor-arg>
<map>
<entry key="action" value="UsernameToken" />
<!-- 密码类型,PasswordText表示明文,密文是PasswordDigest -->
<entry key="passwordType" value="PasswordText" />
<entry key="passwordCallbackRef"> <!-- 回调函数引用 -->
<ref bean="myPasswordCallback" />
</entry>
</map>
</constructor-arg>
</bean>
<bean class="org.apache.cxf.interceptor.LoggingInInterceptor"></bean>
</jaxws:inInterceptors>
<jaxws:outInterceptors>
<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor"></bean>
</jaxws:outInterceptors>
</jaxws:endpoint>
实现webservice的安全访问
1、导入security相关的jar包
2、配置xml文件
WS-Security拦截器,回掉函数需要注入到bean(<bean class="com.zy.api.publish.route.ServerPasswordCallback"
id="myPasswordCallback" />)、回调函数主要用来对密码和账户进行确认的,一般可以从数据库进行查询,
<jaxws:endpoint id="tempWebService"
implementor="com.zy.api.publish.route.egbWebServiceImpl"
address="/egb/thirdParty/toData">
<jaxws:inInterceptors> <!-- WS-Security拦截器 -->
<bean
class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
<constructor-arg>
<map>
<entry key="action" value="UsernameToken" />
<!-- 密码类型,PasswordText表示明文,密文是PasswordDigest -->
<entry key="passwordType" value="PasswordText" />
<entry key="passwordCallbackRef"> <!-- 回调函数引用 -->
<ref bean="myPasswordCallback" />
</entry>
</map>
</constructor-arg>
</bean>
<bean class="org.apache.cxf.interceptor.LoggingInInterceptor"></bean>
</jaxws:inInterceptors>
<jaxws:outInterceptors>
<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor"></bean>
</jaxws:outInterceptors>
</jaxws:endpoint>
<bean
class="com.zy.api.publish.route.ServerPasswordCallback"
id="myPasswordCallback" />
2、编写回调函数
package com.zy.api.publish.route;
import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.apache.wss4j.common.ext.WSPasswordCallback;
/*
* 参考博客:https://blog.csdn.net/chrisjingu/article/details/52385632
*/
public class ServerPasswordCallback implements CallbackHandler {
@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
if ("ThirdParty".equals(pc.getIdentifier())) {
pc.setPassword("egbIh87wmnHbFiKqad9M8NNtdql");
}
}
}
/*
* 使用UsernameToken方式验证,在server端的回调函数中WSPasswordCallback只能获得用户名(identifier),一般这里的逻辑是使用这个用户名到数据库中查询其密码,
* 然后再设置到password属性,WSS4J 会自动比较客户端传来的值和你设置的这个值。你可能会问为什么这里CXF 不把客户端提交的密码传入让我们在ServerPasswordCallbackHandler中比较呢?
* 这是因为客户端提交过来的密码在SOAP 消息中已经被加密为MD5 的字符串,如果我们要在回调方法中作比较,那么第一步要做的就是把服务端准备好的密码加密为MD5 字符串,
* 由于MD5 算法参数不同结果也会有差别,另外,这样的工作CXF 替我们完成了.
*/
/*
* 使用ws-security进行安全验证,概括来说分两步:
一、创建一个验证类,如本实例的ServerPasswordCallback.class
二、配置WSS4JInInterceptor拦截器,如applicationContext-cxf.xml所示。
三、将applicationContext-cxf.xml配置到web.xml的contextConfigLocation属性中
*该类实现ws-security的CallbackHandler 接口,并重写它的handle方法。该类就是身份验证的主要类,
*当客户端传过的用户名中为“huwei“时,该方法会将指定的密码告知ws-security的WSPasswordCallback 类,
*并让它后期去和客户端的密码进行验证,通过就放行,否则打回。
*在applicationContext-cxf.xml中该类会作为WSS4JInInterceptor拦截器的回调函数属性,进行配置。
*/
3、客户端测试(SoapUI)
【浏览器、soapui都可以进行查看,但是无法进行正确调用,需要设置用户名和密码】
提示如下错误:
需要设置用户名和密码;
比如:soapui直接再header里面加上如下信息,即可
用其他框架也可以设置用户名和密码进行请求
小技巧:http请求时,xml文件可以直接copy 工具SoapUI生成的xml文件
<soapenv:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:Username>ThirdParty</wsse:Username>
<wsse:Password Type= "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">
egbIh87wmnHbFiKqad9M8NNtdql</wsse:Password>
</wsse:UsernameToken> </wsse:Security>
<soapenv:Header/>