thrift/swift:对swift2thrift-generator-cli IDL生成工具的改进

swift2thrift-generator-cli是thrift/swift提供的一个IDL文件命令行生成工具,它可以根据一个java服务接口类(interface,class)生成对应的IDL文件
对于基于java做thrift框架的开发项目来说,这可是个神器,如果你的服务端是java开发的,就不需要手工写IDL文件(反正打死我也是不会手写的,太多了),使用这个命令行工具,可以一秒钟生成IDL,再用另一个工具swift-generator-cli就可以将根据生成的IDL生成java client/service调用代码了。这个过程我在之前的一篇博文有详细介绍,参见《thrift:swift 命令行生成 IDL文件及Client java代码过程》

IDL是thrift的接口定义语言,有了IDL格式的接口定义脚本,就可以生成不同开发语言的thrift代码,官网说明参见 《Thrift interface description language》

问题描述

但是后续的开发过程中发现使用swift2thrift-generator-cli生成IDL有一个问题:
对于primitive的对象封装类型(Integer,Long,Boolean),不论是做为字段还是做为服务方法的参数,swift2thrift-generator-cli都把它当做primitive类型处理了。

比如一个服务方法:

public test(Integer arg);

生成thrift client代码时,对应的接口方法变成了

@ThriftMethod(value = "test")
public test(@ThriftField(value=1,name="arg",requiredness=requiredness.NONE) final int arg);

一个类型:

@ThriftStruct
public final TestBean{
    private Integer id;
    @ThriftField(1)
    public Integer getId(){
        return id;
    }
    @ThriftField
    public void setId(Integer id){
        this.id = id;
    }
}

生成thrift client代码时,对应的类变成了:

@ThriftStruct("TestBean")
public final TestBean{
    private int id;
    @ThriftField(value=1,name="id",requiredness=requiredness.NONE)
    public int getId(){
        return id;
    }
    @ThriftField
    public void setId(int id){
        this.id = id;
    }
}

仔细想想这是个大问题:比如我想传一个null参数,在这种情况下这就不可能了,
在很多情况下null并非完全没有意义,如果传一个0当做null,需要client/service双方约定好才行,而且很多情况下0有可能是个有意义的值。
换个别的值?还是有歧义的可能,所以无论如何应该在thrift这一层解决这个问题而不是让应用项目来解决
有没有解决办法?

手工解决办法

当然有,地球人都知道的,手工解决办法很简单在服务方法类定义加上requiredness.OPTIONAL定义,告诉swift2thrift-generator-cli这个字段是可选的。
比如上面的test服务方法可以改为

public test( @ThriftField(value=1,requiredness=requiredness.OPTIONAL)Integer arg);

这样在生成的thrift 接口代码中arg参数的类型就是希望的Integer。
如果你的服务接口很简单只有很少的方法,涉及的类也不多,那么这个办法,可以解决你的问题。

我需要自动解决办法

但是如果服务接口如果非常庞大,涉及的类也很多,手工维护这些属性标记就是个灾难。
很不幸,我遇到的就是这种情况,服务接口中有超过100个方法,还在增加中,涉及的类有十几个,加起来有上百个字段。。。有int,也有Integer(有的必须给值,有的可以为null)。手工去加这些属性太麻烦了,还非常容易出错。

怎么办呢?
从IDL生成工具swift2thrift-generator-cli入手改造它!
这就是本文的中心任务。

改造目标

swift2thrift-generator-cli源码入门,在此基础上修改swift2thrift-generator-cli生成IDL的逻辑,对于一个字段或参数,如果它是primitive类型,就指定为required,如果它是primitive对应的对象封装类型(wraptype),就指定为optional.

问题分析

ThriftFieldMetadata

通过分析swift的源码发现,不论是类的字段还是服务方法的参数,都是一个field,用com.facebook.swift.codec.Metadata.ThriftFieldMetadata这个类来描述的。

requiredness

在thrift IDL规范中每个field都可以指定必要性(requiredness),可以为optional(可选的),required(必须的),default(认)。
在IDL文件一个field如果是基本类型(Base Types,such as i32,i64,bool),且被定义为optional,那么生成的java代码中对应的类型就是该基本类型对应对象封装类型(Integer,Boolean),如果没有指定,那么它就会被生成基本类型对应的primitive类型(int,long,boolean)。
ThriftFieldMetadata中有一个枚举型(com.facebook.swift.codec.ThriftField.requiredness)字段requiredness就是指定该字段的必要性。

基本思路

了解了上面这个关键点,我的解决方案基本思路成形了:
ThriftFieldMetadata类写一个装饰类(decorator)或叫代理类,只需要重载getrequiredness()方法,在这方法中实现前面改造目标中描述的逻辑,根据该field的java type返回我们需要的requiredness。然后将所有对ThriftFieldMetadata的访问(读取,ThriftFieldMetadata是不可变对象)都重定义到这个代理类。这样,在生成IDL过程中对每个field获取requiredness就是我们希望的值。

decorator

decorator的实现并不复杂,全部代码如下(代码中用到了google guava提供的cache技术用于减少重复对象创建提高性能,真正核心关键的地方就是getrequiredness方法重载):

/** * {@link ThriftFieldMetadata}的代理类, * 重载{@link #getrequiredness()}方法,根据参数类型对返回值进行修改 * @author guyadong * */
@Immutable
public class DecoratorThriftFieldMetadata extends ThriftFieldMetadata {
    private static final Logger logger = Logger.getLogger(DecoratorThriftFieldMetadata.class.getName());
    private static Boolean primitiveOptional = null;
    /** * {@link DecoratorThriftFieldMetadata}缓存对象,* 保存每个{@link ThriftFieldMetadata}对应的{@link DecoratorThriftFieldMetadata}实例 */
    private static final LoadingCache<ThriftFieldMetadata,DecoratorThriftFieldMetadata> 
        FIELDS_CACHE = 
            CacheBuilder.newBuilder().build(
                    new CacheLoader<ThriftFieldMetadata,DecoratorThriftFieldMetadata>(){
                        @Override
                        public DecoratorThriftFieldMetadata load(ThriftFieldMetadata key) throws Exception {
                            return new DecoratorThriftFieldMetadata(key);
                        }});
    /** 将{@link ThriftFieldMetadata}转换为 {@link DecoratorThriftFieldMetadata}对象 */
    public static  final Function<ThriftFieldMetadata,ThriftFieldMetadata> 
        FIELD_TRANSFORMER = 
            new Function<ThriftFieldMetadata,ThriftFieldMetadata>(){
                @Nullable
                @Override
                public ThriftFieldMetadata apply(@Nullable ThriftFieldMetadata input) {
                    return null == input || input instanceof DecoratorThriftFieldMetadata  
                            ? input 
                            : FIELDS_CACHE.getUnchecked(input);
                }};
    private final Type javaType;
    private DecoratorThriftFieldMetadata(ThriftFieldMetadata input){
        super(
                input.getId(),input.getrequiredness(),input.getThriftType(),input.getName(),input.getType(),input.getInjections(),input.getConstructorInjection(),input.getmethodInjection(),input.getExtraction(),input.getCoercion());
        // 获取field的类型
        List<ThriftInjection> injections = getInjections();
        checkState(injections.size()>0,"invalid size of injections");
        ThriftInjection injection = injections.get(0);      
        if(injection instanceof ThriftParameterInjection){
            javaType = ((ThriftParameterInjection)injection).getJavaType();
        }else if(injection instanceof ThriftFieldInjection){
            javaType = ((ThriftFieldInjection)injection).getField().getType();
        }else{
            javaType = null;
            // 对于不支持的数据类型无法获取field类型,输出警告
            logger.warning(
                    String.format("UNSUPPORED TYPE %s,can't get Java Type. "
                            + "(不识别的ThriftInjection实例类型,无法实现requiredness转义)",null == injection? null : injection.getClass().getName()));
        }
    }
    /** 重载方法,实现 requiredness 转义 */
    @Override
    public requiredness getrequiredness() {
        requiredness requiredness = super.getrequiredness();
        checkState(requiredness.UNSPECIFIED != requiredness);
        // 当为primitive类型时,requiredness 为required
        // 当为primitive类型的Object封装类型时(Long,Integer,Boolean),requiredness为OPTIONAL
        if( !Boolean.FALSE.equals(primitiveOptional)
                && javaType instanceof Class<?>
                && requiredness == requiredness.NONE){
            Class<?> parameterClass = (Class<?>)javaType;
            if(parameterClass.isPrimitive()){
                requiredness = requiredness.required;
                // logger.info(String.format("%s %s",parameterClass.getSimpleName(),requiredness));
            }else if(Primitives.isWrapperType(parameterClass)){
                requiredness = requiredness.OPTIONAL;
                // logger.info(String.format("%s %s",requiredness));
            }
        }
        return requiredness;
    }
    /** * 设置optional标记<br> * 指定{@link #getrequiredness}方法调用时是否对primitive类型及其封装类型(Integer,Long)参数的返回值进行替换<br> * 认值:{@code true}<br> * 该方法只能被调用一次 * @param optional * @see #getrequiredness() * @throws IllegalStateException 方法已经被调用 */
    public static synchronized void setPrimitiveOptional(boolean optional) {
        checkState(null == DecoratorThriftFieldMetadata.primitiveOptional,"primitiveOptional is initialized already.");
        DecoratorThriftFieldMetadata.primitiveOptional = optional;
    }
}

偷天换日

有了上面的decorator,要让它发挥做用,还要做进一步的工作,需要用将原本对ThriftFieldMetadata的访问请求转向这个新的对象,以服务方法为例 ,我们同样需要写一个ThriftMethodMetadata的代理类。重载getParameters()方法在这里完成对象转换(请求重定向)。
代码如下:

/** * 重载{@link #getParameters()}方法,用{@link DecoratorThriftFieldMetadata}替换{@link ThriftFieldMetadata} * @author guyadong * */
@Immutable
public class ThriftMethodMetadataCustom extends ThriftMethodMetadata {
    private final List<ThriftFieldMetadata> parameters;
    public ThriftMethodMetadataCustom(String serviceName,Method method,ThriftCatalog catalog)
    {
        super(serviceName,method,catalog);
        parameters = Lists.transform(super.getParameters(),DecoratorThriftFieldMetadata.requirednessTransformer);
        // 这里用到了定义在DecoratorThriftFieldMetadata 中的 Function常量
    }

    @Override
    public List<ThriftFieldMetadata> getParameters()
    {
        // 返回转换成DecoratorThriftFieldMetadata类型的参数对象表
        return parameters;
    }
}

对于ThriftStruct对象(也就是我们在项目中自定义的java bean)。同样也要做上面类型的替换,需要对ThriftStructMetadata类的所有涉及访问其中的ThriftFieldMetadata对象的getField系列方法进行重载:

/** * {@link ThriftStructMetadata}的代理类<br> * 重载所有{@link ThriftFieldMetadata}相关方法 * @author guyadong * */
@Immutable
public class DecoratorThriftStructMetadata extends ThriftStructMetadata {
    /** {@link DecoratorThriftStructMetadata}缓存对象,* 保存每个{@link ThriftStructMetadata}对应的{@link DecoratorThriftStructMetadata}实例 */
    private static final LoadingCache<ThriftStructMetadata,DecoratorThriftStructMetadata> 
        STRUCTS_CACHE = 
            CacheBuilder.newBuilder().build(
                    new CacheLoader<ThriftStructMetadata,DecoratorThriftStructMetadata>(){
                        @Override
                        public DecoratorThriftStructMetadata load(ThriftStructMetadata key) throws Exception {
                            return new DecoratorThriftStructMetadata(key);
                        }});
    /** 将{@link ThriftStructMetadata}转换为 {@link DecoratorThriftStructMetadata}对象 */
    public static final Function<ThriftStructMetadata,ThriftStructMetadata> 
        STRUCT_TRANSFORMER = new Function<ThriftStructMetadata,ThriftStructMetadata>(){
            @Nullable
            @Override
            public ThriftStructMetadata apply(@Nullable ThriftStructMetadata input) {
                return null == input || input instanceof DecoratorThriftStructMetadata
                        ? input
                        : STRUCTS_CACHE.getUnchecked(input);
            }};
    private DecoratorThriftStructMetadata(ThriftStructMetadata input){
        super(input.getStructName(),input.getStructType(),input.getBuilderType(),input.getMetadataType(),input.getBuilderMethod(),input.getDocumentation(),ImmutableList.copyOf(input.getFields()),input.getmethodInjections());
    }
    @Override
    public ThriftFieldMetadata getField(int id) {
        return DecoratorThriftFieldMetadata.FIELD_TRANSFORMER.apply(super.getField(id));
    }

    @Override
    public Collection<ThriftFieldMetadata> getFields() {
        return Collections2.transform(super.getFields(),DecoratorThriftFieldMetadata.FIELD_TRANSFORMER);
    }

    @Override
    public Collection<ThriftFieldMetadata> getFields(FieldKind type) {
        return Collections2.transform(super.getFields(type),DecoratorThriftFieldMetadata.FIELD_TRANSFORMER);
    }

}

按照 上面的思路,以此类推要换掉在IDL生成过程中涉及ThriftFieldMetadata访问所有环节。就可以了。

完整代码

限于篇幅,这里不再贴更多代码,需要完整的代码可以访问码云上的Git仓库:
https://gitee.com/l0km/idl-generator

需要用maven编译,下载代码后执行mvn package就可以生成一个uber-jar.
执行下面的命令就可以看到用法说明。

java -jar idl-generator-cli-1.7-standalone.jar

同时上面的gitee项目地址中还包含对应的maven插件,更多详细信息参见README.md
项目的二进制文件jar包已经上传到maven中央仓库,
命令行工具

<dependency>
    <groupId>com.gitee.l0km</groupId>
    <artifactId>swift2thrift-maven-plugin</artifactId>
    <version>1.7</version>
</dependency>

maven 插件

<dependency>
    <groupId>com.gitee.l0km</groupId>
    <artifactId>swift2thrift-maven-plugin</artifactId>
    <version>1.7</version>
</dependency>

如果你只想直接下载jar包运行,可以从maven中央仓库下载:
http://central.maven.org/maven2/com/gitee/l0km/idl-generator-cli/1.7/idl-generator-cli-1.7-standalone.jar

后记

那现在可以传递一个类型为Integer的null值到服务端了么?

说实话,还是不行…

啊?!!!那不是白干了?那你废半天劲写这一大堆文字干嘛?说说为什么不行啊?

关于为什么,可以参见我的上篇博文《thrift/swift:ThriftMethodProcessor代码分析》。 知道了原因,你就明白了:服务端也需要改造,改造的思路参照本文的思路就很容易想明白。有时间的话再我再就这个问题写个博文

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


软件简介:蓝湖辅助工具,减少移动端开发中控件属性的复制和粘贴.待开发的功能:1.支持自动生成约束2.开发设置页面3.做一个浏览器插件,支持不需要下载整个工程,可即时操作当前蓝湖浏览页面4.支持Flutter语言模板生成5.支持更多平台,如Sketch等6.支持用户自定义语言模板
现实生活中,我们听到的声音都是时间连续的,我们称为这种信号叫模拟信号。模拟信号需要进行数字化以后才能在计算机中使用。目前我们在计算机上进行音频播放都需要依赖于音频文件。那么音频文件如何生成的呢?音频文件的生成过程是将声音信息采样、量化和编码产生的数字信号的过程,我们人耳所能听到的声音频率范围为(20Hz~20KHz),因此音频文件格式的最大带宽是20KHZ。根据奈奎斯特的理论,音频文件的采样率一般在40~50KHZ之间。奈奎斯特采样定律,又称香农采样定律。...............
前言最近在B站上看到一个漂亮的仙女姐姐跳舞视频,循环看了亿遍又亿遍,久久不能离开!看着小仙紫姐姐的蹦迪视频,除了一键三连还能做什么?突发奇想,能不能把舞蹈视频转成代码舞呢?说干就干,今天就手把手教大家如何把跳舞视频转成代码舞,跟着仙女姐姐一起蹦起来~视频来源:【紫颜】见过仙女蹦迪吗 【千盏】一、核心功能设计总体来说,我们需要分为以下几步完成:从B站上把小姐姐的视频下载下来对视频进行截取GIF,把截取的GIF通过ASCII Animator进行ASCII字符转换把转换的字符gif根据每
【Android App】实战项目之仿抖音的短视频分享App(附源码和演示视频 超详细必看)
前言这一篇博客应该是我花时间最多的一次了,从2022年1月底至2022年4月底。我已经将这篇博客的内容写为论文,上传至arxiv:https://arxiv.org/pdf/2204.10160.pdf欢迎大家指出我论文中的问题,特别是语法与用词问题在github上,我也上传了完整的项目:https://github.com/Whiffe/Custom-ava-dataset_Custom-Spatio-Temporally-Action-Video-Dataset关于自定义ava数据集,也是后台
因为我既对接过session、cookie,也对接过JWT,今年因为工作需要也对接了gtoken的2个版本,对这方面的理解还算深入。尤其是看到官方文档评论区又小伙伴表示看不懂,所以做了这期视频内容出来:视频在这里:本期内容对应B站的开源视频因为涉及的知识点比较多,视频内容比较长。如果你觉得看视频浪费时间,可以直接阅读源码:goframe v2版本集成gtokengoframe v1版本集成gtokengoframe v2版本集成jwtgoframe v2版本session登录官方调用示例文档jwt和sess
【Android App】实战项目之仿微信的私信和群聊App(附源码和演示视频 超详细必看)
用Android Studio的VideoView组件实现简单的本地视频播放器。本文将讲解如何使用Android视频播放器VideoView组件来播放本地视频和网络视频,实现起来还是比较简单的。VideoView组件的作用与ImageView类似,只是ImageView用于显示图片,VideoView用于播放视频。...
采用MATLAB对正弦信号,语音信号进行生成、采样和内插恢复,利用MATLAB工具箱对混杂噪声的音频信号进行滤波
随着移动互联网、云端存储等技术的快速发展,包含丰富信息的音频数据呈现几何级速率增长。这些海量数据在为人工分析带来困难的同时,也为音频认知、创新学习研究提供了数据基础。在本节中,我们通过构建生成模型来生成音频序列文件,从而进一步加深对序列数据处理问题的了解。
基于yolov5+deepsort+slowfast算法的视频实时行为检测。1. yolov5实现目标检测,确定目标坐标 2. deepsort实现目标跟踪,持续标注目标坐标 3. slowfast实现动作识别,并给出置信率 4. 用框持续框住目标,并将动作类别以及置信度显示在框上
数字电子钟设计本文主要完成数字电子钟的以下功能1、计时功能(24小时)2、秒表功能(一个按键实现开始暂停,另一个按键实现清零功能)3、闹钟功能(设置闹钟以及到时响10秒)4、校时功能5、其他功能(清零、加速、星期、八位数码管显示等)前排提示:前面几篇文章介绍过的内容就不详细介绍了,可以看我专栏的前几篇文章。PS.工程文件放在最后面总体设计本次设计主要是在前一篇文章 数字电子钟基本功能的实现 的基础上改编而成的,主要结构不变,分频器将50MHz分为较低的频率备用;dig_select
1.进入官网下载OBS stdioOpen Broadcaster Software | OBS (obsproject.com)2.下载一个插件,拓展OBS的虚拟摄像头功能链接:OBS 虚拟摄像头插件.zip_免费高速下载|百度网盘-分享无限制 (baidu.com)提取码:6656--来自百度网盘超级会员V1的分享**注意**该插件必须下载但OBS的根目录(应该是自动匹配了的)3.打开OBS,选中虚拟摄像头选择启用在底部添加一段视频录制选择下面,进行录制.
Meta公司在9月29日首次推出一款人工智能系统模型:Make-A-Video,可以从给定的文字提示生成短视频。基于**文本到图像生成技术的最新进展**,该技术旨在实现文本到视频的生成,可以仅用几个单词或几行文本生成异想天开、独一无二的视频,将无限的想象力带入生活
音频信号叠加噪声及滤波一、前言二、信号分析及加噪三、滤波去噪四、总结一、前言之前一直对硬件上的内容比较关注,但是可能是因为硬件方面的东西可能真的是比较杂,而且需要渗透的东西太多了,所以学习进展比较缓慢。因为也很少有单纯的硬件学习研究,总是会伴随着各种理论需要硬件做支撑,所以还是想要慢慢接触理论学习。但是之前总找不到切入点,不知道从哪里开始,就一直拖着。最近稍微接触了一点信号处理,就用这个当作切入点,开始接触理论学习。二、信号分析及加噪信号处理选用了matlab做工具,选了一个最简单的语音信号处理方
腾讯云 TRTC 实时音视频服务体验,从认识 TRTC 到 TRTC 的开发实践,Demo 演示& IM 服务搭建。
音乐音频分类技术能够基于音乐内容为音乐添加类别标签,在音乐资源的高效组织、检索和推荐等相关方面的研究和应用具有重要意义。传统的音乐分类方法大量使用了人工设计的声学特征,特征的设计需要音乐领域的知识,不同分类任务的特征往往并不通用。深度学习的出现给更好地解决音乐分类问题提供了新的思路,本文对基于深度学习的音乐音频分类方法进行了研究。首先将音乐的音频信号转换成声谱作为统一表示,避免了手工选取特征存在的问题,然后基于一维卷积构建了一种音乐分类模型。
C++知识精讲16 | 井字棋游戏(配资源+视频)【赋源码,双人对战】
本文主要讲解如何在Java中,使用FFmpeg进行视频的帧读取,并最终合并成Gif动态图。
在本篇博文中,我们谈及了 Swift 中 some、any 关键字以及主关联类型(primary associated types)的前世今生,并由浅及深用简明的示例向大家讲解了它们之间的奥秘玄机。