cxf
相关知识点记录
1 rest
风格相关依赖
<!-- rs包 -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxrs</artifactId>
<version>3.0.1</version>
</dependency>
<!-- json数据需要使用的jar包 -->
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>2.11.1</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-rs-extension-providers</artifactId>
<version>3.0.1</version>
</dependency>
<!-- 转换json工具包,被extension providers 依赖 -->
<dependency>
<groupId>org.codehaus.jettison</groupId>
<artifactId>jettison</artifactId>
<version>1.3.7</version>
</dependency>
<!-- 跨域工具包,不涉及可直接去掉 -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-rs-security-cors</artifactId>
<version>3.0.1</version>
</dependency>
上面这个跨域工具包其实用的不多,我们在测试的时候一般都是自己写一个过滤器,允许所有的跨域请求。上面工具包中的过滤器配置比较多而且杂,后面专门会研究这个。
2 特殊字段类型的json
转换
我们引入了jackson
的包,所以对应jackson
不支持的就需要自己定义了,下面就以LocalDateTime
举个例子。
涉及
3
种类型转换1.服务器->浏览器:也就是
LocalDateTime
->String
,LocalDateTime
是类属性2.浏览器->服务器:
String
->LocalDateTime
,LocalDateTime
是类属性,且通过请求体传输3.浏览器->服务器:
String
->LocalDateTime
,LocalDateTime
是类属性,且通过query
传输
2.1 LocalDateTime
->yyyy/MM/dd HH:mm:ss
首先来说LocalDateTime
->yyyy/MM/dd HH:mm:ss
,这种类型的转换比较容易实现。举个例子:
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String name;
private LocalDateTime birthday;
}
简单一点,这个类就3
个属性。现在我们在数据库中查到了一个id=1
的person
的信息,service
直接将该person
对象返回,返回的过程中默认是使用jackson
将对象序列化为json
字符串的,此时存在一个问题,jackson
是不支持将LocalDateTime
转换为yyyy/MM/dd HH:mm:ss
格式的字符串。但是没事,jackson
提供了对应的措施让我们可以自定义转换–JsonSerializer
。我们只需要继承该抽象类,重写里面的具体转换逻辑即可。
public class LocalDateTimeToJsonSerializer extends JsonSerializer<LocalDateTime> {
private static Logger logger= LoggerFactory.getLogger(LocalDateTimeToJsonSerializer.class);
/**
* Method that can be called to ask implementation to serialize
* values of type this serializer handles.
*
* @param value Value to serialize; can <b>not</b> be null.
* @param gen Generator used to output resulting Json content
* @param serializers Provider that can be used to get serializers for
*/
@Override
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (ObjectUtils.isNotEmpty(value)){
// 将localDateTime格式化为yyyy/MM/dd HH:mm:ss的字符串
String format = value.format(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"));
logger.info("localDateTime formatt after :{}",format);
// 写入
gen.writeString(format);
}
}
}
定义了这样一个类还不够,还需要使用注解@JsonSerialize(using = LocalDateTimeToJsonSerializer.class)
标记某个属性需要使用该方式的序列化。
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String name;
@JsonSerialize(using = LocalDateTimeToJsonSerializer.class)
private LocalDateTime birthday;
}
2.2 yyyy/MM/dd HH:mm:ss
->LocalDateTime
(请求体传输)
该种方式也比较简单,因为cxf
也是借助jackson
实现将json
字符串转化为对象的,所以我们可以直接使用
jackson
自带的转换工具实现自定义转换。我们只需要继承JsonDeserializer
抽象类,重写里面的具体转换逻辑即可。
public class JsonToLocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
private static Logger logger= LoggerFactory.getLogger(JsonToLocalDateTimeDeserializer.class);
/**
* Method that can be called to ask implementation to deserialize
* JSON content into the value type this serializer handles.
* Returned instance is to be constructed by method itself.
* <p>
* Pre-condition for this method is that the parser points to the
* first event that is part of value to deserializer (and which
* is never JSON 'null' literal, more on this below): for simple
* types it may be the only value; and for structured types the
* Object start marker or a FIELD_NAME.
* </p>
* <p>
* The two possible input conditions for structured types result
* from polymorphism via fields. In the ordinary case, Jackson
* calls this method when it has encountered an OBJECT_START,
* and the method implementation must advance to the next token to
* see the first field name. If the application configures
* polymorphism via a field, then the object looks like the following.
* <pre>
* {
* "@class": "class name",
* ...
* }
* </pre>
* Jackson consumes the two tokens (the <tt>@class</tt> field name
* and its value) in order to learn the class and select the deserializer.
* Thus, the stream is pointing to the FIELD_NAME for the first field
* after the @class. Thus, if you want your method to work correctly
* both with and without polymorphism, you must begin your method with:
* <pre>
* if (p.currentToken() == JsonToken.START_OBJECT) {
* p.nextToken();
* }
* </pre>
* This results in the stream pointing to the field name, so that
* the two conditions align.
* <p>
* Post-condition is that the parser will point to the last
* event that is part of deserialized value (or in case deserialization
* fails, event that was not recognized or usable, which may be
* the same event as the one it pointed to upon call).
* <p>
* Note that this method is never called for JSON null literal,
* and thus deserializers need (and should) not check for it.
*
* @param p Parsed used for reading JSON content
* @param ctxt Context that can be used to access information about
* this deserialization activity.
* @return Deserialized value
*/
@Override
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String value = p.getValueAsString();
if (StringUtils.isEmpty(value)){
logger.info("json to localDateTime is null");
return null;
}
// 将yyyy/MM/dd HH:mm:ss格式字符串转换为LocalDateTime
LocalDateTime localDateTime = LocalDateTime.parse(value, DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"));
return localDateTime;
}
}
然后用注解@JsonSerialize(using = LocalDateTimeToJsonSerializer.class)
标记这个属性需要使用该方式的反序列化。
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String name;
@JsonDeserialize(using = JsonToLocalDateTimeDeserializer.class)
private LocalDateTime birthday;
}
2.3 yyyy/MM/dd HH:mm:ss
->LocalDateTime
(query
传输)
如果是get
请求,只能通过url
传输数据,那怎么办呢?这种方式cxf
是不会使用jackson
的,它的处理逻辑是先使用反射创建一个参数对象,然后调用对应queryParam
的setter
方法将值设置进去。此时就有一个巨大的问题,setter
方法的参数是LocalDateTime
类型,而queryParam
则是String
类型,这样setter
方法能调用成功吗?很明显,绝对会报错,那么如何解决呢?cxf
提供了一个ParamConverter
接口,这个东西和JsonSerializer
差不多,cxf
可以在调用setter
方法之前将先调用它将queryParam
参数值转换一下,然后再调用setter
方法。举个例子
public class LocalDateTimeParamConverter implements ParamConverter<LocalDateTime> {
private static Logger logger= LoggerFactory.getLogger(LocalDateTimeParamConverter.class);
@Override
public LocalDateTime fromString(String value) {
if(StringUtils.isEmpty(value)){
return null;
}
LocalDateTime localDateTime = LocalDateTime.parse(value.trim(), DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"));
return localDateTime;
}
@Override
public String toString(LocalDateTime value) {
if (ObjectUtils.isNotEmpty(value)){
String format = value.format(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"));
logger.info("localDateTime formatt after :{}",format);
return format;
}
return null;
}
}
从方法名就可以看出fromString
是将String
格式化为我们需要的对象,而方法里面的处理逻辑也正是这样。但是光定义了一个转换器不够啊,cxf
怎么知道你定义了一个这样的转换器呢?这就引入了第二个接口ParamConverterProvider
,你可以理解为一个注册中心,负责管理ParamConverter
转换器。
// 必须加上这个注解,cxf才能识别它是注册中心
@Provider
public class LocalDateTimeParamConverterProvider implements ParamConverterProvider {
private static LocalDateTimeParamConverter localDateTimeParamConverter = new LocalDateTimeParamConverter();
@Override
public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation[] annotations) {
// 只有queryParam对应属性的类型为LocalDateTime,
// 才会使用LocalDateTimeParamConverter进行转换
if (rawType.isAssignableFrom(LocalDateTime.class)) {
return (ParamConverter<T>) localDateTimeParamConverter;
}
return null;
}
}
有了上面的配置还是不够,cxf
还是不知道存在这个LocalDateTimeParamConverterProvider
,你需要如下配置,指定在哪些服务中用到这个注册中心。
<jaxrs:server address="/">
<jaxrs:serviceBeans>
<ref bean="testService"></ref>
</jaxrs:serviceBeans>
<jaxrs:inInterceptors>
<bean class="org.apache.cxf.interceptor.LoggingInInterceptor"></bean>
</jaxrs:inInterceptors>
<jaxrs:outInterceptors>
<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor"></bean>
</jaxrs:outInterceptors>
<jaxrs:providers>
<!-- jackson转换器 -->
<bean class="com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider"/>
<!-- 自定义转换器注册中心 -->
<bean class="org.example.it.convert.LocalDateTimeParamConverterProvider"/>
</bean>
</jaxrs:providers>
</jaxrs:server>