最近在项目中,因为几个系统自己需要数据交换,所以采用进来都比较流行的RestFul风格WebService,实现框架采用apache的cxf,apache的东西一直以来都是比较的好用,回话少说,进入正题。
首先,提出两个问题,已经解决方案,后面跟上代码。
1、cxf中如何实现java中泛型的数据序列化和反序列化(通常使用json、xml格式,cxf默认不支持泛型)
2、cxf后台异常,在前台如何反映
问题1答案比较简单,cxf中的数据序列化是可以替换掉使用你实现MessageBodyReader<Object>和MessageBodyWriter<Object>接口就可以啦,针对xml,cxf采用stax2、jaxb、xmlschema、Woodstox库,针对json默认使用jettison实现的几乎都是codehaus作品。知道cxf序列化和反序列化方式就比较容易解决问题啦。默认情况下cxf的jettison对泛型序列化存在问题,因为时间紧(一天就要做好restful webservice部署),没有具体去研究实现问题,我只是在之前使用过jackson,去处理json问题,而且cxf拥有jackson的MessageBodyReader和MessageBodyWriter实现类,我只要导入包并告诉cxf使用我指定的json provider就可以了,所以在客户端和服务器端双方都指定json privoder,jackson 库对json序列化实现非常的到位,异常的强大。我们都知道,只要java源码中指定的泛型类我没都可以反射出来,如果使用泛型站位符,就没法反射,因为java中的擦除法的原因(比如List<String>、List<T>,前面是清楚的指定泛型参数类型,后面一种是在运行时指定),我这里讨论的也是指定泛型参数类型情况下,jackson在这种情况下已经支持,所以不需要自己实现MessageBodyReader和MessageBodyWriter接口。如果是使用xml方式,除自己实现接口外,有更简单的方法,那就是在你的泛型类上面指定@XmlSeeAlso({某某类1.class,某某类2.class...})
问题2同样的比较简单,因为基于http的restful实现时,服务器返回数据的时候都会告诉客户端一个响应状态吗,就是我们常看到的200、404、500等,cxf框架的rs webservice客户端实现是通过判断状态大于等于300时,抛出异常webapplicationexception,所以如果服务器端有异常时,通过设置状态就可以实现,并返回Response(通过实现ExceptionMapper<E extends Throwable>接口,并加入到provider实现),如果客户端需要错误消息(这里不得不说jcp设计的jsr311比较的细腻),可以在Response中设置,客户端catch掉webapplicationexception异常,并可以读取错误消息。cxf到这里还没有完,cxf提供一个ResponseExceptionMapper接口,客户端实现这个接口并加入到provider中,客户端在调用的时候就不用去处理cxf的异常webapplicationexception,而是你自己接口的异常,因为客户端在调用webservice时,cxf创建调用接口的代理,代理在接收到300错误时,他知道服务器是返回webapplicationexception异常,他就是用你的ResponseExceptionMapper处理异常,因为这个接口中唯一方法fromResponse(Response r)返回的是一个异常。也就是说,实现这个类方法时,可以读取webapplicationexception中的Response所包含的消息,并要求返回一次异常对象。这样就达到客户端不用关心webapplicationexception异常而是关系自己接口上面声明的异常。
代码:
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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
|
@XmlRootElement
(name=
"Customer"
)
public
class
Customer {
private
String id;
private
String name;
private
Date birthday;
public
String getId() {
return
id;
}
public
void
setId(String id) {
this
.id = id;
}
public
String getName() {
return
name;
}
public
void
setName(String name) {
this
.name = name;
}
public
Date getBirthday() {
return
birthday;
}
public
void
setBirthday(Date birthday) {
this
.birthday = birthday;
}
}
@XmlRootElement
(name=
"Me"
)
public
class
Me
implements
Serializable {
private
String name;
/**
* @return the name
*/
public
String getName() {
return
name;
}
/**
* @param name the name to set
*/
public
void
setName(String name) {
this
.name = name;
}
}
@XmlRootElement
(name =
"Page"
)
@XmlSeeAlso
({Customer.
class
,Me.
class
})
public
class
Page<T>
implements
Serializable{
/**
*
*/
private
static
final
long
serialVersionUID = 5859907455479273251L;
public
static
int
DEFAULT_PAGE_SIZE =
10
;
private
int
pageSize = DEFAULT_PAGE_SIZE;
// 每页的记录数
private
long
start;
// 当前页第一条数据在List中的位置,从0开始
private
List<T> data;
// 当前页中存放的记录,类型一般为List
private
long
totalCount;
// 总记录数
/**
* 构造方法,只构造空页.
*/
public
Page() {
this
(
0
,
0
, DEFAULT_PAGE_SIZE,
new
ArrayList<T>());
}
public
Page(
int
pageSize) {
this
(
0
,
0
, pageSize,
new
ArrayList<T>());
}
/**
* 默认构造方法.
*
* @param start
* 本页数据在数据库中的起始位置
* @param totalSize
* 数据库中总记录条数
* @param pageSize
* 本页容量
* @param data
* 本页包含的数据
*/
public
Page(
long
start,
long
totalSize,
int
pageSize, List<T> data) {
this
.pageSize = pageSize;
this
.start = start;
this
.totalCount = totalSize;
this
.data = data;
}
/**
* 取总记录数.
*/
public
long
getTotalCount() {
return
this
.totalCount;
}
/**
* 取总页数.
*/
public
long
getTotalPageCount() {
if
(totalCount % pageSize ==
0
)
return
totalCount / pageSize;
else
return
totalCount / pageSize +
1
;
}
/**
* 取每页数据容量.
*/
public
int
getPageSize() {
return
pageSize;
}
/**
* 取当前页中的记录.
*/
public
List<T> getResult() {
return
data;
}
/**
* 取该页当前页码,页码从1开始.
*/
public
long
getCurrentPageNo() {
return
start / pageSize +
1
;
}
/**
* 该页是否有下一页.
*/
public
boolean
hasNextPage() {
return
this
.getCurrentPageNo() <
this
.getTotalPageCount();
}
/**
* 该页是否有上一页.
*/
public
boolean
hasPreviousPage() {
return
this
.getCurrentPageNo() >
1
;
}
/**
* 获取任一页第一条数据在数据集的位置,每页条数使用默认值.
*
* @see #getStartOfPage(int,int)
*/
protected
static
int
getStartOfPage(
int
pageNo) {
return
getStartOfPage(pageNo, DEFAULT_PAGE_SIZE);
}
/**
* 获取任一页第一条数据在数据集的位置.
*
* @param pageNo
* 从1开始的页号
* @param pageSize
* 每页记录条数
* @return 该页第一条数据
*/
public
static
int
getStartOfPage(
int
pageNo,
int
pageSize) {
return
(pageNo -
1
) * pageSize;
}
@Override
public
String toString() {
return
ToStringBuilder.reflectionToString(
this
,
ToStringStyle.SHORT_PREFIX_STYLE);
}
/**
* @return the start
*/
public
long
getStart() {
return
start;
}
/**
* @param start the start to set
*/
public
void
setStart(
long
start) {
this
.start = start;
}
/**
* @return the data
*/
public
List<T> getData() {
return
data;
}
/**
* @param data the data to set
*/
public
void
setData(List<T> data) {
this
.data = data;
}
/**
* @param pageSize the pageSize to set
*/
public
void
setPageSize(
int
pageSize) {
this
.pageSize = pageSize;
}
/**
* @param totalCount the totalCount to set
*/
public
void
setTotalCount(
long
totalCount) {
this
.totalCount = totalCount;
}
}
|
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
|
<p>
public
class
ServiceException
extends
RuntimeException {
</p>
<p>
private
static
final
long
serialVersionUID = 7607640803750403555L;
public
ServiceException() {
super
();
}
</p>
<p>
public
ServiceException(String message) {
super
(message);
}
</p>
<p>
public
ServiceException(String message, Throwable cause) {
super
(message, cause);
}
</p>
<p>
public
ServiceException(Throwable cause) {
super
(cause);
}
}
</p>
|
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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
|
<span></span>
@Path
(value =
"/customer"
)
@Produces
({
"application/xml"
,
"application/json"
})
public
interface
CustomerService {
@GET
@Path
(value =
"/{id}/info"
)
Customer findCustomerById(
@PathParam
(
"id"
)String id);
@GET
@Path
(value =
"/search"
)
Customer findCustomerByName(
@QueryParam
(
"name"
)String name);
@POST
@Path
(value =
"/all"
)
List<Customer> findAllCustomer();
@POST
@Path
(value =
"/page"
)
Page<Customer> findPageCustomer()
throws
ServiceException;
@POST
@Path
(value =
"/pageMe"
)
Page<Me> findPage();
}
public
class
CustomerServiceImpl
implements
CustomerService {
public
Customer findCustomerById(String id) {
Customer customer =
new
Customer();
customer.setId(id);
customer.setName(id);
customer.setBirthday(Calendar.getInstance().getTime());
return
customer;
}
public
Customer findCustomerByName(String name) {
Customer customer =
new
Customer();
customer.setId(name);
customer.setName(name);
customer.setBirthday(Calendar.getInstance().getTime());
return
customer;
}
/** (non-Javadoc)
* @see edu.xdev.restful.CustomerService#findAllCustomer()
*/
@Override
public
List<Customer> findAllCustomer() {
List<Customer> tar =
new
LinkedList<Customer>();
Customer customer =
new
Customer();
customer.setId(
"e24234"
);
customer.setName(
"張三"
);
customer.setBirthday(Calendar.getInstance().getTime());
tar.add(customer);
customer =
new
Customer();
customer.setId(
"324324"
);
customer.setName(
"李四"
);
customer.setBirthday(Calendar.getInstance().getTime());
tar.add(customer);
return
tar;
}
/** (non-Javadoc)
* @see edu.xdev.restful.CustomerService#findPageCustomer()
*/
public
Page<Customer> findPageCustomer()
throws
ServiceException {
List<Customer> tar =
new
LinkedList<Customer>();
Customer customer =
new
Customer();
customer.setId(
"e24234"
);
customer.setName(
"張三"
);
customer.setBirthday(Calendar.getInstance().getTime());
tar.add(customer);
customer =
new
Customer();
customer.setId(
"324324"
);
customer.setName(
"李四"
);
customer.setBirthday(Calendar.getInstance().getTime());
tar.add(customer);
Page<Customer> page =
new
Page<Customer>(
1
,
2
,
1
, tar);
if
(
1
==
1
){
throw
new
ServiceException(
"abcd"
);
}
return
page;
}
/** (non-Javadoc)
* @see edu.xdev.restful.CustomerService#findPage()
*/
public
Page<Me> findPage() {
List<Me> tar =
new
LinkedList<Me>();
Me m =
new
Me();
m.setName(
"中文"
);
tar.add(m);
m =
new
Me();
m.setName(
"English"
);
tar.add(m);
Page<Me> page =
new
Page<Me>(
1
,
2
,
1
, tar);
return
page;
}
}
|
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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
|
@Provider
public
class
InvokeFaultExceptionMapper
implements
ExceptionMapper<ServiceException> {
/** (non-Javadoc)
* @see javax.ws.rs.ext.ExceptionMapper#toResponse(java.lang.Throwable)
*/
@Override
public
Response toResponse(ServiceException ex) {
ResponseBuilder rb = Response.status(Response.Status.INTERNAL_SERVER_ERROR);
rb.type(
"application/json;charset=UTF-8"
);
rb.entity(ex);
rb.language(Locale.SIMPLIFIED_CHINESE);
Response r = rb.build();
return
r;
}
}
public
class
ServiceExceptionMapper
implements
ResponseExceptionMapper<ServiceException>{
/** (non-Javadoc)
* @see org.apache.cxf.jaxrs.client.ResponseExceptionMapper#fromResponse(javax.ws.rs.core.Response)
*/
@Override
public
ServiceException fromResponse(Response r) {
Object obj = r.getEntity();
ObjectMapper mapper =
new
ObjectMapper();
mapper.configure(org.codehaus.jackson.map.DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,
false
);
try
{
return
mapper.readValue(obj.toString(), ServiceException.
class
);
}
catch
(JsonParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch
(JsonMappingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch
(IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return
new
ServiceException(obj.toString());
}
}
public
class
Server {
/**
* @param args
*/
public
static
void
main(String[] args) {
JAXRSServerFactoryBean factoryBean =
new
JAXRSServerFactoryBean();
factoryBean.getInInterceptors().add(
new
LoggingInInterceptor());
factoryBean.getOutInterceptors().add(
new
LoggingOutInterceptor());
factoryBean.setResourceClasses(CustomerServiceImpl.
class
);
List<Object> list =
new
LinkedList<Object>();
list.add(
new
org.codehaus.jackson.jaxrs.JacksonJsonProvider());
list.add(
new
InvokeFaultExceptionMapper());
factoryBean.setProviders(list);
factoryBean.setAddress(
"http://localhost:9000/ws/jaxrs"
);
factoryBean.create();
}
}
public
class
ClientTest {
private
static
List<Object> getJacksonJsonProvider(){
List<Object> providers =
new
LinkedList<Object>();
providers.add(
new
ServiceExceptionMapper());
JacksonJsonProvider provider =
new
JacksonJsonProvider();
provider.configure(org.codehaus.jackson.map.DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,
false
);
providers.add(provider);
return
providers;
}
/**
* @param args
*/
public
static
void
main(String[] args) {
try
{
Page<Me> pages = getServiceInstance(CustomerService.
class
).findPage();
for
(Me u:pages.getResult()){
System.out.println(u.getName());
}
Page<Customer> page = getServiceInstance(CustomerService.
class
).findPageCustomer();
for
(Customer u:page.getResult()){
System.out.println(u.getName());
}
}
catch
(WebApplicationException e){
if
(e
instanceof
WebApplicationException){
WebApplicationException we = (WebApplicationException)e;
System.out.println(we.getMessage());
//System.out.println(we.getCause().getMessage());
}
e.printStackTrace();
}
catch
(Exception e){
e.printStackTrace();
}
}
private
static
Map<Class<?>, Object> repos =
new
HashMap<Class<?>, Object>();
private
static
String baseUrl;
static
{
baseUrl =
"http://localhost:9000/ws/jaxrs"
;
}
@SuppressWarnings
(
"unchecked"
)
public
static
<T> T getServiceInstance(Class<T> clazz){
T t = (T) repos.get(clazz);
if
(t==
null
){
synchronized
(clazz) {
t = (T) repos.get(clazz);
if
(t==
null
){
t = JAXRSClientFactory.create(baseUrl, clazz, getJacksonJsonProvider());
Client client = WebClient.client(t);
WebClient.getConfig(client).getInInterceptors().add(
new
LoggingInInterceptor());
WebClient.getConfig(client).getInFaultInterceptors().add(
new
LoggingInInterceptor());
WebClient.getConfig(client).getOutFaultInterceptors().add(
new
LoggingOutInterceptor());
WebClient.getConfig(client).getOutInterceptors().add(
new
LoggingOutInterceptor());
client.accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_JSON).acceptEncoding(
"UTF-8"
);
repos.put(clazz, t);
}
}
}
return
t;
}
}
|
总结:
问题1:针对json格式使用jackson替换jettison库。针对xml格式,只要在指定泛型参数类上面同同过@XmlSeeAlso注解指定泛型参数类class即可。
问题2:通过ExceptionMapper接口和webapplicationexception异常实现,如果想更进一步可以加上ResponseExceptionMapper完成更舒坦的WebService设计
这里特别指出一下。MessageBodyReader、MessageBodyWriter、ExceptionMapper、webapplicationexception、XmlSeeAlso都是java规范中的api,ResponseExceptionMapper为cxf中的api。如果大家选择maven依赖管理cxf,注意cxf默认的jax-rs api依赖,其中2.7.4中默认依赖是javax.ws.rs-api-2.0-m10.jar,cxf2.5.10默认依赖是jsr311-api.1.1.1.jar。也就是说,要默认按照它依赖的jar,不要以为jax-rs 2.0的api还是m阶段,就降低api使用低版本正是版本jsr311-api.1.1.1.jar,这里在cxf中是有问题的。cxf官网上面明明说cxf目前实JAX-RS 1.1 and JAX-RS 1.0 (JSR-311),可实际已经开始支持jax-rs2版本,而jax-rs2 还没正式发布,所以cxf对jax-rs2实现自然就有问题。我开发时,被这里害惨啦,最终选2.5.10版本