目录
环境说明
1、本文使用 Spring4.3.10 集成 Apache CXF 3.3 。
2、亲测 Java JDK 1.8 + Tomcat 9 集成成功(Tomcat 8 时集成失败了),采用手动导入 jar 包的方式,不使用 Maven。
3、将新建两个 Java Web 项目,web_service_spring_s 作为服务端,web_service_spring_c 作为客户端 。
4、使用 IDEA 14 开发。
5、如上所示是已经建好的项目,且运行成功之后的样子,现在解释如下;
beans.xml:spring 核心配置文件,CXF 也会在其中进行配置
springMVC.xml:springMVC 核心配置文件,为了尽可能的和实际项目一致,这里使用 MVC,方便从浏览器访问
UserController:用户控制器层,浏览器访问其中地址,如:http://localhost/web_service_spring_s/getUserById.action
User:用户实体类
UserServiceImpl:UserService 服务接口的实现类
UserService :服务接口,即是服务端的服务层,也会标识为 @WebService 作为 webService 服务接口
web.xml:Java web 应用入口文件,整合其它的配置文件。
index.jsp:默认的主页
服务端
导入 jar 包
1、这里只是演示 Spring 集成 CXF,不使用数据库操作,所以不导入操作数据库的 jar 包
2、官网下载的 Spring4.3.10 lib 目录下一共有 62 个 jar 包,官网下载的 CXF 的 lib 下一共有 182 个 Jar 包,显然全部导入不太现实,因为很多功能根本没有使用。
3、本人采用的方式是先导入 Spring4.3.10 的包,然后配置 beans.xml、springMVC.xml,web.xml,让项目能正常跑起来之后,再导入 CXF 的 jar,再修改配置文件进行整合。
4、使用 Spring 且不操作数据库时,只导入以下的 jar 包即可,下载地址:
链接:https://pan.baidu.com/s/1roPyjx3kPCXIH3sMn5f7pw 提取码:ybof
链接:https://pan.baidu.com/s/1fRjr5U9z22SeN5G0LTJxXw 提取码:oll7
5、当 Spring 可以使用之后,可以再导入 CXF 的 jar包,只要把《Apache CXF 入门第一个示例》中的 Jar 包追加即可,所以整个集成项目导入的 Jar 包汇总如下:
源码展示
1、接下来将内容逐个进行粘贴展示,按照开发是代码的先后顺序进行展示。
User 实体内容如下:
import java.util.Date;
/**
* Created by Administrator on 2019/2/19 0019.
* 用户实体
*/
public class User {
private Integer uId;//用户id
private String uName;//用户姓名
private Date bithday;//用户数生日
private Float price;//用户身价
public Date getBithday() {
return bithday;
}
public void setBithday(Date bithday) {
this.bithday = bithday;
}
public Float getPrice() {
return price;
}
public void setPrice(Float price) {
this.price = price;
}
public Integer getuId() {
return uId;
}
public void setuId(Integer uId) {
this.uId = uId;
}
public String getuName() {
return uName;
}
public void setuName(String uName) {
this.uName = uName;
}
@Override
public String toString() {
return "User{" +
"bithday=" + bithday +
", uId=" + uId +
", uName='" + uName + '\'' +
", price=" + price +
'}';
}
}
UserService 接口内容如下:
import com.lct.www.domain.User;
import javax.jws.WebService;
/**
* Created by Administrator on 2019/2/19 0019.
* 用户接口
*
* @WebService :标识此接口为 webService 接口
*/
@WebService
public interface UserService {
/**
* 根据用户id查询用户信息
*
* @param uId
* @return
*/
public User getUserById(Integer uId);
}
UserServiceImpl 服务接口实现类内容如下:
import com.lct.www.domain.User;
import com.lct.www.service.UserService;
import org.springframework.stereotype.Service;
import javax.jws.WebService;
import java.util.Date;
import java.util.Random;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Created by Administrator on 2019/2/19 0019.
*
* @WebService :标识此类为 webService 服务类, 注意 endpointInterface 属性不用再写,否则报错
*/
@WebService
@Service
public class UserServiceImpl implements UserService {
private static final Logger logger = Logger.getGlobal();//日志记录器
@Override
public User getUserById(Integer uId) {
logger.log(Level.INFO, "根据用户id查询用户信息,uId=" + uId);
//用于演示,则不从数据查询,这里直接造一个假数据返回
User user = new User();
user.setuId(uId);
user.setuName(UUID.randomUUID().toString());
user.setBithday(new Date());
user.setPrice(new Random().nextInt(10000000) * 1.0F);
logger.log(Level.INFO, "查出用户信息为:" + user);
return user;
}
}
UserController 控制器内容如下:
import com.lct.www.service.UserService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Random;
/**
* Created by Administrator on 2019/2/19 0019.
* 用户控制器
*/
@Controller
public class UserController {
@Resource
private UserService userService;
/**
* 根据用户id获取用户信息
* 浏览器访问地址为:http://localhost/web_service_spring_s/getUserById.action
*
* @return 直接将内容打印到页面
*/
@RequestMapping("getUserById.action")
public void getUserById(HttpServletResponse response) {
try {
PrintWriter printWriter = response.getWriter();
printWriter.write(userService.getUserById(new Random().nextInt(1000)).toString());
printWriter.flush();
printWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
配置文件
beasn.xml 内容如下(注意xml元素的命名空间在头信息中都必须存在):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd">
<!-- 包扫描,因为没有操作数据库,所以没有dao层,只有servive层-->
<context:component-scan base-package="com.lct.www.service.impl"/>
<!-- 引入 cxf 的一些核心配置,这些文件文件都在导入的 CXF 核心包中已经提供了的 -->
<import resource="classpath:META-INF/cxf/cxf.xml"/>
<!-- 如果是 2.5.9 这样的老版本 cxf,则还有以下两个文件也要写上,3.3版本时已经没有了,所以不要写-->
<!--<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml"/>-->
<!--<import resource="classpath:META-INF/cxf/cxf-servlet.xml"/>-->
<!-- webService服务终端配置-->
<!-- id:标识此服务终端,唯一,自定义名称即可,因为可以写多个 endpoint,千万注意不要和其它bean的名称冲突,特别是service层 -->
<!-- implementor:服务接口的实现类全路径-->
<!-- address:将来客户端请求 webService url -->
<!-- 相当于Endpoint.publish("http://localhost:8080/web_service_spring_s/userService", UserServiceImpl());-->
<!-- 经实测,address 属性值无法多级,如 /cxf/userService,则只有 /cxf有效,后面的 /userService无效-->
<jaxws:endpoint id="userServiceWebS" implementor="com.lct.www.service.impl.UserServiceImpl"
address="/userService">
</jaxws:endpoint>
</beans>
springMVC.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:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- 对所有的Controller进行包扫描 -->
<context:component-scan base-package="com.lct.www.controller"/>
<!-- 内部资源视图解析器 -->
<bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
web.xml 内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<!-- 1、配置spring容器-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:beans.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 2、配置springMVC-->
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springMVC.xml</param-value>
</init-param>
</servlet>
<!-- 对所有后缀名为 .action的请求进行过滤处理-->
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
<!-- 3、配置中文乱码过滤器-->
<filter>
<filter-name>SpringEncoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>SpringEncoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 注意 CXF 的 url-pattern 一定要和 SpringMVC 的 url-pattern 分离,否则二者的功能只能使用其一-->
<!-- 假设 CXF 的 url-pattern 为 "/*" ,则所有的请求都被 CXF 过滤了,包括那些不是 webService 的服务请求,从而导致- -->
<!-- SpringMVC 应该请求后台控制器的也被当做 webService 请求被处理了-->
<servlet>
<servlet-name>CXFServlet</servlet-name>
<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>CXFServlet</servlet-name>
<url-pattern>/userService/*</url-pattern>
</servlet-mapping>
</web-app>
浏览器访问测试
将生成的 xml 内容粘贴如下:
<?xml version='1.0' encoding='UTF-8'?><wsdl:definitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://impl.service.www.lct.com/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:ns2="http://schemas.xmlsoap.org/soap/http" xmlns:ns1="http://service.www.lct.com/" name="UserServiceImplService" targetNamespace="http://impl.service.www.lct.com/">
<wsdl:import location="http://localhost:8080/web_service_spring_s/userService/userService?wsdl=UserService.wsdl" namespace="http://service.www.lct.com/">
</wsdl:import>
<wsdl:binding name="UserServiceImplServiceSoapBinding" type="ns1:UserService">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="getUserById">
<soap:operation soapAction="" style="document"/>
<wsdl:input name="getUserById">
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output name="getUserByIdResponse">
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="UserServiceImplService">
<wsdl:port binding="tns:UserServiceImplServiceSoapBinding" name="UserServiceImplPort">
<soap:address location="http://localhost:8080/web_service_spring_s/userService/userService"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
客户端
1、服务端完成之后,客户端则简单的多了,可以使用 Java JDK 自带的 wsimport.exe 工具生成代理类,也可以使用 CXF 的 wsdl2java。
2、客户端如果是 Java SE 项目,则调用完全和以前的一样,可以参考《Apache CXF 入门第一个示例》,一是导入代理类,然后生成对象,调用方法即可,连 CXF 的包都可以不用导入。
UserServiceImplService userServiceImplService = new UserServiceImplService();
UserService userService = userServiceImplService.getUserServiceImplPort();
System.out.println(userService.getUserById(1));
3、所以这里重点介绍CXF 和 Spring 集成,因为既然使用了 Spring,显然代理类接口就应该交由 Spring 容器来管理,这样后期便可以直接获取然后。
4、同样是 Jdk8+Tomcat9,Spring4.3.10 集成 Apache CXF 3.3,新建 Java Web 项目,web_service_spring_c,导入的 Jar 包和服务端完全一致,整体结构如下。
5、如上所示,演示的流程是,应用启动后,从浏览器访问 UserControl 中的方法,然后使用 UserService 直接调用 webService 服务端,因为不走自己的数据库,所以很简洁。
wsdl2java 生成代理类
1、无论是 Java JDK 自带的 wsimport.exe 工具还是 CXF 的 wsdl2java都是可以的,这里就使用 wsdl2java。
2、cmd 先进入 CXF 的 bin 目录下,然后使用 wsdl2java 工具,-d:表示代理类生成后存放的地方,-encoding:表示编码,-verbose:表示显示过程信息。生成之后将它们全部导入到项目中,包路径使用默认的路径,也就是和服务端保持一致。
PS E:\Study_Note\apache-cxf-3.3.0\bin> .\wsdl2java -d E:/wmx/webservice/cxf4 -encoding UTF-8 -verbose http://localhost:8080/web_service_spring_s/userService/userService?wsdl
3、再重复一句,导入 jar 包完全和服务端时一致,这里不再贴图。
源码内容
1、将所有源码内容逐个粘贴如下,beans.xml 内容如下,CXF 和 Spring 集成主要就是里面的那一句:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd">
<!-- jaxws:client 这里 spring会给它自动进行实例化,为 UserService 接口创建实例,后期自己使用@Resource获取使用即可-->
<!-- id:bean 的唯一标识,使用接口名首字母小写即可;serviceClass:服务接口的全路径,会交由spring容器管理,实例化-->
<!-- address:webService 服务端 wsdl 的访问路径,注意这里结尾不用加 "?wsdl",以后可以直接调用 serviceClass 中的方法-->
<jaxws:client id="userService"
serviceClass="com.lct.www.service.UserService"
address="http://localhost:8080/web_service_spring_s/userService/userService">
</jaxws:client>
</beans>
2、这是 SpringMVC 的内容,配置控制器包扫描,以及视图解析器,springMVC.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:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- 对所有的Controller进行包扫描 -->
<context:component-scan base-package="com.lct.www.controller"/>
<!-- 内部资源视图解析器 -->
<bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
3、web.xml内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<!-- 1、配置spring容器-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:beans.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 2、配置springMVC-->
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springMVC.xml</param-value>
</init-param>
</servlet>
<!-- 对所有后缀名为 .action的请求进行过滤处理-->
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
<!-- 3、配置中文乱码过滤器-->
<filter>
<filter-name>SpringEncoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>SpringEncoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
4、UserController内容如下:
import com.lct.www.service.UserService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* Created by Administrator on 2019/2/19 0019.
* 用户控制器
*/
@Controller
public class UserController {
//生成的代理类,也是接口和实现类的结构,已经交由了Spring容器管理,所以直接取值即可
@Resource
private UserService userService;
/**
* 根据用户id获取用户信息
* 浏览器访问地址为:http://localhost/web_service_spring_s/getUserById.action
*
* @return 直接将内容打印到页面
*/
@RequestMapping("getUserById.action")
public void getUserById(HttpServletResponse response) {
try {
PrintWriter printWriter = response.getWriter();
printWriter.write(userService.getUserById(1).toString());
printWriter.flush();
printWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
浏览器访问测试
服务端控制台日志输出如下:
20-Feb-2019 15:49:03.766 信息 [http-nio-8080-exec-10] com.lct.www.service.impl.UserServiceImpl.getUserById 根据用户id查询用户信息,uId=1
20-Feb-2019 15:49:03.767 信息 [http-nio-8080-exec-10] com.lct.www.service.impl.UserServiceImpl.getUserById 查出用户信息为:User{bithday=Wed Feb 20 15:49:03 CST 2019, uId=1, uName='e5473511-98d6-4edc-afb2-b331702f9217', price=9381686.0}
配置拦截器
1、《Apache CXF 拦截器示例》中的使用代码添加的拦截器,这里改为在 beans.xml 文件中进行配置,拦截器的内容是一样的,仅仅是配置不同。
服务端
1、这里只粘贴修改过的的内容,WsUserInterceptor 内容如下:
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 javax.xml.namespace.QName;
import java.util.logging.Logger;
/**
* Created by Administrator on 2019/2/18 0018.
* 自定义拦截器——————用于验证客户端的账号密码
*/
public class WsUserInterceptor extends AbstractPhaseInterceptor<SoapMessage> {
private static final Logger logger = Logger.getGlobal();//日志记录器
/**
* 构造器中必须使用 super 设置拦截器发生的时刻/阶段
* org.apache.cxf.phase.Phase 中提供了大量的常量表示拦截器发生的时刻
*/
public WsUserInterceptor() {
super(Phase.PRE_PROTOCOL);//协议前进行拦截
}
/**
* 客户端传来的 soap 消息先进入拦截器这里进行处理,客户端的账目与密码消息放在 soap 的消息头<head></head>中,类似如下:
* <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Header><userInfo><name>admin</name><password>123456</password></userInfo></soap:Header><soap:Body><ns2:getStudentById xmlns:ns2="http://web_service.lct.com/"><sId>11</sId></ns2:getStudentById></soap:Body></soap:Envelope>
* 现在只需要解析其中的 <head></head>标签,如果解析验证成功,则放行,否则这里直接抛出异常,服务端不会再往后运行,客户端也会跟着抛出异常,得不到正确结果
*
* @param message
* @throws Fault
*/
@Override
public void handleMessage(SoapMessage message) throws Fault {
/**org.apache.cxf.headers.Header
* QName :xml 限定名称,客户端设置头信息时,必须与服务器保持一致,否则这里返回的 header 为null,则永远通不过的
*/
Header header = message.getHeader(new QName("userInfo"));
if (header != null) {
Element rootEle = (Element) header.getObject();//获取根元素,即 <userInfo>标签
String name = rootEle.getElementsByTagName("name").item(0).getTextContent();//获取元素值
String password = rootEle.getElementsByTagName("password").item(0).getTextContent();//获取元素值
/**为了演示简单,直接写死了,实际中建议放在配置文件中提供配置*/
if ("admin".equals(name) && "123456".equals(password)) {
logger.info("webService 服务端自定义拦截器验证通过....");
return;//放行
}
}
/**
* 验证失败,客户端没有提供账号密码、或者提供账号密码错误
*/
logger.info("webService 服务端自定义拦截器验证失败....");
throw new Fault(new RuntimeException("请提供正确的用户名和密码!格式:<Header><userInfo><name>xxx</name><password>xxx</password></userInfo></Header>"));
}
}
2、beans.xml 中进行配置拦截器,只粘贴修改的部分内容:
<!-- 引入 cxf 的一些核心配置,这些文件文件都在导入的 CXF 核心包中已经提供了的 -->
<import resource="classpath:META-INF/cxf/cxf.xml"/>
<!-- 如果是 2.5.9 这样的老版本 cxf,则还有以下两个文件也要写上,3.3版本时已经没有了,所以不要写-->
<!--<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml"/>-->
<!--<import resource="classpath:META-INF/cxf/cxf-servlet.xml"/>-->
<!-- webService服务终端配置-->
<!-- id:标识此服务终端,唯一,自定义名称即可,因为可以写多个 endpoint,千万注意不要和其它bean的名称冲突,特别是service层 -->
<!-- implementor:服务接口的实现类全路径-->
<!-- address:将来客户端请求 webService url -->
<!-- 相当于Endpoint.publish("http://localhost:8080/web_service_spring_s/userService", UserServiceImpl());-->
<!-- 经实测,address 属性值无法多级,如 /cxf/userService,则只有 /cxf有效,后面的 /userService无效-->
<jaxws:endpoint id="userServiceWebS" implementor="com.lct.www.service.impl.UserServiceImpl"
address="/userService">
<!-- 配置 CXF 入拦截器-->
<jaxws:inInterceptors>
<!-- LoggingInInterceptor:这是 CXF 默认提供的日志入拦截器,在开发调试阶段有用,部署阶段可以删除-->
<bean class="org.apache.cxf.interceptor.LoggingInInterceptor"/>
<!-- WsUserInterceptor:自定义的入拦截器,用于账号密码验证-->
<bean class="com.lct.www.interceptors.WsUserInterceptor"/>
</jaxws:inInterceptors>
</jaxws:endpoint>
3、先写拦截器,再配置拦截器,其余内容不变。
客户端
1、WsUserInterceptor 内容如下:
import com.sun.org.apache.xml.internal.utils.DOMHelper;
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.Document;
import org.w3c.dom.Element;
import javax.xml.namespace.QName;
import java.util.List;
import java.util.logging.Logger;
/**
* Created by Administrator on 2019/2/18 0018.
* 自定义 webService 拦截器——————用于提供请求的账号与密码
*/
public class WsUserInterceptor extends AbstractPhaseInterceptor<SoapMessage> {
private static final Logger logger = Logger.getGlobal();//日志记录器
/**
* webService 需要传递的账号与密码
*/
private String name;
private String password;
/**
* 使用构造器传入参数,构造器中必须使用 super 设置拦截器发生的时刻/阶段
* org.apache.cxf.phase.Phase 中提供了大量的常量表示拦截器发生的时刻
*
* @param name
* @param password
*/
public WsUserInterceptor(String name, String password) {
super(Phase.PRE_PROTOCOL);//协议前进行拦截
this.name = name;
this.password = password;
}
/**
* 默认情况下,客户端给服务端请求的 soap 消息如下:
* <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><ns2:getStudentByIdResponse xmlns:ns2="http://web_service.lct.com/"><getStudentById><birthday>2019-02-18T16:19:12.102+08:00</birthday><punishStatus>5</punishStatus><sID>11</sID><sName>0a90959c-22ee-497b-b60d-0550b1f2adf7</sName></getStudentById></ns2:getStudentByIdResponse></soap:Body></soap:Envelope>
* 其中的 <body></body>部分为请求的主体,现在为 Envelope 设置头信息 <head></head>,将账户密码信息放在 <head></head>中,<head>与 body 同级
* <head></head>中的内容设置为账户与密码元素,如:
* <head><userInfo><name>admin</name><password>123456</password></userInfo></head>,其中的 userInfo、name、password 等元素名称必须和服务端约定的一致,
* 因为服务端还需要根据这些名称解析出元素的值
*
* @param message :在客户端请求服务端前,先进入此方法,然后将账户密码信息添加到 soap 消息头中
* @throws Fault
*/
@Override
public void handleMessage(SoapMessage message) throws Fault {
/**
* com.sun.org.apache.xml.internal.utils.DOMHelper
* org.w3c.dom.Document
* org.w3c.dom.Element
*/
Document document = DOMHelper.createDocument();//创建 w3c 的文档对象
Element userInfoEle = document.createElement("userInfo");//创建标签元素
Element nameEle = document.createElement("name");//创建标签元素
Element passwordEle = document.createElement("password");//创建标签元素
nameEle.setTextContent(this.name);//设置标签元素内容
passwordEle.setTextContent(this.password);//设置标签元素内容
userInfoEle.appendChild(nameEle);//添加子元素
userInfoEle.appendChild(passwordEle);//添加子元素
/**
* org.apache.cxf.headers.Header
* 最后将创建好的 soap 头信息添加到 SoapMessage 中
*/
List<Header> headerList = message.getHeaders();
/**QName构造器中的值与后面的 userInfoEle 元素的标签名保持一致*/
headerList.add(new Header(new QName("userInfo"), userInfoEle));
logger.info("客户端 webServic 自定义出拦截器执行完毕....");
}
}
2、beans.xml 文件中配置拦截器,与服务端配置完全一致,仅将变动的内容粘贴如下:
<!-- jaxws:client 这里 spring会给它自动进行实例化,为 UserService 接口创建实例,后期自己使用@Resource获取使用即可-->
<!-- id:bean 的唯一标识,使用接口名首字母小写即可;serviceClass:服务接口的全路径,会交由spring容器管理,实例化-->
<!-- address:webService 服务端 wsdl 的访问路径,注意这里结尾不用加 "?wsdl",以后可以直接调用 serviceClass 中的方法-->
<jaxws:client id="userService"
serviceClass="com.lct.www.service.UserService"
address="http://localhost:8080/web_service_spring_s/userService/userService">
<!-- 出拦截器配置-->
<jaxws:outInterceptors>
<!--LoggingOutInterceptor 是CXF自身提供的日志出拦截器,直接使用即可 -->
<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor"/>
<!--WsUserInterceptor:自定义的拦截器,用于提供验证账号与密码,通过构造器赋值 -->
<bean class="com.lct.www.interceptors.WsUserInterceptor">
<constructor-arg name="name" value="admin"/>
<constructor-arg name="password" value="123456"/>
</bean>
</jaxws:outInterceptors>
</jaxws:client>
3、其余内容不变。
访问测试
1、服务端使用自定义拦截器要求客户端调用时提供账号密码,账号:admin,密码:123456。
2、这里测试时先使用正确的账号密码进行调用,然后使用错误的账号 root 进行调用。
账号:admin,密码:123456 调用时如下:
1)此时客户端控制打印日志如下,可以看到<head>头信息中正确的的账号与密码:
---------------------------
ID: 1
Address: http://localhost:8080/web_service_spring_s/userService/userService
Encoding: UTF-8
Http-Method: POST
Content-Type: text/xml
Headers: {Accept=[*/*], SOAPAction=[""]}
Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Header><userInfo><name>admin</name><password>123456</password></userInfo></soap:Header><soap:Body><ns2:getUserById xmlns:ns2="http://service.www.lct.com/"><arg0>1</arg0></ns2:getUserById></soap:Body></soap:Envelope>
--------------------------------------
2)此时服务端控制端输出信息如下:
----------------------------
ID: 1
Address: http://localhost:8080/web_service_spring_s/userService/userService
Encoding: UTF-8
Http-Method: POST
Content-Type: text/xml; charset=UTF-8
Headers: {Accept=[*/*], cache-control=[no-cache], connection=[keep-alive], Content-Length=[291], content-type=[text/xml; charset=UTF-8], host=[localhost:8080], pragma=[no-cache], SOAPAction=[""], user-agent=[Apache-CXF/3.3.0]}
Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Header><userInfo><name>admin</name><password>123456</password></userInfo></soap:Header><soap:Body><ns2:getUserById xmlns:ns2="http://service.www.lct.com/"><arg0>1</arg0></ns2:getUserById></soap:Body></soap:Envelope>
--------------------------------------
20-Feb-2019 16:26:00.535 信息 [http-nio-8080-exec-7] com.lct.www.interceptors.WsUserInterceptor.handleMessage webService 服务端自定义拦截器验证通过....
20-Feb-2019 16:26:00.629 信息 [http-nio-8080-exec-7] com.lct.www.service.impl.UserServiceImpl.getUserById 根据用户id查询用户信息,uId=1
20-Feb-2019 16:26:00.629 信息 [http-nio-8080-exec-7] com.lct.www.service.impl.UserServiceImpl.getUserById 查出用户信息为:User{bithday=Wed Feb 20 16:26:00 CST 2019, uId=1, uName='e26e2162-09a3-4de1-b1b0-1eb66ff4128e', price=7310311.0}
账号:root,密码:123456 调用时如下:
服务端控制台日志信息如下:
----------------------------
ID: 2
Address: http://localhost:8080/web_service_spring_s/userService/userService
Encoding: UTF-8
Http-Method: POST
Content-Type: text/xml; charset=UTF-8
Headers: {Accept=[*/*], cache-control=[no-cache], connection=[keep-alive], Content-Length=[290], content-type=[text/xml; charset=UTF-8], host=[localhost:8080], pragma=[no-cache], SOAPAction=[""], user-agent=[Apache-CXF/3.3.0]}
Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Header><userInfo><name>root</name><password>123456</password></userInfo></soap:Header><soap:Body><ns2:getUserById xmlns:ns2="http://service.www.lct.com/"><arg0>1</arg0></ns2:getUserById></soap:Body></soap:Envelope>
--------------------------------------
20-Feb-2019 16:29:25.225 信息 [http-nio-8080-exec-2] com.lct.www.interceptors.WsUserInterceptor.handleMessage webService 服务端自定义拦截器验证失败....
20-Feb-2019 16:29:25.229 警告 [http-nio-8080-exec-2] org.apache.cxf.phase.PhaseInterceptorChain.doDefaultLogging Interceptor for {http://impl.service.www.lct.com/}UserServiceImplService has thrown exception, unwinding now
org.apache.cxf.interceptor.Fault: 请提供正确的用户名和密码!格式:<Header><userInfo><name>xxx</name><password>xxx</password></userInfo></Header>
at com.lct.www.interceptors.WsUserInterceptor.handleMessage(WsUserInterceptor.java:57)
测试成功。
Github 服务端源码地址:https://github.com/wangmaoxiong/web_service_spring_s
Github 客户端源码地址:https://github.com/wangmaoxiong/web_service_spring_c