web service
web service是什么
-
web service出现的目的:为了支持“异构网络”中的应用程序之间交互出现的;
-
web service被定义成一组模块化的api,可以通过网络进行远程调用;
-
web service是一个跨语言、跨平台的远程调用技术;
web service使用场景
-
不同公司系统之间的数据交互
注册微信公众号的公司 和 腾讯公司需要交互数据
电商系统和物流公司(四通一达 + 顺丰)需要数据交互(查询物流信息)
-
一些公共的数据服务:手机号归属地查询、天气预报服务、股票行情、英文翻译…
-
同一公司,不同系统(有可能不是同一种开发语言)之间的数据交互
web service的三要素
- WSDL(Web Service Definition/Description Language)
web服务定义/描述语言
- 用于描述具体服务,定义客户端和服务端之间数据交互时传递的数据格式(请求和响应的数据)
- 每一个web service对应为一个wsdl文档
- WSDL可以认为是web service说明书
- SOAP(Simple Object Access Protocol)
简单对象访问协议,基于Http协议,使用XML传递消息;
它是一种轻量级的通信协议;
用于不同应用之间的通信;
使用Http协议进行通信;
独立于平台、编程语言,基于XML,简单并可扩展
- UDDI
web service规范
- JAX-WS规范
全称:Java Api For Xml-Based WebService;
早期的时候是叫JAX-RPC(Java Api For Xml-Rmote Procedure Call);
JAX-RPC目前已经被JAX-WS取代;JDK5.0开始支持JAX-WS的2.0版本;
JDK1.6.0_13版本开始支持JAX-WS的2.1版本
JDK7支持了JAX-WS的2.2版本采用标准的Soap协议传输数据,Soap协议是基于应用层的Http协议,传输xml数据;
采用WSDL做为服务的描述语言;
- JAX-RS规范
是Java针对REST风格的请求制定的一套web服务规范;
没有随着JDK1.6发布支持JAX-RS规范的框架
- CXF
- RESTEasy :JBOSS的
- RESTLet:比较早的rest框架,比JAX-RS规范还要早
- Jersey
使用JDK方式开发web service
开发
HelloService.java==>接口
package com.etoak.service;
// 这个@WebService需要同时写在接口和实现类上
@WebService
public interface HelloService {
String sayHello(String name);
}
HelloServiceImpl.java==>实现
package com.etoak.service.impl;
@WebService
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) {
System.out.println("server invoke");
return "Hello " + name;
}
}
JdkService.java==>启动服务
package com.etoak;
public class JdkServer {
public static void main(String[] args) {
// 参数一:服务地址
// 参数二:要发布的服务
Endpoint.publish("http://localhost:8000/hello",
new HelloServiceImpl());
}
}
调用
将开发的webservice拉取到本地
cmd命令
wsimport -keep http://localhost:8000/hello?wsdl
将文件粘贴到工程中
JdkClient.java==>客户端调用
package com.etoak;
public class JdkClient {
public static void main(String[] args) {
HelloServiceImplService service = new HelloServiceImplService();
HelloServiceImpl soap = service.getHelloServiceImplPort();
String result = soap.sayHello("web service");
System.out.println(result);
}
}
使用CXF
CXF框架是Apache的顶级开源项目
官方地址:cxf.apache.org
Apache CXF = Celtix + Xfire,开始叫 Apache CeltiXfire,后来更名为 Apache CXF了。
Apache CXF 是一个开源的 web Service 框架,CXF 帮助您构建和开发 web Services,它支持多种协议,比如:SOAP1.1,1,2 XML/HTTP、RESTful 或者CORBA。
灵活的部署方式: 可以运行在Tomcat、Jboss、Jetty(内置)、weblogic上面。
下载CXF二进制包
解压到当前目录
主要用到bin目录下的一个命令:wsdl2java
这个命令用来根据wsdl创建客户端代码;
wsdl2java -d E:\xxx wsdl地址
maven依赖
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>3.1.18</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>3.1.18</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http-jetty</artifactId>
<version>3.1.18</version>
</dependency>
使用CXF开发服务器与客户端
开发
- 添加依赖
- 实体类:略
- 定义服务接口UserService.java
package com.etoak.service;
/**
* 定义服务接口
*/
@WebService(serviceName = "UserService", portName = "UserServiceSoap")
public interface UserService {
User getById(Integer id);
}
- 实现UserServiceImpl.java
package com.etoak.service.impl;
@WebService(serviceName = "UserService", portName = "UserServiceSoap")
public class UserServiceImpl implements UserService {
@Override
public User getById(Integer id) {
return new User(id, "zs", 23);
}
}
- 启动服务CxfService.java
package com.etoak;
public class CxfServer {
public static void main(String[] args) {
// 1. 创建JaxWsServerFactoryBean
JaxWsServerFactoryBean factory = new JaxWsServerFactoryBean();
// 2. 设置wsdl地址
factory.setAddress("http://localhost:8001/user");
// 3. 设置要发布的服务
factory.setServiceClass(UserService.class);
// 4. 设置服务实现
factory.setServiceBean(new UserServiceImpl());
// 5. 创建服务并启动服务
Server server = factory.create();
server.start();
System.out.println("server start");
}
}
调用
-
拉取文件
-
到bin目录下启动cmd
`wsdl2java -d E:\xxx wsdl地址`
-
-
调用CxfClient.java
package com.etoak;
public class CxfClient {
public static void main(String[] args) {
// 1. 创建JaxWsProxyFactoryBean
JaxWsProxyFactoryBean factory =
new JaxWsProxyFactoryBean();
// 2. 设置wsdl访问地址
factory.setAddress("http://localhost:8001/user");
// 3. 设置服务接口
factory.setServiceClass(UserService.class);
// 4. 创建远程服务的代理对象
UserService userService = (UserService)factory.create();
User user = userService.getById(100);
System.out.println(user.getId() + " - "
+ user.getName() + " - "
+ user.getAge());
}
}
调用其他webservice【以手机号码归属地查询服务为例】
- 拉取文件【网址在网上获得】,文件存放在E盘下cxf文件夹内
wsdl2java -d E:/cxf http://ws.webxml.com.cn/WebServices/MobileCodeWS.asmx?wsdl
-
将文件复制到工程中
-
创建文件调用服务
-
jdk方式
package cn.com; public class Client { public static void main(String[] args) throws Exception{ MobileCodeWS service = new MobileCodeWS(); MobileCodeWSSoap soap = service.getMobileCodeWSSoap(); //以下两个方法由该服务提供 /** *获得国内手机号码归属地省份、地区和手机卡类型信息 *输入参数:mobileCode = 字符串(手机号码,最少前7位数字),userID = 字符串(商业用户ID) 免费用户为空字符串; *返回数据:字符串(手机号码:省份 城市 手机卡类型)。 */ String result = soap.getMobileCodeInfo("15508633260",""); /** *获得国内手机号码归属地数据库信息 *输入参数:无;返回数据:一维字符串数组(省份 城市 记录数量)。 */ ArrayOfString databaseInfo = soap.getDatabaseInfo(); System.out.println(result); System.out.println(databaseInfo.getString()); } }
-
cxf方式
package cn.com; public class Cxf { public static void main(String[] args) { JaxWsProxyFactoryBean factoryBean = new JaxWsProxyFactoryBean(); factoryBean.setAddress("http://ws.webxml.com.cn/WebServices/MobileCodeWS.asmx?wsdl"); factoryBean.setServiceClass(MobileCodeWSSoap.class); MobileCodeWSSoap soap = (MobileCodeWSSoap)factoryBean.create(); //同上 String result = soap.getMobileCodeInfo("15508633260",""); ArrayOfString databaseInfo = soap.getDatabaseInfo(); System.out.println(result); System.out.println(databaseInfo.getString()); } }
-
cxf整合spring
Maven依赖
javax.servlet-api
spring-web(不是spring-webmvc)
spring-context
cxf-rt-frontend-jaxws
cxf-rt-transports-http
lombok
服务端
- 在web.xml中配置CXF
<web-app>
<!-- CXFServlet -->
<servlet>
<servlet-name>CXF</servlet-name>
<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
<!-- 如果不配置config-location,默认查找/WEB-INF/cxf-servlet.xml -->
<init-param>
<param-name>config-location</param-name>
<param-value>classpath:spring-cxf.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>CXF</servlet-name>
<url-pattern>/ws/*</url-pattern>
</servlet-mapping>
</web-app>
- 创建服务接口
package com.etoak.service;
/**
* 用户服务接口
*/
@WebService(serviceName = "UserServiceWS", portName = "UserServiceSoap")
public interface UserService {
User getById(Integer id);
}
package com.etoak.mapper;
public interface UserMapper {
User getById(int id);
}
- 创建服务接口实现
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.etoak.mapper.UserMapper">
<select id="getById" parameterType="int" resultType="User">
select id,name,age from t_springmvc_user where id = #{value}
</select>
</mapper>
package com.etoak.service.impl;
@WebService(serviceName = "UserServiceWS",portName = "UserServiceSoap")
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Override
public User getById(Integer id) {
return userMapper.getById(id);
}
}
- 使用spring发布web service服务
spring-cxf.xml
<context:component-scan base-package="com.etoak" />
<!-- 数据源
SqlSessionFactoryBean
MapperScannerConfigurer
-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://127.0.0.1:3306/et1912" />
<property name="username" value="root" />
<property name="password" value="root" />
<!-- 初始化连接数 -->
<property name="initialSize" value="20" />
<!-- 最大活跃连接数 -->
<property name="maxActive" value="50" />
<property name="validationQuery" value="select 1" />
</bean>
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="typeAliasesPackage" value="com.etoak.bean" />
<property name="mapperLocations"
value="classpath:/mappers/*.xml" />
<property name="plugins" >
<list>
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<props>
<prop key="reasonable">true</prop>
</props>
</property>
</bean>
</list>
</property>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.etoak.mapper" />
</bean>
<!--
1. JaxWsServerFactoryBean
2. wsdl地址 : http://localhost:8001/ws/user
3. 设置服务接口
4. 设置服务实现
5. 创建服务并启动服务
-->
<jaxws:server address="/user" serviceClass="com.etoak.service.UserService">
<jaxws:serviceBean>
<ref bean="userServiceImpl" />
</jaxws:serviceBean>
</core:bus>
客户端
- Maven依赖
cxf-rt-frontend-jaxws
cxf-rt-transports-http
cxf-rt-transports-http-jetty
spring-context
-
根据wsdl创建客户端代码,
-
开启服务,利用以下命令获得客户端代码
-
wsdl2java -d e:/client http://localhost:8080/ws/user?wsdl
-
-
配置spring bean
<context:component-scan base-package="com.etoak" />
<!--
JaxWsProxyFactoryBean
setAddress:地址
setServiceClass: 服务接口
创建服务代理
-->
<jaxws:client id="userService" address="http://localhost:9090/ws/user" serviceClass="com.etoak.service.UserService">
</jaxws:client>
- 客户端调用
package com.etoak;
public class SpringClient {
public static void main(String[] args) {
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-cxf.xml");
UserService userService = (UserService)ioc.getBean("userService");
User user = userService.getById(2);
System.out.println(user.getId()+"+"+user.getName()+"+"+user.getAge());
}
}
CXF拦截器
Cxf可以在web service发送前后,动态操作请求和响应的报文数据(xml的soap消息);
-
拦截器分类
-
按消息方向:In拦截器、Out拦截器
客户端发送请求,先走Out拦截器,结果回来之前走In拦截器;
服务端接收请求,先走In拦截器,响应之前走Out拦截器;
-
拦截位置:服务端拦截、客户端拦截;
-
按定义者:Cxf官方定义的拦截器、使用方定义的拦截(自定义拦截器);
-
-
拦截的几个API
- Interceptor**、PhaseInterceptor阶段拦截、**SoapInterceptor
- 阶段拦截器:定义了拦截器需要在哪个阶段(封装协议前、发送前、发送后、接收后…)进行拦截。详细阶段在Phase.java`类中定义
配置官方拦截器
-
服务端拦截器
- 配置In拦截器
<jaxws:server address="/user" serviceClass="com.etoak.service.UserService"> <jaxws:serviceBean> <ref bean="userServiceImpl" /> </jaxws:serviceBean> <!-- 服务器接收请求:使用In拦截器 --> <jaxws:inInterceptors> <!-- 使用官方定义的日志拦截器: 默认拦截阶段 -> receive --> <bean class="org.apache.cxf.interceptor.LoggingInInterceptor" /> </jaxws:inInterceptors> </jaxws:server>
- 配置Out拦截器
<jaxws:server address="/user" serviceClass="com.etoak.service.UserService"> <jaxws:serviceBean> <ref bean="userServiceImpl" /> </jaxws:serviceBean> <!-- 服务器响应结果:使用Out拦截器 --> <jaxws:outInterceptors> <!-- 使用官方定义的日志拦截器 --> <bean class="org.apache.cxf.interceptor.LoggingOutInterceptor" /> </jaxws:outInterceptors> </jaxws:server>
-
客户端拦截器
- 配置In拦截器
<jaxws:client id="userService" address="http://localhost:9090/ws/user" serviceClass="com.etoak.service.UserService"> <!-- 客户端In拦截器:接收服务器响应的某个阶段(receive)执行 --> <jaxws:inInterceptors> <bean class="org.apache.cxf.interceptor.LoggingInInterceptor" /> </jaxws:inInterceptors> </jaxws:client>
- 配置Out拦截器
<jaxws:client id="userService" address="http://localhost:9090/ws/user" serviceClass="com.etoak.service.UserService"> <!-- 客户端Out拦截器:客户端在发送请求的某个阶段(pre-stream)执行 --> <jaxws:outInterceptors> <bean class="org.apache.cxf.interceptor.LoggingOutInterceptor" /> </jaxws:outInterceptors> </jaxws:client>
配置自定义拦截器
- 自定义拦截器需要继承AbstractPhaseInterceptor
- 使用自定义拦截器实现用户验证
- 用户请求Soap消息
- 验证失败返回的Soap消息
-
客户端创建、配置自定义拦截器
- 创建拦截器
package com.etoak.interceptor; public class AuthOutInterceptor extends AbstractPhaseInterceptor<SoapMessage> { private String name; private String password; public AuthOutInterceptor(String name, String password) { // 调用父类的构造方法,传入默认的拦截阶段 super(Phase.PREPARE_SEND); this.name = name; this.password = password; } /** * 处理请求数据 * <soap:Header> * <et1912> * <name>zs</name> * <password>123456</password> * </et1912> * </soap:Header> * * @param message * @throws Fault */ @Override public void handleMessage(SoapMessage message) throws Fault { // 创建Document Document document = DOMUtils.createDocument(); Element et1912 = document.createElement("et1912"); // 创建子元素<name> <password> Element nameElement = document.createElement("name"); nameElement.setTextContent(this.name); Element passwordElement = document.createElement("password"); passwordElement.setTextContent(this.password); // 将<name>和<password>元素添加到et1912 et1912.appendChild(nameElement); et1912.appendChild(passwordElement); Header header = new Header(new QName(""), et1912); message.getHeaders().add(header); } }
- 配置拦截器
<jaxws:client id="userService" address="http://localhost:9090/ws/user" serviceClass="com.etoak.service.UserService"> <!-- 客户端In拦截器:接收服务器响应的某个阶段(receive)执行 --> <jaxws:inInterceptors> <bean class="org.apache.cxf.interceptor.LoggingInInterceptor" /> </jaxws:inInterceptors> <!-- 客户端Out拦截器: --> <jaxws:outInterceptors> <!-- 客户端在发送请求的某个阶段(pre-stream)执行 --> <bean class="org.apache.cxf.interceptor.LoggingOutInterceptor" /> <!-- 配置自定义拦截器--> <bean class="com.etoak.interceptor.AuthOutInterceptor"> <constructor-arg name="name" value="zs" /> <constructor-arg name="password" value="123456" /> </bean> </jaxws:outInterceptors> </jaxws:client>
- 发送soap报文
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Header> <et1912> <name>zs</name> <password>123456</password> </et1912> </soap:Header> <soap:Body> <ns2:getById xmlns:ns2="http://service.etoak.com/"> <arg0>1</arg0> </ns2:getById> </soap:Body> </soap:Envelope>
- 错误响应
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <soap:Fault> <faultcode>soap:Server</faultcode> <faultstring>用户名格式错误</faultstring> </soap:Fault> </soap:Body> </soap:Envelope>
- 正确响应
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <ns2:getByIdResponse xmlns:ns2="http://service.etoak.com/"> <return> <age>22</age> <id>1</id> <name>admin</name> </return> </ns2:getByIdResponse> </soap:Body> </soap:Envelope>
-
服务端创建、配置自定义拦截器
- 创建拦截器
package com.etoak.interceptor; public class AuthInInterceptor extends AbstractPhaseInterceptor<SoapMessage> { public AuthInInterceptor(String phase) { super(phase); } @Override public void handleMessage(SoapMessage message) throws Fault { Header et1912 = message.getHeader(new QName("et1912")); if(et1912 != null) { // 解析soap header Element et1912Ele = (Element)et1912.getObject(); // 获取name元素 并校验name元素 NodeList nameList = et1912Ele.getElementsByTagName("name"); if(nameList == null || nameList.getLength() != 1) { throw new Fault(new RuntimeException("用户名格式错误")); } // 获取password元素,并校验password元素 NodeList passwordList = et1912Ele.getElementsByTagName("password"); if(passwordList == null || passwordList.getLength() != 1) { throw new Fault(new RuntimeException("密码格式错误")); } // 走到这里,表示校验通过,获取用户名和密码 String name = nameList.item(0).getTextContent(); String password = passwordList.item(0).getTextContent(); // 校验用户名和密码 (根据用户名和密码查询数据库) if("zs".equals(name) && "123456".equals(password)) { System.out.println("校验成功!"); return; } else { throw new Fault(new RuntimeException("用户名或密码错误")); } } // 走到这里,表示soap消息中没有<et1912>的Header元素 throw new Fault(new RuntimeException("请传入用户名和密码")); } }
- 配置拦截器
<jaxws:server address="/user" serviceClass="com.etoak.service.UserService"> <jaxws:serviceBean> <ref bean="userServiceImpl" /> </jaxws:serviceBean> <!-- 服务器接收请求:使用In拦截器 --> <jaxws:inInterceptors> <!-- 使用官方定义的日志拦截器: 默认拦截阶段 -> receive --> <bean class="org.apache.cxf.interceptor.LoggingInInterceptor" /> <!-- 使用自定义拦截器 --> <bean class="com.etoak.interceptor.AuthInInterceptor"> <!-- 执行阶段:方法执行前拦--> <constructor-arg name="phase" value="pre-invoke" /> </bean> </jaxws:inInterceptors> <!-- 服务器响应结果:使用Out拦截器 --> <jaxws:outInterceptors> <!-- 使用官方定义的日志拦截器 --> <bean class="org.apache.cxf.interceptor.LoggingOutInterceptor" /> </jaxws:outInterceptors> </jaxws:server>
- 配置全局拦截器
<jaxws:server address="/user" serviceClass="com.etoak.service.UserService"> <jaxws:serviceBean> <ref bean="userServiceImpl" /> </jaxws:serviceBean> </jaxws:server> <jaxws:server address="/student" serviceClass="com.etoak.service.StudentService"> <jaxws:serviceBean> <ref bean="studentServiceImpl" /> </jaxws:serviceBean> </jaxws:server> <core:bus> <core:inInterceptors> <!-- 使用官方定义的日志拦截器: 默认拦截阶段 -> receive --> <bean class="org.apache.cxf.interceptor.LoggingInInterceptor" /> <!-- 使用自定义拦截器 --> <bean class="com.etoak.interceptor.AuthInInterceptor"> <!-- 执行阶段:方法执行前拦--> <constructor-arg name="phase" value="pre-invoke" /> </bean> </core:inInterceptors> <core:outInterceptors> <!-- 使用官方定义的日志拦截器 --> <bean class="org.apache.cxf.interceptor.LoggingOutInterceptor" /> </core:outInterceptors> </core:bus>