WebService的CXF实现
Web Service技术, 能使得运行在不同机器上的不同应用无须借助附加的、专门的第三方软件或硬件, 就可相互交换数据或集成 。一种跨编程语言和跨操作系统平台的远程调用技术。
XML、SOAP和WSDL就是构成Web Service平台的三大技术。
- Web Service采用Http协议来在客户端和服务端之间传输数据。WebService使用XML来封装数据,XML主要的优点在于它是跨平台的。
- Web Service通过HTTP协议发送请求和接收结果时,发送的请求内容和结果内容都采用XML格式封装,并增加了一些特定的HTTP消息头,以说明HTTP消息的内容格式,这些特定的HTTP消息头和XML内容格式就是SOAP协议规定的。
- Web Service服务器端首先要通过一个WSDL文件来说明自己有什么服务可以对外调用。简单的说,WSDL就像是一个说明书,用于描述Web Service及其方法、参数和返回值。 WSDL文件保存在Web服务器上,通过一个URL地址就可以访问到它。客户端要调用一个Web Service服务之前,要知道该服务的WSDL文件的地址。Web Service服务提供商可以通过两种方式来暴露它的WSDL文件地址:1.注册到UDDI服务器,以便被人查找;2.直接告诉给客户端调用者。
SOAPMessage的结构:
SOAP消息创建的几个步骤:
MessageFactory mFactory = MessageFactory.newInstance();
//2、根据消息工厂创建SOAP消息
SOAPMessage message = mFactory.createMessage();
//3、创建soappart
SOAPPart part = message.getSOAPPart();
//4、获得SOAP信封SOAPEnvelope
SOAPEnvelope envelope = part.getEnvelope();
//5、可以通过SoapEnvelope有效的获取相应的body和header
SOAPBody body = envelope.getBody();
//6、根据QName创建相应的节点(QName就是一个带有命名空间的节点)
//<ns:add xmlns="http://service.soap.org/">
QName qname = new QName("http://service.soap.org/", "add", "ns");
//可通过以下方式给消息体赋值
SOAPBodyElement ele = body.addBodyElement(qname);
ele.addChildElement("a").setValue("3");
ele.addChildElement("b").setValue("5");
//打印消息信息
message.writeTo(System.out);
@WebService
- serviceName:对外发布的服务名,指定Web Servcice的服务名称:
wsdl:service
,缺省默认为 Java类的简单名称+Service。 - endpointlnterface:服务接口全路径,指定做SEI( Service EndPoint Interface )服务端点接口。
- name:此属性的值包含XML Web Service的名称。默认情况下,该值是XML Web Service类的名称。
- portName:
wsdl:portName
,默认为 WebService.name+Port 。 - targetNamespace:指定你想要的名称空间,认是使用接口实现类的包名的反缀,**结尾需要加上 / **
- wsdlLocation:指定用于定义 Web Service 的 WSDL 文档的 Web 地址。Web 地址可以是相对路径或绝对路径。
@WebMethod
注释表示作为一项 Web Service 操作的方法,将此注释应用于客户机或服务器服务端点接口(SEI)上的方法,或者应用于 JavaBeans 端点的服务器端点实现类。 仅支持在使用 @WebService 注释来注释的类上使用 @WebMethod 注释
- operationName:指定与此方法相匹配的wsdl:operation的名称。默认为java方法的名称。
- action:定义此操作的行为,对于SOAP绑定,此值将确定SOAPAction头的值。默认为java方法的名称。
- exclude:指定是否从 Web Service中排除某一方法。默认为false。
@OneWay
将一个方法表示为只有输入消息而没有输出消息的webService单向操作,将此注释应用于客户机或服务器服务端点接口(SEI)上的方法,或者应用于javaBeans端点的服务器端点实现类。
@WebParam
用于定制从单个参数至web Service消息部件和XML元素的映射。
将此注释应用于客户机或服务器服务端点接口(SEI)上的方法,或者应用于 JavaBeans 端点的服务器端点实现类 。
-
name:参数名称,如果操作是远程调用RPC类型并且未指定partName属性,那么这些是用于表示参数的wsdl:part属性的名称。
如果操作是文档类型或者参数映射至某个头,那么 -name 是用于表示该参数的 XML 元素的局部名称。如果操作是文档类型、
参数类型为 BARE 并且方式为 OUT 或 INOUT,那么必须指定此属性。(字符串)
-
partName:定义用于表示此参数的 wsdl:part属性的名称。仅当操作类型为 RPC 或者操作是文档类型并且参数类型为BARE 时才使用此参数。(字符串)
-
targetNamespace:指定参数的 XML 元素的 XML 名称空间。当属性映射至 XML 元素时,仅应用于文档绑定。缺省值为 Web Service 的 targetNamespace。(字符串)
-
mode:此值表示此方法的参数流的方向。有效值为 IN、INOUT 和 OUT。(字符串)
-
header:指定参数是在消息头还是消息体中。缺省值为 false。(布尔值)
@WebResult
注释用于定制从返回值至 WSDL 部件或 XML 元素的映射。将此注释应用于客户机或服务器服务端点接口(SEI)上的方法,或者应用于 JavaBeans 端点的服务器端点实现类。
-
name:当返回值列示在 WSDL 文件中并且在连接上的消息中找到该返回值时,指定该返回值的名称。对于 RPC 绑定,这是用于表示返回值的 wsdl:part属性的名称。对于文档绑定,-name参数是用于表示返回值的 XML 元素的局部名。对于 RPC 和 DOCUMENT/WRAPPED 绑定,缺省值为 return。对于 DOCUMENT/BARE 绑定,缺省值为方法名 + Response。(字符串)
-
targetNamespace:指定返回值的 XML 名称空间。仅当操作类型为 RPC 或者操作是文档类型并且参数类型为 BARE 时才使用此参数。(字符串)
-
header:指定头中是否附带结果。缺省值为false。(布尔值)
-
partName:指定 RPC 或 DOCUMENT/BARE 操作的结果的部件名称。缺省值为@WebResult.name。(字符串)
@HandlerChain
注释用于使 Web Service 与外部定义的处理程序链相关联。只能通过对 SEI 或实现类使用 @HandlerChain 注释来配置服务器端的处理程序。
但是可以使用多种方法来配置客户端的处理程序。可以通过对生成的服务类或者 SEI 使用 @HandlerChain 注释来配置客户端的处理程序。此外,可以按程序在服务上注册您自己的 HandlerResolver 接口实现,或者按程序在绑定对象上设置处理程序链。
-
file:指定处理程序链文件所在的位置。文件位置可以是采用外部格式的绝对 java.net.URL,也可以是类文件中的相对路径。(字符串)
-
name:指定配置文件中处理程序链的名称。
AbstractPhaseInterceptor拦截器
CXF拦截器是功能的主要实现单元,也是主要的扩展点,可以在不对核心模块进行修改的情况下,动态添加功能。当服务被调用时,会经过多个拦截器链(Interceptor Chain)处理,拦截器链在服务输入(IN)或输出(OUT)阶段实现附加功能,拦截器可以在客户端加入,也可以在服务端加入。
拦截器有多个阶段,每个阶段都有多个拦截器。拦截器在哪个阶段起作用,可以在拦截器的 构造函数 中声明。
基本实现
pom依赖(注意版本)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
<!-- 集成cxf -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-spring-boot-starter-jaxws</artifactId>
<version>3.2.4</version>
</dependency>
<!-- 服务端:用来生成JSONArray -->
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>
<classifier>jdk15</classifier>
<version>2.4</version>
</dependency>
<!-- 客户端:用来解析JSONArray -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.6.2</version>
</dependency>
application.properties配置
webservices.username=admin
webservices.password=root
webservices.service.endpoint=/user
webservices.service.prefix=/mySoap
service方法接口类
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
@WebService
public interface IMessageService {
@WebMethod
public String getMessage(@WebParam(name = "param") String param);
}
service方法实现类
package com.cxfwebserver.demo.service;
import net.sf.json.JSONArray;
import javax.jws.WebService;
import java.util.*;
@WebService(name = "textCXF",targetNamespace = "http://service.demo.cxfwebserver.com/",
endpointInterface = "com.cxfwebserver.demo.service.IMessageService")
public class MessageServiceImpl implements IMessageService{
@Override
public String getMessage(String param) {
List<Map<String, String>> list = new ArrayList<>();
Map<String, String> messaryMap1 = new HashMap<>();
messaryMap1.put("param", param);
messaryMap1.put("name", "张三");
messaryMap1.put("sex", "男");
messaryMap1.put("age", "20");
Map<String, String> messaryMap2 = new HashMap<String, String>();
messaryMap2.put("param", param);
messaryMap2.put("name", "李四");
messaryMap2.put("sex", "女");
messaryMap2.put("age", "18");
list.add(messaryMap1);
list.add(messaryMap2);
// 返回JSON数组字符串
return JSONArray.fromObject(list).toString();
}
}
CXF客户端拦截器
package com.cxfwebserver.demo.filter;
import java.util.List;
import javax.xml.namespace.QName;
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;
public class ClientInterceptor extends AbstractPhaseInterceptor<SoapMessage> {
private String username;
private String password;
public ClientInterceptor(String username, String password) {
// 发送请求之前进行拦截
super(Phase.PREPARE_SEND);
this.username = username;
this.password = password;
}
@Override
public void handleMessage(SoapMessage soapMessage) throws Fault {
List<Header> headers = soapMessage.getHeaders();
Document doc = DOMUtils.createDocument();
Element auth = doc.createElement("authrity");
Element username = doc.createElement("username");
Element password = doc.createElement("password");
username.setTextContent(this.username);
password.setTextContent(this.password);
auth.appendChild(username);
auth.appendChild(password);
headers.add(0, new Header(new QName("tiamaes"), auth));
}
}
CXF服务端拦截器
package com.cxfwebserver.demo.filter;
import org.apache.cxf.binding.soap.SoapHeader;
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.springframework.util.StringUtils;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import javax.xml.soap.SOAPException;
import java.util.List;
public class AuthInterceptor extends AbstractPhaseInterceptor<SoapMessage> {
String USERNAME;
String PASSWORD;
public AuthInterceptor(String username,String password) {
//定义在什么阶段拦截,pre-protocol
super(Phase.PRE_PROTOCOL);
this.PASSWORD = password;
this.USERNAME = username;
}
@Override
public void handleMessage(SoapMessage soapMessage) throws Fault {
String username = null;
String password = null;
List<Header> headers = soapMessage.getHeaders();
if (headers == null){
throw new Fault(new IllegalAccessException("header未找到,无法验证"));
}
// 获取客户端传递的用户名和密码
for (Header header : headers) {
SoapHeader soapHeader = (SoapHeader) header;
Element e = (Element) soapHeader.getObject();
NodeList usernameNode = e.getElementsByTagName("username");
NodeList passwordNode = e.getElementsByTagName("password");
username = usernameNode.item(0).getTextContent();
password = passwordNode.item(0).getTextContent();
if(StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
throw new Fault(new IllegalArgumentException("用户信息为空!"));
}
}
// 校验客户端用户名密码是否和服务端一致
if(!(username.equals(USERNAME) && password.equals(PASSWORD))) {
throw new Fault(new SOAPException("用户信息认证失败!"));
}
}
}
配置类CXFConfig
package com.cxfwebserver.demo.config;
import javax.xml.ws.Endpoint;
import com.cxfwebserver.demo.filter.AuthInterceptor;
import com.cxfwebserver.demo.service.IMessageService;
import com.cxfwebserver.demo.service.MessageServiceImpl;
import org.apache.cxf.Bus;
import org.apache.cxf.bus.spring.SpringBus;
import org.apache.cxf.jaxws.EndpointImpl;
import org.apache.cxf.transport.servlet.CXFServlet;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CXFConfig {
/**
* 从配置文件application.properties中获取参数
*/
@Value("${webservices.service.prefix}")
private String prefix;
@Value("${webservices.service.endpoint}")
private String endpoint;
@Value("${webservices.username}")
private String username;
@Value("${webservices.password}")
private String password;
/**
* 作用:改变服务名的前缀名为prefix的值,默认是services
* 此方法被注释后:wsdl访问(默认)地址为http://127.0.0.1:8080/services/user?wsdl
* 去掉注释后:wsdl访问地址为:http://127.0.0.1:8080/mySoap/user?wsdl
*
* @return
*/
@SuppressWarnings("all")
@Bean(name = "cxfServlet") //指定bean的名称,防止扫描时没有被加载
public ServletRegistrationBean dispatcherServlet() {
return new ServletRegistrationBean(new CXFServlet(), prefix + "/*"); //指定前缀后面必须跟上 /*
}
@Bean(name = Bus.DEFAULT_BUS_ID)
public SpringBus springBus() {
return new SpringBus();
}
//注入要调用的方法接口类
@Bean
public IMessageService messageServiceImpl() {
return new MessageServiceImpl();
}
@Bean
public Endpoint endpoint() {
EndpointImpl endpointImpl = new EndpointImpl(springBus(), messageServiceImpl());
// 服务端添加自定义拦截器:用户密码
endpointImpl.getInInterceptors().add(new AuthInterceptor(username, password));
endpointImpl.publish(endpoint); //endpoint访问路径 /user
return endpointImpl;
}
}
客户端连接
package com.cxfwebserver.demo;
import com.cxfwebserver.demo.filter.ClientInterceptor;
import com.google.gson.Gson;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public class ClientMain {
private static String address = "http://localhost:8080/mySoap/user?wsdl";
@SuppressWarnings("all")
public static void main(String[] args) {
// 创建动态客户端
JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance();
Client client = dcf.createClient(address);
// 添加用户信息验证
client.getOutInterceptors().add(new ClientInterceptor("admin", "root"));
// 取返回值
Object[] objects = new Object[0];
try {
// 接口方法、参数
objects = client.invoke("getMessage", "clientParam");
Map[] maps = new Gson().fromJson(objects[0].toString(),Map[].class);
List<Map<String, String>> list = Arrays.asList(maps);
System.out.println("返回的数据:");
System.out.println(list);
} catch (Exception e) {
System.out.println("客户端接口访问失败!!");
e.printStackTrace();
}
}
}