您的位置:首页 > 编程语言 > Java开发

CXF-09: CXF 与 Spring 的整合 之 暴露 Web Service 接口

2016-10-27 22:53 881 查看
         CXF 与 Spring 的整合:可以在传统的 Java EE 应用的基础上添加一层 Web Service 层,我们的 Java EE 应用就可以对外暴露成 Web Service,这样就允许任何平台、任何语言编写的程序来调用这个 Java EE 应用;

          


 * 在传统Spring项目基础上增加 Web Service 的步骤(SSH 已经配置过,SSM 未配过):

        1 . 复制 CXF 的 jar 包(最核心的6个), jar 包地址 http://pan.baidu.com/s/1jHJ56BW
  注:请选择性复制 (Apache官网上也可以下载);

asm-3.3.jar    commons-logging-1.1.1.jar    cxf-2.4.1.jar    neethi-3.0.0.jar    wsdl4j-1.6.2.jar    xmlschema-core-2.0.jar

        2 . 在 web.xml 配置CXF的核心控制器:CXFServlet;

        3 . 在 Spring 配置文件中导入 CXF 提供 Schema、XML(cxf.jar 包里提供)
                * Schema 文件
                        <beans xmlns:jaxws="http://cxf.apache.org/jaxws" 
                                xsi:schemaLocation="
                                     
  http://cxf.apache.org/jaxws //命名空间
                                     
  http://cxf.apache.org/schemas/jaxws.xsd">//物理路径
                * XML 的配置文件
[b]                        <import resource="classpath:META-INF/cxf/cxf.xml"/>
[b]                        
<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml"/>
                        <import resource="classpath:META-INF/cxf/cxf-servlet.xml"/>[/b]

        4 . 在 Spring 配置文件中使用 jaxws:endpoint 元素类暴露 Web Service;
[/b]
        5 . 添加拦截器,在 jaxws:endpoint 元素里添加 inInterceptors、outInterceptors 子元素;

 * 代码有时比语言更有说服力,新建一个 Web Project 项目代码如下:

(1)、服务器端:

          




①Food.java



public class Food {
private Integer id;
private String name;
private String describe;
public Food() {
super();
}
public Food(Integer id, String name, String describe) {
super();
this.id = id;
this.name = name;
this.describe = describe;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescribe() {
return describe;
}
public void setDescribe(String describe) {
this.describe = describe;
}
}


②User.java



public class User {
private Integer id;
private String name;
private String tel;
private String describe;
public Integer getId() {
return id;
}
public User() {
super();
}
public User(Integer id, String name, String tel, String describe) {
super();
this.id = id;
this.name = name;
this.tel = tel;
this.describe = describe;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTel() {
return tel;
}
public void setTel(String tel) {
this.tel = tel;
}
public String getDescribe() {
return describe;
}
public void setDescribe(String describe) {
this.describe = describe;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((describe == null) ? 0 : describe.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
User other = (User) obj;
if (describe == null) {
if (other.describe != null)
return false;
} else if (!describe.equals(other.describe))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}


③UserService.java



import java.util.List;
import java.util.Map;
import org.fjava.cxf.model.Food;
import org.fjava.cxf.model.User;
public interface UserService {
List<Food> getFoodsByUser(User user);
Map<String, Food> getAllFoods();
}


[b]④UserServiceImpl.java[/b]



import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.fjava.cxf.model.Food;
import org.fjava.cxf.model.User;
public class UserServiceImpl implements UserService {
//为简单,不写Dao层,用一个HashMap来模拟内存中的数据库
static Map<User, List<Food>> foodDb = new HashMap<>();
static{
List<Food> catList1 = new ArrayList<Food>();
catList1.add(new Food(1 , "一个汉堡" , "是三层的,有夹层哦!"));
catList1.add(new Food(2 , "火腿肠" , "这是章鱼哥从岸上偷运来的,据说很美味!"));
foodDb.put(new User(1 , "海绵宝宝" , "123123123" , "开心的海绵宝宝!") , catList1);
List<Food> catList2 = new ArrayList<Food>();
catList2.add(new Food(3 , "蟹王汉堡" , "橙色,亮金色,我的宝贝,我的爱!"));
catList2.add(new Food(4 , "海绵金币" , "吃着金币样的甜甜饼,想着海绵宝宝赚的钱被扣了,哈哈哈,爽气!"));
foodDb.put(new User(2 , "蟹老板" , "321321321" , "哇!好有钱的蟹老板") , catList2);
}
@Override
public List<Food> getFoodsByUser(User user) {
return foodDb.get(user);
}
@Override
public Map<String, Food> getAllFoods() {
Map<String, Food> map = new HashMap<String, Food>();
for (List<Food> foods : foodDb.values()) {
for (Food food : foods) {
map.put(food.getName(), food);
}
}
return map;
}
}


⑤HelloWorld.java



import java.util.List;
import java.util.Map;
import javax.jws.WebService;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.fjava.cxf.model.Food;
import org.fjava.cxf.model.User;
import org.fjava.cxf.ws.util.HelloXmlAdepter;
@WebService//J2EE文档里查看WebService
public interface HelloWorld {
//还有一些像 Map、非JavaBean式的复合类,CXF不可以处理;
//CXF不能处理Map<String, Food>类型,我们采用HelloXmlAdepter进行处理
@XmlJavaTypeAdapter(value = HelloXmlAdepter.class) Map<String, Food> getAllFoods();
}


[b]⑥AuthInterceptor.java[/b]



import java.util.ArrayList;
import java.util.List;
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;
//通过PhaseInterceptor,可以指定拦截器在哪个阶段起作用。
public class AuthInterceptor extends AbstractPhaseInterceptor<SoapMessage> {
public AuthInterceptor(){
//super表示显示调用父类有参数的构造器。
//显示调用父类构造器,程序将不会隐式调用父类无参的构造器。
//---父类构造器里方法AbstractPhaseInterceptor(String phase)
//---phase是指一个拦截阶段
//---CXF文档里有Phase类,这个类里有各个阶段
super(Phase.PRE_INVOKE);//该拦截器将会在"调用之前"拦截Soap消息。
}
//实现自己的拦截器时,需要实现handleMessage方法。
//handleMessage方法中的形参就是被拦截到的Soap消息。
//一旦程序获得了Soap消息,剩下的事情就可以解析Soap消息,或修改Soap消息。
@Override
public void handleMessage(SoapMessage msg) throws Fault{
//下面代码显示"调用之前"成功拦截了信息
System.out.println("----------------"+ msg);
//得到Soap消息的所有Header
List<Header> headers = new ArrayList<Header>();
try {
headers = msg.getHeaders();
} catch (Exception e) {
throw new Fault(new IllegalArgumentException("没有Header,禁止调用!"));
}
//如果根本没有Header
if(headers == null || headers.size() < 1 ){
throw new Fault(new IllegalArgumentException("没有Header,禁止调用!"));
}
System.out.println("headers" + headers);
//假如要求第一个Header携带了用户名、密码信息
Header firstHeader = headers.get(0);
Element ele = (Element)firstHeader.getObject();
NodeList userIds = ele.getElementsByTagName("userId");
NodeList passwords = ele.getElementsByTagName("password");
if(userIds == null || userIds.getLength() != 1){
throw new Fault(new IllegalArgumentException("用户名格式不正确!"));
}
if(passwords == null || passwords.getLength() != 1){
throw new Fault(new IllegalArgumentException("密码格式不正确!"));
}
System.out.println("userIds" + userIds);
System.out.println("passwords" + passwords);
//得到userId元素里的文本内容,以该内容作为用户名
String userId = userIds.item(0).getTextContent();
String password = passwords.item(0).getTextContent();
//实际项目中,是去查询数据库,该用户名、密码是否被授权访问该Web Service。
if(!"admin".equals(userId) || !"admin".equals(password)){
throw new Fault(new IllegalArgumentException("用户名、密码不正确!"));
}
}
}


[b]⑦HelloWorldWs.java[/b]



import java.util.Date;
import java.util.List;
import java.util.Map;
import javax.jws.WebService;
import org.fjava.cxf.model.Food;
import org.fjava.cxf.model.User;
import org.fjava.cxf.ws.HelloWorld;
import org.springframework.stereotype.Service;
import org.fjava.cxf.service.UserService;
import org.fjava.cxf.service.UserServiceImpl;
@Service
@WebService(endpointInterface="org.fjava.cxf.ws.HelloWorld", serviceName="HelloWorldWs")
//endpointInterface=""表示实现的接口  serviceName是wsdl2java或其他语言后的文件名,可以不和HelloWorldWs相同
public class HelloWorldWs implements HelloWorld {
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}
@Override
public Map<String, Food> getAllFoods() {
return userService.getAllFoods();
}
}


[b]⑧HelloXmlAdepter.java[/b]



import java.util.HashMap;
import java.util.Map;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import org.fjava.cxf.model.Food;
import org.fjava.cxf.ws.util.StringFood.Entry;
//该转换器负责完成StringFood与Map<String, Food>的相互转换
public class HelloXmlAdepter extends XmlAdapter<StringFood, Map<String, Food>> {
@Override
public Map<String, Food> unmarshal(StringFood v) throws Exception {
Map<String, Food> resMap = new HashMap<>();
for (Entry entry : v.getEntries()) {
resMap.put(entry.getKey(), entry.getValue());
}
return resMap;
}
@Override
public StringFood marshal(Map<String, Food> v) throws Exception {
StringFood sf = new StringFood();
for (String key : v.keySet()) {
sf.getEntries().add(new Entry(key, v.get(key)));
}
return sf;
}
}


[b]⑨StringFood.java[/b]



import java.util.ArrayList;
import java.util.List;
import org.fjava.cxf.model.Food;
public class StringFood {
public static class Entry{
private String key;
private Food value;
public Entry() {
super();
}
public Entry(String key, Food value) {
super();
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public Food getValue() {
return value;
}
public void setValue(Food value) {
this.value = value;
}
}
private List<Entry> entries = new ArrayList<>();
public List<Entry> getEntries() {
return entries;
}
public void setEntries(List<Entry> entries) {
this.entries = entries;
}
}


[b]⑩applicationContext.xml[/b]



<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=" http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- Web应用的类加载路径有两类
1.WEB-INF/classes目录
2.WEB-INF/lib目录下
-->
<import resource="classpath:META-INF/cxf/cxf.xml"/>
<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml"/>
<import resource="classpath:META-INF/cxf/cxf-servlet.xml"/>
<!-- 注册Service -->
<bean id="userService" class="org.fjava.cxf.service.UserServiceImpl"/>
<bean id="helloWorldWs" class="org.fjava.cxf.ws.impl.HelloWorldWs"
p:userService-ref="userService"/>
<!-- implementor指定WebService的服务提供者
1.直接给定服务器提供者类名
2.设置为容器中的Bean,要在Bean的id前加上#号
-->
<jaxws:endpoint implementor="#helloWorldWs" address="/getAllFoods">
<!-- 添加了两个In拦截器 -->
<jaxws:inInterceptors>
<bean class="org.apache.cxf.interceptor.LoggingInInterceptor"/><!-- 嵌套Bean,创建一个Bean -->
<bean class="org.fjava.cxf.ws.auth.AuthInterceptor"/>
<!-- <ref bean="anotherInterceptor">引用一个已有的Bean -->
</jaxws:inInterceptors>
<!-- 需要配置Out拦截器,使用<jaxws:outInterceptors> -->
</jaxws:endpoint>
<!-- <jaxws:endpoint implementor="org.fjava.cxf.ws.impl.HelloWorldWs" address="/getAllFoods">
</jaxws:endpoint> -->
</beans>
⑪web.xml


<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
<display-name></display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<!-- 该Listener会保证在Web应用启动时加载Spring容器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 配置CXF的核心控制器,所有来自/cxf-Spring/service/*的请求都交给CXF处理 -->
<servlet>
<servlet-name>cxf</servlet-name>
<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>cxf</servlet-name>
<url-pattern>/cxf-Spring/service/*</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
(2)、客户端:

        我编写的服务器端地址为:http://192.168.0.159:8080/CXF_Spring/cxf-Spring/service/getAllFoods?wsdl;


[b][b]        按照文章 CXF-02 中生成客户端的操作,用命令 wsdl2java http://192.168.0.159:8080/CXF_Spring/cxf-Spring/service/getAllFoods?wsdl 生成客户端;

①ClientMain.java

[/b][/b]



import java.util.List;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory;
import org.fjava.cxf.ws.Entry;
import org.fjava.cxf.ws.Food;
import org.fjava.cxf.ws.HelloWorld;
import org.fjava.cxf.ws.StringFood;
import org.fjava.cxf.ws.auth.AddHeaderInterceptor;
import org.fjava.cxf.ws.impl.HelloWorldWs;
public class ClientMain {
public static void main(String[] args) {
//这是命令生成的类,该类的实例可当成工厂来使用
HelloWorldWs factory = new HelloWorldWs();
//无参的方法,返回的是远程Web Service服务端的代理,服务端不能关闭。
HelloWorld helloWorld = factory.getHelloWorldWsPort();
Client client = ClientProxy.getClient(helloWorld);
client.getOutInterceptors().add(new AddHeaderInterceptor("admin","admin"));
client.getOutInterceptors().add(new LoggingOutInterceptor());
StringFood allFoods = helloWorld.getAllFoods();
List<Entry> entries = allFoods.getEntries();
for (Entry entry : entries) {
System.out.println(entry.getKey() + " " + entry.getValue().getDescribe());
}
}
}
②AddHeaderInterceptor.java——服务端限制了用户名与密码,在 Soap 中增加 header 元素
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 AddHeaderInterceptor extends AbstractPhaseInterceptor<SoapMessage> {
private String userId;
private String password;
public AddHeaderInterceptor(String userId, String password) {
super(Phase.PREPARE_SEND);//在准备发送Soap消息时启用该拦截器
this.userId = userId;
this.password = password;
}
@Override
public void handleMessage(SoapMessage msg) throws Fault {
List<Header> headers = msg.getHeaders();
//创建Document对象
Document document = DOMUtils.createDocument();
Element eleAuthHeader = document.createElement("authHeader");
//此处创建的元素应该与服务器的拦截元素相同
Element eleUserId = document.createElement("userId");
eleUserId.setTextContent(userId);
Element elePassword = document.createElement("password");
elePassword.setTextContent(password);
eleAuthHeader.appendChild(eleUserId);
eleAuthHeader.appendChild(elePassword);
/*
* 上面代码生成了一个如下XML文档片段:
* <authHeader>
*	<userId></userId>
*	<password></password>
*</authHeader>
*/
//把ele元素包装成Header,并添加到SOAP消息的Header列表中。
headers.add(new Header(new QName("fjava"),eleAuthHeader));
}
}


代码编写完毕,运行结果如下,

[b]服务端正常启动运行客户端调用运行结果:
[/b]

十月 27, 2016 10:43:30 下午 org.apache.cxf.transport.servlet.CXFServlet updateContext
信息: Load the bus with application context
十月 27, 2016 10:43:30 下午 org.apache.cxf.bus.spring.BusApplicationContext getConfigResources
信息: No cxf.xml configuration file detected, relying on defaults.
十月 27, 2016 10:43:30 下午 org.apache.cxf.transport.servlet.AbstractCXFServlet replaceDestinationFactory
信息: Servlet transport factory already registered
十月 27, 2016 10:43:31 下午 org.apache.cxf.interceptor.LoggingInInterceptor logging
信息: Inbound Message
----------------------------
Encoding: UTF-8
Headers: {cache-control=[no-cache], content-type=[text/xml; charset=UTF-8], connection=[keep-alive], host=[192.168.0.159:8080], content-length=[264], SOAPAction=[""], user-agent=[Apache CXF 2.4.1], Accept=[*/*], pragma=[no-cache]}
Messages:
Message:

Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Header><authHeader><userId>admin</userId><password>admin</password></authHeader></soap:Header><soap:Body><ns2:getAllFoods xmlns:ns2="http://ws.cxf.fjava.org/"/></soap:Body></soap:Envelope>
--------------------------------------
----------------org.apache.cxf.binding.soap.SoapMessage@de912969
headers[org.apache.cxf.binding.soap.SoapHeader@608d8ce4]
userIdscom.sun.org.apache.xerces.internal.dom.DeepNodeListImpl@6d1cfe41
passwordscom.sun.org.apache.xerces.internal.dom.DeepNodeListImpl@a78955e


[b]客户端运行结果:[/b]



十月 27, 2016 10:43:31 下午 org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromWSDL
信息: Creating Service {http://impl.ws.cxf.fjava.org/}HelloWorldWs from WSDL: http://192.168.0.159:8080/CXF_Spring/cxf-Spring/service/getAllFoods?wsdl 十月 27, 2016 10:43:31 下午 org.apache.cxf.interceptor.AbstractLoggingInterceptor log
信息: Outbound Message
---------------------------
ID: 1
Address: http://192.168.0.159:8080/CXF_Spring/cxf-Spring/service/getAllFoods Encoding: UTF-8
Content-Type: text/xml
Headers: {Accept=[*/*], SOAPAction=[""]}
Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Header><authHeader><userId>admin</userId><password>admin</password></authHeader></soap:Header><soap:Body><ns2:getAllFoods xmlns:ns2="http://ws.cxf.fjava.org/"/></soap:Body></soap:Envelope>
--------------------------------------
蟹王汉堡 橙色,亮金色,我的宝贝,我的爱!
海绵金币 吃着金币样的甜甜饼,想着海绵宝宝赚的钱被扣了,哈哈哈,爽气!
一个汉堡 是三层的,有夹层哦!
火腿肠 这是章鱼哥从岸上偷运来的,据说很美味!


[b]做一个权限控制,有用户名和密码的时候才允许调用 Web Service 该功能完成!
[/b]

希望对你有帮助,祝你有一个好心情,加油!

若有错误、不全、可优化的点,欢迎纠正与补充;转载请注明出处!

若有疑问请下方留言。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  cxf spring 整合