cxf结合maven实现rest服务
本小节中,我们将以下面背景为需求开发一组包含基本增删改查操作的 RESTFul web services 服务。
某集成网络服务提供商拥有大量的用户,现在需要将部分用户信息处理能力暴露给第三方经过验证和授权的合作伙伴,所以该服务提供商决定开发一个 RESTful web services 程序来满足这部分需求,该程序需要实现的功能是:
- 查询用户
- 增加用户
- 修改用户信息
- 注销一个用户
- 获取某个时间段内注册的用户
创建 Maven 工程
通过 File->New-Other.. 菜单,选择 Maven Project
图 7. 新建 Maven 工程
选择 maven-archetype-quikstart 工程类型,这种类型的项目只包含 Maven 工程的基本结构。
图 8. 选择工程的 Archetype
填写工程的 groupId,artifactId,package 等信息,这些信息分别对应了 Maven 的 pom.xml 项目管理文件中的同名标签的信息。groupId 代表 Maven 工程的群组 id,一般实现相似功能的 Maven 工程具有相同的群组 ID。例如,我们可能会开发很多关于用户的 Web Service ,如用户基本信息服务,用户定制服务管理服务,用户交互信息管理服务等,那么这些工程最好都有相同的群组 ID。 artifactId 是用来标识相似功能工程群组中不同的项目。例如,我们实现用户的基本信息管理的项目被命名为为 user-info-service. package 的默认值是 groupId. artifactId, 代表工程根包名称,
一般会做一些调整使之更具实际意义。
图 9. 填写工程的相关信息
点击 Finish 按钮完成项目创建过程。
创建完成后,将 src/main/java 和 src/test/java 文件夹设置为工程的 source folder
图 10. 配置工程的 Build Path
点击 OK,可以看到工程结构如图所示:
图 11. 工程的目录结构
其中 src/main/java 是 java 源文件存放的目录,src/test/java 是测试类存放的地方,一般我们把写的 Junit 单元测试类放在这个目录。pom.xml 是 Maven 项目的管理配置文件,我们对 OSGi bundle 的依赖管理、包管理,以及通过 Maven 插件完成的任务都在这里配置。
配置 Maven 工程
由于我们的项目开发需要用到 CXF 和 JSR311 API,所以需要在 pom.xml 加入对这些包的依赖。
代码片段如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
代码 3. 加入 CXF 和 JSR311 jar 包依赖代码示例 < dependencies > < dependency > < groupId >javax.ws.rs</ groupId > < artifactId >jsr311-api</ artifactId > < version >1.1.1</ version > </ dependency > < dependency > < groupId >org.apache.cxf</ groupId > < artifactId >cxf-rt-frontend-jaxrs</ artifactId > < version >2.4.4</ version > </ dependency > </ dependencies > |
其次还需要修改 packaging 标签对应的值为 bundle,因为默认建好工程后这个值为 jar。这样提示 Maven 需要将工程打包成一个 OSGi Bundle。
1
2
|
代码 4. 项目打包类型修改代码示例 < packaging >bundle</ packaging > |
最后,我们需要配置基本 OSGi bundle 工程的信息,如 Export Packge 点点、 Import Package、 Private Packge 等信息,熟悉 OSGi 规范的开发者都知道,每一个 OSGi bundle 都含有一个 /META-INF/manifest.mf 文件来描述 bundle 的基本信息。OSGi 容器通过这些信息来控制 bundle 的包访问策略。对于用 Maven 打包的 OSGi 项目,需要在 POM 文件的相应标签内配置这些信息,而这些信息也将会在打包时被写入最终的 manifest.mf 文件。如果您没有或者想了解 OSGi 规范的有关知识,请参考 OSGi 联盟网站 http://www.osgi.org/
本例中 OSGi bundle 配置的代码片段如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
代码 5. OSGi bundle 开发基本配置代码示例 < build > < plugins > < plugin > < groupId >org.apache.felix</ groupId > < artifactId >maven-bundle-plugin</ artifactId > < version >2.2.0</ version > < extensions >true</ extensions > < configuration > < instructions > < Export-Package >com.netprovider.user.service.baseinfo</ Export-Package > < Private-Package >com.netprovider.user.service.baseinf.*</ Private-Package > < Import-Package > META-INF.cxf, META-INF.cxf.osgi, javax.xml.bind, javax.xml.bind.annotation, javax.xml.ws, javax.ws.rs.*, * </ Import-Package > < DynamicImport-Package >*</ DynamicImport-Package > </ instructions > </ configuration > </ plugin > </ plugins > </ build > |
通过上述配置我们可以看到,使用 Maven 来管理项目时,对于第三方包的依赖无需再显示引入 jar 包到 classpath,而且通过在 Maven 管理文件中加入相应的依赖,这样做的好处是可以轻松的修改项目所以来的软件包的版本,而不需要在项目里替换 jar 包。
开发项目代码
前面我们提到过,JAXB 是 CXF 默认的数据绑定方式,所以本例中我们会使用 JAXB 标签标注创建的 User 类,来定义 User 类和 XML 之间的映射关系。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
代码 6. User 类源码 @XmlAccessorType(XmlAccessType.PROPERTY) @XmlRootElement(name = "User") @XmlType(propOrder = { "userId", "nickname", "gender","registerDate"}) publicclassUser implementsSerializable { privatestaticfinallongserialVersionUID= 1L; privateString userId; privateString nickname; privateString gender; privateDate registerDate; publicUser(String userId, String nickname, String gender, Date registerDate) { this.userId = userId; this.nickname = nickname; this.gender = gender; this.registerDate = registerDate; } publicString getUserId() { returnuserId; } publicvoidsetUserId(String userId) { this.userId = userId; } publicString getNickname() { returnnickname; } publicvoidsetNickname(String nickname) { this.nickname = nickname; } publicString getGender() { returngender; } publicvoidsetGender(String gender) { this.gender = gender; } @XmlJavaTypeAdapter(DateConverter.class) publicDate getRegisterDate() { returnregisterDate; } publicvoidsetRegisterDate(Date registerDate) { this.registerDate = registerDate; } @Override publicString toString() { return"User [userId=" + userId + ", nickname=" + nickname + ", gender=" + gender + ", registerDate =" + registerDate + "]"; } } 代码 7. Users 类源码 |
为了用 XML 映射多个用户,我们定义了 Users 类,Users 类只有一个成员变量,它是一个装有 User 对象的 java.util.List 列表。
1
2
3
4
5
6
7
8
9
10
11
12
|
@XmlRootElement(name = "Users") publicclassUsers { @XmlElement(name = "User") privateList< User > users; publicList< User > getUsers() { returnusers; } publicvoidsetUsers(List< User > users) { this.users = users; } } |
对于注册日期 registerDate 变量的映射,需要做一些必要的处理,我们希望日期被映射为 yyyy-mm-dd 的形 式,上述 User 类中, 我们用 @XmlJavaTypeAdapter 标签 注释 registerDate 的 getter 方法, 希望 告知 JAXB 遇到这个属性的时候, 用 DateConverter 类做处理,DateConverter 类继承自 javax.xml.bind.
annotation.adapters.XmlAdapter
类
并
覆盖重写了 JAXB 的 marshall/unmarshall 方法, 对于传入的日期做了 转换处理。具体代码片段如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
代码 8. DateConverter 类源码 publicclassDateConverter extendsXmlAdapter< String , Date> { privateDateFormat df = newSimpleDateFormat("yyyy-MM-dd"); publicDate unmarshal(String date) throwsException { returndf.parse(date); } publicString marshal(Date date) throwsException { returndf.format(date); } } |
为了简化程序复杂度,程序中用到的用户数据是写死在内存中的五个测试用户数据。测试数据如下:
图 12. 测试数据
根据上文介绍的需求和 CXF,JAX-RS 的相关知识,我们知道 增删改查方法 分 别对应了 HTTP 协议的 POST,DELETE,PUT,GET 请求,下面我们定义一个 UserService 接口,声明我们要实现的服务方法以及它们对应的 HTTP 方法类型,接受和返回内容的 MIME 类型等,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
代码 9. UserService 类源码 @Produces({ MediaType.APPLICATION_XML}) @Consumes({ MediaType.APPLICATION_XML}) publicinterfaceUserService { @GET @Produces(MediaType.APPLICATION_XML) @Path("/searchuser/{userId}") User searchUser(@PathParam("userId") String userId); @POST @Consumes(MediaType.APPLICATION_XML) @Path("/adduser") Response addUser(User user); @DELETE @Consumes(MediaType.APPLICATION_XML) @Path("/deleteuser/{userId}") Response deleteUser(@PathParam("userId") String userId); @PUT @Path("/category") @Consumes(MediaType.APPLICATION_XML) Response updateUser(User user); @GET @Path("/getusers/startdate/{startDate}/enddate/{endDate}") @Produces({ MediaType.APPLICATION_XML}) Users getUsers(@PathParam("startDate") String startDate , @PathParam("endDate") String endDate); } |
定义了服务接口类后,接下来就是实现它了。代码中增删改查方法都调用了 UserDAO 类,该类模拟了数据库的操作,不同的是这里其实是从一个内存中的静态列表中操作数据,具体可以参考附件。
在得到了 User 对象实例后,对于 GET 类型的方法,会直接返回相应的 JAXB 标注过的类,CXF 会使用 JAXB 根据注释解析类属性,完成 User 类实例与 XML 之间的转化。对于 PUT,POST,DELETE 类型的方法,都是先检查是否存在这样的数据,如果不存在,会返回一个提示消息告诉需要操作的数据不存在。具体请参考代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
代码 10. UserServiceImpl 类源码 publicclassUserServiceImpl implementsUserService { publicUser searchUser(String userId) { User user = UserDAO.searchUser(userId); if(user == null) { ResponseBuilder builder = Response.status(Status.NOT_FOUND); builder.type("application/xml"); builder.entity("< errorMsg >User with id:" + userId + " can not be found!</ errorMsg >"); thrownewWebApplicationException(builder.build()); } else{ System.out.println("User with id:" + userId + " is found"); returnuser; } } publicResponse addUser(User user) { User userObj = (User) UserDAO.searchUser(user.getUserId()); if(userObj != null) { ResponseBuilder builder = Response.status(Status.FORBIDDEN); builder.type("application/xml"); builder.entity("< errorMsg >User with id:" + user.getUserId() + " already exists</ errorMsg >"); thrownewWebApplicationException(builder.build()); } else{ UserDAO.addUser(user); returnResponse.ok(user).build(); } } publicResponse deleteUser(String userId) { User userObj = (User) UserDAO.searchUser(userId); if(userObj == null) { ResponseBuilder builder = Response.status(Status.FORBIDDEN); builder.type("application/xml"); builder.entity("< errorMsg >User with id:" + userId + " is not existed, delettion request is rejected</ errorMsg >"); thrownewWebApplicationException(builder.build()); } else{ UserDAO.deleteUser(userId); returnResponse.ok().build(); } } publicResponse updateUser(User user) { User userObj = (User) UserDAO.searchUser(user.getUserId()); if(userObj == null) { ResponseBuilder builder = Response.status(Status.FORBIDDEN); builder.type("application/xml"); builder.entity("< errorMsg >User with id:" + user.getUserId() + " is not existed, update request is rejected</ errorMsg >"); thrownewWebApplicationException(builder.build()); } else{ UserDAO.updateUser(user); returnResponse.ok(user).build(); } } publicUsers getUsers(String startDate, String endDate) { List< User > userList = newArrayList< User >(); ResponseBuilder builder = Response.status(Status.OK); builder.type("application/xml"); try{ userList = UserDAO.getUsersByRegDate(startDate, endDate); } catch(Exception e) { e.printStackTrace(); builder = Response.status(Status.NOT_ACCEPTABLE); builder.entity("< errorMsg >" + "not accpertable date format for startDate or endDate</ errorMsg >"); thrownewWebApplicationException(builder.build()); } if(userList.size() < 1 ) { builder = Response .status(Status.NOT_FOUND); builder.entity("<errorMsg>no user found registered between" +startDate+ " and " + endDate + "</ errorMsg >"); thrownewWebApplicationException(builder.build()); } else{ Users users = newUsers(); users.setUsers(userList); returnusers; } } |
至此,主要类的代码开发就完成了,现在我们需要利用 Spring 去配置和发布 web services ,CXF 对于 Spring 有很好的集成。 在项目中创建如下 目录结构,因为这样才会在打包后被 Servicemix 容器认识。
图 13. 添加 Spring 配置文件
其中 beans.xml 就是 Spring 配置文件,这个文件名称可以随便命名,但必须是 XML 类型。对于熟悉 Spring 的开发人员来说,下面配置很容易看懂,但是 <jax-rs : server> 标签是 CXF 集成了 Spring 后提供的 , 其中会包含
Web services
服务的相对地址,服务的实现类等,具体请参考代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
代码 11. 通过 Spring 配置文件发布 Web services 代码示例 < beans xmlns = "http://www.springframework.org/schema/beans" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxrs = "http://cxf.apache.org/jaxrs" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd http://camel.apache.org/schema/osgi http://camel.apache.org/schema/osgi/camel-osgi.xsd http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd http://camel.apache.org/schema/cxf http://camel.apache.org/schema/cxf/camel-cxf.xsd"> < import resource = "classpath:META-INF/cxf/cxf.xml" /> < jaxrs:server id = "userRestService" address = "/userservices/v1.0" > < jaxrs:serviceBeans > < ref bean = "userServiceImpl" /> </ jaxrs:serviceBeans > </ jaxrs:server > < bean id = "userServiceImpl" class = "com.netprovider.user.service.baseinfo.impl.UserServiceImpl" > </ bean > </ beans > |
至此,RESTful Web Service 开发和配置工作就完成了。
打包和部署
打包
Maven 作为本文用到的项目 build 工具,已经作为插件被集成到 eclipse IDE 中,现在是发挥它打包作用的时候了。在 eclipse 中右键单击工程,选择 Run As ->Maven install 开始打包过程。
图 14. 使用 Maven 打包工程
Maven install 操作不仅会完成打包工作,而且还会将该打包后的 bundle(user-info-service-0.0.1-SNAPSHOT.jar) 文件安装到 Maven 本地仓库中。
打包完成后,会在工程的 target 目录下看到打包好的文件。
图 15. 查看工程打包结果
部署
ServiceMix 支持热部署,所以我们只需要将打包好的 jar 文件拷贝到 $SERVICEMIX_HOME /deploy 目录下,然后跑 $SERVICEMIX_HOME/bin/servicemix.bat 命令启动 ServiceMix 容器即可。
容器启动后在控制台输入 osgi:list 命令,如果看到如下所示信息代表启动 bundle 成功了。
图 16. 查看 OSGi bundle 部署是否成功
图中第四列代表 Spring 容器的状态,如果没有看到 Started , 请手动重启 : restart 158
其中 158 是 ServiceMix 容器分配给 bundle 的 ID. 运行 restart 命令时请替换成你环境中 bundle 的 ID
bundle 启动后,可以在浏览器中输入 :http://<your_ip>:8181/cxf 查看 Web Service 是否已经成功被发布。8181 是 servicemix 的默认端口。 如果看到下图显示的信息,代表服务已经发布成功。