0. web service内容
- web service是什么
- web service使用场景
- web service三要素
- java的web service规范
- 使用jdk方式发布web service
- 使用cxf方式发布web service
- cxf整合spring web
- cxf拦截器
1. web service是什么
web service出现的目的:为了支持处于“异构网络”的应用程序之间交互(通信)出现的;
web service被定义成一组模块化的api,可以通过网络进行远程调用;
web service是一个跨语言、跨平台的远程调用技术;
2. web service使用场景
-
不同公司系统之间的数据交互
注册微信公众号的公司 和 腾讯公司需要交互数据
电商系统和物流公司需要数据交互(查询物流信息)
-
一些公共的数据服务:手机号归属地查询、天气预报服务、股票行情、英文翻译…
在这里插入图片描述
- 同一公司,不同系统(有可能不是同一种开发语言)之间的数据交互
3. web service的三要素
-
WSDL(Web Service Definition/Description Language)
web服务定义/描述语言 1. 用于描述具体服务,定义客户端和服务端之间数据交互时传递的数据格式(请求和响应的数据) 2. 每一个web service对应为一个wsdl文档 3. WSDL可以认为是web service说明书
-
SOAP(Simple Object Access Protocol)
简单对象访问协议,基于Http协议,使用XML传递消息; 它是一种轻量级的通信协议; 用于不同应用之间的通信; 使用Http协议进行通信; 独立于平台、编程语言,基于XML,简单并可扩展
-
UDDI
4. web service规范
-
JAX-WS规范
全称:Java Api For Xml-Based WebService; 早期的时候是叫JAX-RPC(Java Api For Xml-Rmote Procedure Call); JAX-RPC目前已经被JAX-WS取代; 采用WSDL做为服务的描述语言; 采用标准的Soap协议传输xml数据;
-
JAX-RS规范
是Java针对REST风格的请求制定的一套web服务规范; 支持JAX-RS规范的框架 - CXF - RESTEasy :JBOSS的 - RESTLet:比较早的rest框架,比JAX-RS规范还要早 - Jersey
5. 使用jdk方式开发web service
5.1 服务端
-
开发服务接口(interface)
SEI
(Service Endpoint Interface) -
开发服务实现
-
发布web service服务
-
注意:
接口和实现类上上都加上@WebService
5.2 开发jdk方式的客户端
-
根据服务端的wsdl创建客户端代码
jdk提供了创建客户端代码的工具:wsimport
wsimport -keep http://localhost:8080/hello?wsdl
-
发起远程调用
-
测试使用PostMan发起远程服务调用
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<a:sayHello xmlns:a="http://impl.service.etoak.com/">
<arg0>asdasdasdasdasd</arg0>
</a:sayHello>
</soap:Body>
</soap:Envelope>
JdkServer
import com.etoak.service.impl.HelloServiceImpl;
import javax.xml.ws.Endpoint;
public class JdkServer {
public static void main(String[] args) {
Endpoint.publish("http://localhost:8080/hello",new HelloServiceImpl());
System.out.println("Server start");
}
}
jdkClient
import com.etoak.service.impl.HelloServiceImpl;
import com.etoak.service.impl.HelloServiceImplService;
public class jdkClient {
public static void main(String[] args) {
//创建服务视图,视图是从wsdl文件的service标签的name属性获取
HelloServiceImplService soap = new HelloServiceImplService();
//获取服务实现类,实现类从wsdl的proType的name属性获取的
HelloServiceImpl soapPort = soap.getHelloServiceImplPort();
//获取查询方法,从portType的operation标签获取
String result = soapPort.sayHello("ZS");
System.out.println(result);
}
}
6. 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上面。
6.1 下载CXF二进制包
下载地址:http://cxf.apache.org/download.html
6.2 解压到当前目录
工具:cxf框架提供了wsdl2java命令
作用:创建客户端代码
使用方式:wsdl2java -d e:/client http://localhost:9000/user?wsdl
-d参数:表示客户端代码创建到哪个目录中
6.3 maven依赖
maven依赖说明:
http://cxf.apache.org/docs/using-cxf-with-maven.html
<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>
6.4 使用Cxf开发服务端 和客户端
- 开发服务端
- 创建服务接口
- 创建服务实现
- 发布服务
- 开发客户端
- 根据wsdl创建客户端代码
- 调用远程服务
CxfServer
import com.etoak.service.UserService;
import com.etoak.service.impl.UserServiceImpl;
import org.apache.cxf.endpoint.Server;
import org.apache.cxf.jaxws.JaxWsServerFactoryBean;
public class CxfServer {
public static void main(String[] args) {
//创建jaxWSServerFactoryBean
JaxWsServerFactoryBean factoryBean = new JaxWsServerFactoryBean();
//2.设置服务接口(wsdl地址)
factoryBean.setAddress("http://localhost:8080/user");
//3.设置服务接口
factoryBean.setServiceClass(UserService.class);
//4、设置服务实现类
factoryBean.setServiceBean(new UserServiceImpl());
//5、创建并启动服务
Server server = factoryBean.create();
server.start();
System.out.println("Server start");
}
}
CxfClient
import com.etoak.service.User;
import com.etoak.service.UserService;
import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
public class CxfClient {
public static void main(String[] args) {
//1.创建JaxWsProxyFactoryBean
JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
//2.设置服务地址
factory.setAddress("http://localhost:8080/user?wsdl");
//3.设置接口
factory.setServiceClass(UserService.class);
//4.创建服务代理对象
UserService userService = (UserService) factory.create();
//5.调用远程服务
User user = userService.getById(100);
System.out.println(user.getId() + "-" +user.getName());
}
}
7. cxf整合spring web
实现根据id查询用户信息
- 搭建客户端工程
- 创建客户端代码
- 配置spring容器
- 调用远程服务
服务器端
web.xml
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<servlet>
<servlet-name>cxf</servlet-name>
<servlet-class>
org.apache.cxf.transport.servlet.CXFServlet
</servlet-class>
<init-param>
<param-name>config-location</param-name>
<param-value>classpath:spring-cxf.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>cxf</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
spring-cxf.xml
<?xml version="1.0" encoding="UTF-8"?>
<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:context="http://www.springframework.org/schema/context"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xmlns:core="http://cxf.apache.org/core"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd
http://cxf.apache.org/core
http://cxf.apache.org/schemas/core.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd">
<context:component-scan base-package="com.etoak"/>
<!--整合数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="root" />
<property name="password" value="etoak" />
<property name="url" value="jdbc:mysql://localhost:3306/et2004" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
</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" >
<array>
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<props>
<prop key="reasonable">true</prop>
</props>
</property>
</bean>
</array>
</property>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.etoak.mapper"></property>
</bean>
<!--配置服务-->
<!--
JaxWsServiceFactoryBean
设置服务地址
设置服务接口
设置服务实现
创建服务
-->
<!--服务地址:http://ip:port/path/ws/user?wdls-->
<jaxws:server address="/User" serviceClass="com.etoak.service.USerService">
<jaxws:serviceBean>
<bean class="com.etoak.service.impl.UserServiceImpl"></bean>
</jaxws:serviceBean>
</jaxws:server>
</beans>
UserServiceImpl
import com.etoak.bean.User;
import com.etoak.mapper.UserMapper;
import com.etoak.service.USerService;
import org.springframework.beans.factory.annotation.Autowired;
import javax.jws.WebService;
@WebService
public class UserServiceImpl implements USerService {
@Autowired
UserMapper userMapper;
@Override
public User getById(int id) {
return userMapper.getById(id);
}
}
客户端
spring-cxf.xml
<?xml version="1.0" encoding="UTF-8"?>
<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:context="http://www.springframework.org/schema/context"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xmlns:core="http://cxf.apache.org/core"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd
http://cxf.apache.org/core
http://cxf.apache.org/schemas/core.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd">
<context:component-scan base-package="com.etoak"/>
<!--配置服务-->
<!--
//JaxWsServiceFactoryBean
设置服务地址
设置服务接口
创建服务代理对象
-->
<!--远程服务接口的代理对象-->
<jaxws:client address="http://localhost:8080/ws/User" id="userService"
serviceClass="com.etoak.service.USerService">
</jaxws:client>
</beans>
SpringClient
import com.etoak.service.USerService;
import com.etoak.service.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringClient {
public static void main(String[] args) {
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-cxf.xml");
USerService userService = ioc.getBean("userService", USerService.class);
User user = userService.getById(2);
System.out.println(user.getName()+"-"+user.getAge());
}
}
生成客户端代码
8. cxf拦截器
Cxf可以在web service发送前后,动态操作请求和响应的soap报文数据(xml消息);
1.1 拦截器分类
-
拦截位置:服务端拦截、客户端拦截;
-
按消息方向:In拦截器、Out拦截器
客户端发送请求走Out拦截器,结果回来之前走In拦截器;
服务端接收请求先走In拦截器,响应之前走Out拦截器;
-
按定义者:Cxf官方定义的拦截器、自定义拦截器;
1.2 拦截器的几个API
Interceptor、PhaseInterceptor、SoapInterceptor
阶段拦截器:协议化之前、发送前、接收前、执行前、执行后等等
拦截阶段定义在了Phase.java中
1.3 配置和使用官方拦截器
这里以日志拦截器LoggingInInterceptor和LoggingOutInterceptor为例
1.3.1 在服务端配置官方拦截器
<jaxws:server address="/user"
serviceClass="com.etoak.service.UserService">
<jaxws:serviceBean>
<bean class="com.etoak.service.impl.UserServiceImpl" />
</jaxws:serviceBean>
<!-- 服务端配置In拦截器 -->
<jaxws:inInterceptors>
<bean class="org.apache.cxf.interceptor.LoggingInInterceptor" />
</jaxws:inInterceptors>
<!-- 服务端配置Out拦截器 -->
<jaxws:outInterceptors>
<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor" />
</jaxws:outInterceptors>
</jaxws:server>
1.3.2 在客户端配置官方拦截器
<jaxws:client address="http://localhost:8080/ws/User" id="userService"
serviceClass="com.etoak.service.USerService">
<jaxws:inInterceptors>
<bean class="org.apache.cxf.interceptor.LoggingInInterceptor"/>
</jaxws:inInterceptors>
<jaxws:outInterceptors>
<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor"/>
</jaxws:outInterceptors>
</jaxws:client>
1.4 自定义拦截器
1.4.1 实现内容
使用自定义拦截器实现用户验证
注意:自定义拦截器需要继承AbstractPhaseInterceptor
1.4.2 客户端创建、配置(使用)自定义拦截器
-
创建拦截器
package com.etoak.interceptor; import org.apache.cxf.binding.soap.SoapMessage; import org.apache.cxf.headers.Header; import org.apache.cxf.helpers.DOMUtils; import org.apache.cxf.interceptor.Fault; import org.apache.cxf.phase.AbstractPhaseInterceptor; import org.apache.cxf.phase.Phase; import org.w3c.dom.Document; import org.w3c.dom.Element; import javax.xml.namespace.QName; import java.util.List; public class AuthOutInterceptor extends AbstractPhaseInterceptor<SoapMessage> { private String name; private String password; /** * <bean> * <constructor-arg name="name" value="" /> * <constructor-arg name="password" value="" /> * </bean> * @param name * @param password */ public AuthOutInterceptor(String name, String password) { super(Phase.PREPARE_SEND); this.name = name; this.password = password; } /** * 操作Soap消息 * <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> * * <soap:Header> * <et2004> * <name>zs</name> * <password>123</password> * </et2004> * </soap:Header> * * <soap:Body> * <ns2:getById xmlns:ns2="http://service.etoak.com/"> * <arg0>3</arg0> * </ns2:getById> * </soap:Body> * </soap:Envelope> * * @param message * @throws Fault */ @Override public void handleMessage(SoapMessage message) throws Fault { // 先创建Document文档对象 Document doc = DOMUtils.createDocument(); // 创建<et2004>元素 Element et2004 = doc.createElement("et2004"); // 创建<name>元素, 并为<name>添加文本值 Element nameElement = doc.createElement("name"); nameElement.setTextContent(this.name); // 创建<password>元素, 并为<password>添加文本值 Element passwordElement = doc.createElement("password"); passwordElement.setTextContent(this.password); // 为<et2004>元素添加<name>和<password> et2004.appendChild(nameElement); et2004.appendChild(passwordElement); // 为<soap:Header>添加<et2004> List<Header> headers = message.getHeaders(); headers.add(new Header(new QName(""), et2004)); } }
-
配置拦截器
<jaxws:client id="userService" address="http://localhost:8080/ws/user" serviceClass="com.etoak.service.UserService"> <!-- 客户端配置In拦截器 --> <jaxws:inInterceptors> <bean class="org.apache.cxf.interceptor.LoggingInInterceptor" /> </jaxws:inInterceptors> <!-- 客户端配置Out拦截器 --> <jaxws:outInterceptors> <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> <et2004> <name>zs</name> <password>123456</password> </et2004> </soap:Header> <soap:Body> <ns2:getById xmlns:ns2="http://service.etoak.com/"> <arg0>3</arg0> </ns2:getById> </soap:Body> </soap:Envelope>
-
响应的soap报文
1.4.2 服务端创建、配置(使用)自定义拦截器
-
创建拦截器
package com.etoak.interceptor; import org.apache.commons.lang3.StringUtils; import org.apache.cxf.binding.soap.SoapMessage; import org.apache.cxf.headers.Header; import org.apache.cxf.interceptor.Fault; import org.apache.cxf.phase.AbstractPhaseInterceptor; import org.apache.cxf.phase.Phase; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import javax.xml.namespace.QName; public class AuthInInterceptor extends AbstractPhaseInterceptor<SoapMessage> { public AuthInInterceptor() { //拦截阶段 super(Phase.PRE_INVOKE); } /** * 解析xml * <soap:Header> * <et2004> * <name>zs</name> * <password>123456</password> * </et2004> * </soap:Header> * * @param message * @throws Fault */ @Override public void handleMessage(SoapMessage message) throws Fault { // 先获取et2004 Header元素 Header header = message.getHeader(new QName("et2004")); if(header == null) { throw new Fault(new RuntimeException("请传入用户名和密码")); } Element et2004 = (Element)header.getObject(); // 获取name元素节点 NodeList nameNode = et2004.getElementsByTagName("name"); if (nameNode == null || nameNode.getLength() != 1) { throw new Fault(new RuntimeException("name格式不正确")); } // 获取password元素节点 NodeList pwdNode = et2004.getElementsByTagName("password"); if (pwdNode == null || pwdNode.getLength() != 1) { throw new Fault(new RuntimeException("password格式不正确")); } String name = nameNode.item(0).getTextContent(); String password = pwdNode.item(0).getTextContent(); if(StringUtils.equals(name, "zs") && StringUtils.equals(password, "123456")) { System.out.println("验证成功"); return; } else { throw new Fault(new RuntimeException("用户名或密码错误")); } } }
-
配置拦截器
<jaxws:server address="/user" serviceClass="com.etoak.service.UserService"> <jaxws:serviceBean> <bean class="com.etoak.service.impl.UserServiceImpl" /> </jaxws:serviceBean> <!-- 服务端配置In拦截器 --> <jaxws:inInterceptors> <bean class="org.apache.cxf.interceptor.LoggingInInterceptor" /> <bean class="com.etoak.interceptor.AuthInInterceptor" /> </jaxws:inInterceptors> <!-- 服务端配置Out拦截器 --> <jaxws:outInterceptors> <bean class="org.apache.cxf.interceptor.LoggingOutInterceptor" /> </jaxws:outInterceptors> </jaxws:server>
1.5 全局拦截器
全局拦截器可以为所有web service添加拦截
- 引入Schema
-
为所有web service配置全局拦截器
<!-- 全局拦截器,可以为所有的web service设置拦截 --> <core:bus> <core:inInterceptors> <bean class="org.apache.cxf.interceptor.LoggingInInterceptor" /> <bean class="com.etoak.interceptor.AuthInInterceptor" /> </core:inInterceptors> <core:outInterceptors> <bean class="org.apache.cxf.interceptor.LoggingOutInterceptor" /> </core:outInterceptors> </core:bus>
2. 整合springboot
使用cxf提供的cxf-spring-boot-starter-jaxws
2.1 关于CXFServlet配置的拦截地址
-
自动配置的拦截地址
不能以 /* 结尾
cxf:
path: /ws
或者
cxf:
path: /ws/
- 手动配置
@Bean
public ServletRegistrationBean<CXFServlet> cxfServletRegistration() {
ServletRegistrationBean<CXFServlet> servletRegistrationBean =
new ServletRegistrationBean<>();
servletRegistrationBean.setServlet(new CXFServlet());
servletRegistrationBean.addUrlMappings("/ws/*");
return servletRegistrationBean;
}
CxfApp
@SpringBootApplication
public class CxfApp {
public static void main(String[] args) {
SpringApplication.run(CxfApp.class);
}
@Autowired
HelloService helloService;
@Autowired
Bus bus;
//第一种发布服务方式
@Bean
public JaxWsServerFactoryBean helloService(){
JaxWsServerFactoryBean factoryBean = new JaxWsServerFactoryBean();
factoryBean.setAddress("/hello");
factoryBean.setServiceClass(HelloService.class);
factoryBean.setServiceBean(helloService);
factoryBean.getInInterceptors().add(new LoggingInInterceptor());
Server server = factoryBean.create();
server.start();
return factoryBean;
}
//第二种发布服务方式
@Bean
public EndpointImpl endpoint(){
//添加全局拦截 给所有的服务添加out拦截
bus.getOutFaultInterceptors().add(new LoggingOutInterceptor());
EndpointImpl endpoint = new EndpointImpl(bus,helloService);
endpoint.publish("/hello2");
return endpoint;
}
}