cxf相关知识点记录

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->StringLocalDateTime是类属性

2.浏览器->服务器:String->LocalDateTimeLocalDateTime是类属性,且通过请求体传输

3.浏览器->服务器:String->LocalDateTimeLocalDateTime是类属性,且通过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=1person的信息,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的,它的处理逻辑是先使用反射创建一个参数对象,然后调用对应queryParamsetter方法将值设置进去。此时就有一个巨大的问题,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>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值