一、基本介绍
Web Services是一个软件接口,它描述了一组可以在网络上通过标准化的 XML 消息传递访问的操作。它使用基于 XML 语言的协议来描述要执行的操作或者要与另一个 Web 服务交换的数据。Web Services更多是一种标准,而不是一种具体的技术,不同的平台、语言大都提供Web Services的开发实现。在java领域,Web Services的框架很多,例如:Axis、xfire、CXF等。
二、CXF基本介绍
Apache CXF = Celtix + XFire,Apache CXF 的前身叫 Apache CeltiXfire,现在已经正式更名为 Apache CXF 了,以下简称为 CXF。CXF 继承了 Celtix 和 XFire 两大开源项目的精华,提供了对 JAX-WS 全面的支持,并且提供了多种 Binding 、DataBinding、Transport 以及各种 Format 的支持,并且可以根据实际项目的需要,采用代码优先(Code First)或者 WSDL 优先(WSDL First)来轻松地实现 Web Services 的发布和使用。
Apache CXF 是一个开源的 Services 框架,CXF 帮助您利用 Frontend 编程 API 来构建和开发 Services ,像 JAX-WS 。这些 Services 可以支持多种协议,比如:SOAP、XML/HTTP、RESTful HTTP 或者 CORBA ,并且可以在多种传输协议上运行,比如:HTTP、JMS 或者 JBI,CXF 大大简化了 Services 的创建,同时它继承了 XFire 传统,一样可以天然地和 Spring 进行无缝集成。
CXF WebService 开发,主要分为两种服务提供方式 WS 、RS 。这里分别介绍这两种方式独立发布方法和与Spring整合的发布方法。
三、CXF-WS开发入门
1、JAX-WS独立服务使用
JAX-WS 传输数据,就是 XML 格式,基于 SOAP 协议。
开发步骤:
1.建立简单的Maven项目。
2.引入关键坐标依赖,具体如下配置。
4.0.0
cn.leon.maven
cxf_ws_first_application
0.0.1-SNAPSHOT
cxf_ws_first_application
独立发布的CXF-WS服务java项目
org.apache.cxf
cxf-rt-frontend-jaxws
3.0.1
org.apache.cxf
cxf-rt-transports-http-jetty
3.0.1
org.slf4j
slf4j-log4j12
1.7.12
javax.jws
jsr181
1.0
- 第一个坐标是用来开发cxf应用,第二个坐标是搭建一个服务器,可以独立运行cxf应用,这个服务器类似独立的tomca插件服务器。
- 第三个坐标是日志输出的jar依赖,设置了这个依赖,就可以在项目运行期间打印出详细的数据传递的日志信息。但是不能忘记将log4j.properties配置文件拷贝至项目根目录下。
3.编写服务端程序,编写实体类。
User案例实体类:
package cn.leon.cxf.domain;
import java.util.ArrayList;
import java.util.List;
public class User {
private Integer id;
private String username;
private String city;
private List cars = new ArrayList();
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public List getCars() {
return cars;
}
public void setCars(List cars) {
this.cars = cars;
}
}
Car案例实体类:
package cn.leon.cxf.domain;
public class Car {
private Integer id;
private String name;
private Double price;
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 Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
@Override
public String toString() {
return "Car [id=" + id + ", name=" + name + ", price=" + price + "]";
}
}
服务端程序:
服务端口接口程序,这个是对外发布的接口,必须得有,具体的代码业务实现,是它的实现类来完成。接口代码如下:
package cn.leon.cxf.service;
import java.util.List;
import javax.jws.WebMethod;
import javax.jws.WebService;
import cn.leoncxf.domain.Car;
import cn.leon.cxf.domain.User;
/**
* 该接口是服务端提供对外服务的接口,服务的主要代码由其实现类来完成。
* @author lemon
*
*/
@WebService // 使用在类上面,标记类是 WebService 服务提供对象
public interface IUserService {
@WebMethod // 使用在方法上面,标记方法 是 WebService 服务提供方法
String sayHello(String name);
@WebMethod
List findCarByUser(User user);
}
注解说明:
@WebService:使用在类上面,标记类是 WebService 服务提供对象;
@WebMethod:使用在方法上面,标记方法是 WebService 服务提供方法。
实现类代码实现:
package cn.leon.cxf.service;
import java.util.ArrayList;
import java.util.List;
import javax.jws.WebService;
import cn.leon.cxf.domain.Car;
import cn.leon.cxf.domain.User;
@WebService(endpointInterface = "cn.itcast.cxf.service.IUserService", serviceName = "userService")
// 注解设置 endPointInterface 接口服务完整类名, servicename 服务名称
public class UserServiceImpl implements IUserService {
// 传递简单的数据
public String sayHello(String name) {
return "Hello " + name;
}
// 传递复杂的数据
public List findCarByUser(User user) {
List list = null;
if ("tom".equals(user.getUsername())) {
list = new ArrayList();
Car car = new Car();
car.setId(1);
car.setName("QQ汽车");
car.setPrice(10000D);
list.add(car);
}
return list;
}
}
注解参数说明:
endpointInterface:接口服务的完整类名;
serviceName:服务名。
4.服务发布,编写服务发布代码。
package cn.leon.cxf.ws.server;
import javax.xml.ws.Endpoint;
import cn.itcast.cxf.service.IUserService;
import cn.itcast.cxf.service.UserServiceImpl;
/**
* 使用CXF将将UserService注册到到网络上
* @author lemon
*
*/
public class WS_Server {
public static void main(String[] args) {
// 1.服务实现类对象
IUserService userService = new UserServiceImpl();
// 2.发布服务地址
String address = "http://localhost:9999/userService";
// 3.发布服务
Endpoint.publish(address, userService);
System.out.println("服务开启了....");
}
}
服务地址:http://localhost:9999/userService?wsdl可以在浏览器上访问这个地址,实现数据的访问,但是由于从浏览器没法传递参数,故无法完成对服务的方法的调用。
5.编写客户端程序代码,实现通信。
package cn.leon.cxf.ws.client;
import java.util.List;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
import cn.leon.cxf.domain.Car;
import cn.leon.cxf.domain.User;
import cn.leon.cxf.service.IUserService;
public class WS_Client {
public static void main(String[] args) {
// 编写客户端,调用WebService服务
JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean();
jaxWsProxyFactoryBean.setAddress("http://localhost:9999/userService");
jaxWsProxyFactoryBean.setServiceClass(IUserService.class);
// 日志打印设置,是可选设置
jaxWsProxyFactoryBean.getInInterceptors().add(new LoggingInInterceptor());
jaxWsProxyFactoryBean.getOutInterceptors().add(new LoggingOutInterceptor());
// 创建调用远程服务的代理对象
IUserService proxy = (IUserService) jaxWsProxyFactoryBean.create();
// 直接调用远程服务的方法
System.out.println(proxy.sayHello("ITCAST"));
User user = new User();
user.setUsername("tom");
List cars = proxy.findCarByUser(user);
for (Car car : cars) {
System.out.println(car);
}
}
}
这就是一个独立发布的JAX-WS的一个完整流程,现总结如下:
- 配置相关依赖,也就是配置依赖的jar包;
- 编写传递数据的载体——实体类;
- 编写服务端的接口;
- 编写服务端接口的实现类;
- 编写服务发布的应用;
- 提供访问路径给第三方,由第三方编写客户端实现数据的通信。
JAX-WS原理分析:
客户端创建jaxWsProxyFactoryBean对象,它是一个服务端实现类的代理对象的创建工厂,通过create()方法可以创建服务端实现类的代理对象,这个对象可以调用一切在接口上添加了@WebMethod的方法,它的类型就是接口的类型,相当于服务端实现类对象的兄弟对象,这就完成了一个完整的Web Service应用。
2、JAX-WS与Spring整合开发Web Service
第一步:建立 maven web 项目 ,基于 tomcat 发布服务。
第二步:导入Maven坐标,建立依赖关系。
4.0.0
cn.leon.maven
cxf_ws_spring
0.0.1-SNAPSHOT
war
cxf_ws_spring
CXF的WS和Spring整合服务发布
org.apache.cxf
cxf-rt-frontend-jaxws
3.0.1
org.springframework
spring-context
4.1.7.RELEASE
org.springframework
spring-web
4.1.7.RELEASE
org.springframework
spring-test
4.1.7.RELEASE
junit
junit
4.12
javax.servlet
javax.servlet-api
3.0.1
test
org.codehaus.mojo
tomcat-maven-plugin
1.1
9998
3.配置web.xml
contextConfigLocation
classpath:applicationContext.xml
org.springframework.web.context.ContextLoaderListener
CXFService
org.apache.cxf.transport.servlet.CXFServlet
1
CXFService
/service/*
index.html
index.htm
index.jsp
default.html
default.htm
default.jsp
4.导入前面已有的实体类、接口和服务实现类。
5.在applicationContext.xml中配置服务,这个服务交给spring来管理。
需要注意的是:需要引入jaxws命名空间和约束。xmlns:jaxws="http://cxf.apache.org/jaxws"
6.使用tomcat插件启动maven项目,提供给第三方访问路径:http://localhost:9998/cxf_ws_spring/service/userService?wsdl。
注意:尤其要注意访问路径的拼接问题。tomcat访问路径+项目名+web.xml配置的访问路径+服务名,后面连接上参数wsdl。
7.整合spring,配置客户端,客户端交给spring来管理。
其中消息拦截器是可选设置。
8.编写测试案例。
package cn.leon.cxf.server.test;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import cn.leon.cxf.domain.Car;
import cn.leon.cxf.domain.User;
import cn.leon.cxf.service.IUserService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class CXFTest {
// 注入一个代理对象
@Autowired
private IUserService proxy;
@Test
public void testServer() {
System.out.println(proxy.sayHello("黑马程序员"));
}
@Test
public void testServer2() {
User user = new User();
user.setUsername("tom");
List cars = proxy.findCarByUser(user);
for (Car car : cars) {
System.out.println(car);
}
}
}
四、CXF-RS开发入门(重点)
1、JAX-RS独立服务使用
1.建立简单的Maven java项目。
2.导入Maven坐标。
4.0.0
cn.leon.maven
cxf_rs_first_application
0.0.1-SNAPSHOT
cxf_rs_first_application
独立的jax-rs应用
org.apache.cxf
cxf-rt-frontend-jaxrs
3.0.1
org.apache.cxf
cxf-rt-transports-http-jetty
3.0.1
org.slf4j
slf4j-log4j12
1.7.12
org.apache.cxf
cxf-rt-rs-client
3.0.1
org.apache.cxf
cxf-rt-rs-extension-providers
3.0.1
org.codehaus.jettison
jettison
1.3.7
3.编写实体类。
User类:
package cn.leon.cxf.domain;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "User")
public class User {
private Integer id;
private String username;
private String city;
private List cars = new ArrayList();
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public List getCars() {
return cars;
}
public void setCars(List cars) {
this.cars = cars;
}
@Override
public String toString() {
return "User [id=" + id + ", username=" + username + ", city=" + city + ", cars=" + cars + "]";
}
}
Car类:
package cn.itcast.cxf.domain;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "Car")
public class Car {
private Integer id;
private String name;
private Double price;
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 Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
@Override
public String toString() {
return "Car [id=" + id + ", name=" + name + ", price=" + price + "]";
}
}
注意:@XmlRootElement(name = "Car")指定了转换成xml和json的时候对象的名称。
4.编写业务接口和业务实现类。
业务接口:
package cn.leon.cxf.service;
import java.util.List;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import cn.leon.cxf.domain.User;
@Path("/userService")
@Produces("*/*")
public interface IUserService {
@POST
@Path("/user") // 设置二级访问路径
@Consumes({ "application/xml", "application/json" }) // 设置接收参数的格式
void saveUser(User user);
@PUT
@Path("/user")
@Consumes({ "application/xml", "application/json" })
void updateUser(User user);
@GET
@Path("/user")
@Consumes({ "application/xml", "application/json" })
@Produces({ "application/xml", "application/json" }) // 指定返回数据格式
List findAll();
@GET
@Path("/user/{id}")
@Consumes({ "application/xml", "application/json" })
@Produces({ "application/xml", "application/json" })
User findUserById(@PathParam("id") Integer id); // 参数指定了接收数据的名字
@DELETE
@Path("/user/{id}")
@Consumes({ "application/xml", "application/json" })
void deleteUserById(@PathParam("id") Integer id);
}
说明:
- @Path 指定了服务访问资源路径,访问路径是类上的path+方法上的path;
- @Consumes 指定能够处理客户端传递过来数据格式,也就是说指定了客户端传递过来的数据格式;
- @Produces 指定能否生成哪种格式数据返回给客户端;
- @GET 查询 @PUT 修改 @POST 增加 @DELETE 删除;
- @PathParam("id")指定了拼接在访问路径上参数。
业务实现类:
package cn.leon.cxf.service;
import java.util.ArrayList;
import java.util.List;
import cn.leon.cxf.domain.Car;
import cn.leon.cxf.domain.User;
public class UserServiceImpl implements IUserService {
public void saveUser(User user) {
System.out.println("save user:" + user);
}
public void updateUser(User user) {
System.out.println("update user:" + user);
}
public List findAll() {
List users = new ArrayList();
User user1 = new User();
user1.setId(1);
user1.setUsername("小明");
user1.setCity("北京");
List carList1 = new ArrayList();
Car car1 = new Car();
car1.setId(101);
car1.setName("保时捷");
car1.setPrice(1000000d);
carList1.add(car1);
Car car2 = new Car();
car2.setId(102);
car2.setName("宝马");
car2.setPrice(400000d);
carList1.add(car2);
user1.setCars(carList1);
users.add(user1);
User user2 = new User();
user2.setId(2);
user2.setUsername("小丽");
user2.setCity("上海");
users.add(user2);
return users;
}
public User findUserById(Integer id) {
if (id == 1) {
System.out.println(22222);
User user1 = new User();
user1.setId(1);
user1.setUsername("小明");
user1.setCity("北京");
return user1;
}
return null;
}
public void deleteUserById(Integer id) {
System.out.println("delete user id :" + id);
}
}
5.发布服务
package cn.leon.cxf.server;
import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
import cn.leon.cxf.domain.Car;
import cn.leon..cxf.domain.User;
import cn.leon.cxf.service.IUserService;
import cn.leon.cxf.service.UserServiceImpl;
public class RSServer {
public static void main(String[] args) {
// 创建服务端业务接口实现类对象
IUserService userService = new UserServiceImpl();
// 创建服务
JAXRSServerFactoryBean jaxrsServerFactoryBean = new JAXRSServerFactoryBean();
jaxrsServerFactoryBean.setResourceClasses(User.class, Car.class); // 指定将哪些实体类转换成xml、json进行发送
jaxrsServerFactoryBean.setAddress("http://localhost:9997");
jaxrsServerFactoryBean.setServiceBean(userService);
// 发布服务
jaxrsServerFactoryBean.create();
System.out.println("服务开启了....");
}
}
现在可以通过浏览器来进行对服务端的数据访问了。
6.编写客户端。
对于客户端程序的编写 有两种做法
- 使用 http client 工具 ,需要自己对 HTTP 协议内容进行定制和解析
- WebClient 工具类使用(CXF 自带)
使用第二种,需要导入一个依赖,cxf-rt-rs-client。
依赖:
org.apache.cxf
cxf-rt-rs-client
3.0.1
客户端编写:
package cn.leon.cxf.client;
import java.util.Collection;
import javax.ws.rs.core.MediaType;
import org.apache.cxf.jaxrs.client.WebClient;
import cn.leon.cxf.domain.User;
public class RSClient {
public static void main(String[] args) {
// 参数说明:
// create是创建一个连接,给定一个访问地址
// accept是接收一个指定类型的数据,但是这个类型必须是服务接口中指定的返回数据类型
// getCollection是获取所有的对象的方法,如果是一个对象,就是要get,这个根据实际来定
// type是发送数据的格式,这个必须是服务端接口定义的接收的数据类型
Collection extends User> collection = WebClient.create("http://localhost:9997/userService/user")
.accept(MediaType.APPLICATION_XML).getCollection(User.class);
System.out.println(collection);
// 保存一个对象
User user = new User();
user.setUsername("Lemon");
WebClient.create("http://localhost:9997/userService/user")
.type(MediaType.APPLICATION_XML).post(user);
}
}
方法说明:
- create是创建一个连接,给定一个访问地址
- accept是接收一个指定类型的数据,但是这个类型必须是服务接口中指定的返回数据类型
- getCollection是获取所有的对象的方法,如果是一个对象,就是要get,这个根据实际来定
- type是发送数据的格式,这个必须是服务端接口定义的接收的数据类型
思考:
如何是要JSON数据格式进行交互?
要是要JSON数据进行交互,那就必须导入两个依赖。否则会报错:
Caused by: javax.ws.rs.ProcessingException: No message body writer has been found for class cn.itcast.cxf.domain.User, ContentType: application/json
需要导入的依赖是:
org.apache.cxf
cxf-rt-rs-extension-providers
3.0.1
org.codehaus.jettison
jettison
1.3.7
使用JSON数据进行交互的客户端:
package cn.leon.cxf.client;
import java.util.Collection;
import javax.ws.rs.core.MediaType;
import org.apache.cxf.jaxrs.client.WebClient;
import cn.leon.cxf.domain.User;
public class RSClient2 {
public static void main(String[] args) {
// 参数说明:
// create是创建一个连接,给定一个访问地址
// accept是接收一个指定类型的数据,但是这个类型必须是服务接口中指定的返回数据类型
// getCollection是获取所有的对象的方法,如果是一个对象,就是要get,这个根据实际来定
// type是发送数据的格式,这个必须是服务端接口定义的接收的数据类型
Collection extends User> collection = WebClient.create("http://localhost:9997/userService/user")
.accept(MediaType.APPLICATION_JSON).getCollection(User.class);
System.out.println(collection);
// 保存一个对象
User user = new User();
user.setUsername("Lemon");
WebClient.create("http://localhost:9997/userService/user")
.type(MediaType.APPLICATION_JSON).post(user);
}
}
2、JAX-RS与Spring整合开发Web Service
1.建立Maven Web项目。
2.导入相关依赖。
4.0.0
cn.leon.maven
cxf_rs_spring
0.0.1-SNAPSHOT
war
cxf_rs_spring
CXF的rs服务发布与spring整合
org.apache.cxf
cxf-rt-frontend-jaxrs
3.0.1
org.slf4j
slf4j-log4j12
1.7.12
org.apache.cxf
cxf-rt-rs-client
3.0.1
org.apache.cxf
cxf-rt-rs-extension-providers
3.0.1
org.codehaus.jettison
jettison
1.3.7
org.springframework
spring-context
4.1.7.RELEASE
org.springframework
spring-web
4.1.7.RELEASE
org.springframework
spring-test
4.1.7.RELEASE
junit
junit
4.12
org.codehaus.mojo
tomcat-maven-plugin
1.1
9996
第三步:3.导入web.xml
contextConfigLocation
classpath:applicationContext.xml
org.springframework.web.context.ContextLoaderListener
CXFService
org.apache.cxf.transport.servlet.CXFServlet
1
CXFService
/service/*
index.html
index.htm
index.jsp
default.html
default.htm
default.jsp
4.导入上述的实体类和服务接口、服务实现类。需要注意的是:服务接口上的@Path需要删除,或者将applicationContext.xml中发访问地址设置为空。@Path与applicationContext.xml 配置重复,所以一般注释掉,但是还是建议将xml中的配置设置为空,这里的path设置不为空,因为xml中可以配置接口的多个实现类,如果在xml设置配置统一的访问路径,那么就难以区分到底是哪个实现类在工作,而在类上设置@path,那么就可以区分不同的实现类,很明了。
5.配置applicationContext.xml
需要注意的是:需要引入名称空间:
xmlns:jaxrs="http://cxf.apache.org/jaxrs" http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd
6.启动服务,访问路径。
访问路径:服务器根目录地址(项目地址) + web.xml 配置 + applicationContext.xml address 配置 (或类 @Path )+ 方法 @Path
7.编写客户端。
package cn.leon.cxf.client;
import java.util.Collection;
import javax.ws.rs.core.MediaType;
import org.apache.cxf.jaxrs.client.WebClient;
import cn.leon.cxf.domain.User;
public class RSClient {
public static void main(String[] args) {
// 参数说明:
// create是创建一个连接,给定一个访问地址
// accept是接收一个指定类型的数据,但是这个类型必须是服务接口中指定的返回数据类型
// getCollection是获取所有的对象的方法,如果是一个对象,就是要get,这个根据实际来定
// type是发送数据的格式,这个必须是服务端接口定义的接收的数据类型
Collection extends User> collection = WebClient.create("http://localhost:9996/cxf_rs_spring/service/userService/user")
.accept(MediaType.APPLICATION_XML).getCollection(User.class);
System.out.println(collection);
// 保存一个对象
User user = new User();
user.setUsername("Lemon");
WebClient.create("http://localhost:9996/cxf_rs_spring/service/userService/user")
.type(MediaType.APPLICATION_XML).post(user);
}
}