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

来源:互联网 发布:arm linux centos 编辑:程序博客网 时间:2024/06/05 09:29

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);

一个类型:

@ThriftStructpublic 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, name="arg", 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,Long,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 * */@Immutablepublic 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", parameterClass.getSimpleName(),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 * */@Immutablepublic 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 * */@Immutablepublic 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.getConstructorInjection(),                 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代码分析》。
知道了原因,你就明白了:服务端也需要改造,改造的思路参照本文的思路就很容易想明白。有时间的话再我再就这个问题写个博文

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 没网steam要登录怎么办 电脑有gta还需要安装怎么办 孤岛惊魂5卡顿怎么办 酷匠密码忘记了怎么办 战地1有时候卡死怎么办 战地一fps太低怎么办 Dnf与系统不兼容怎么办 使命召唤7显示w怎么办 战地3王者太卡怎么办 合金装备5消音器没了怎么办 红警基地没了怎么办 玩战地1帧数太低怎么办 战地1点游戏不开怎么办 ios耳机孔坏了怎么办? 吃泻药都不排便怎么办 上司离职了我该怎么办 我上司要辞职我怎么办 一方坚决不同意离婚我该怎么办 模拟农场车翻了怎么办 手机退出键坏了怎么办 dnf邮件发错了怎么办 手机提示sd卡已损坏怎么办 解压包文件数据损坏该怎么办 电脑被压缩后电脑打不开怎么办 眼睛里进了飞虫怎么办 虫子飞到眼睛里怎么办 云电脑pc版双鼠标怎么办 电脑蓝屏代码7f怎么办 笔记本电脑蓝屏开不了机怎么办 装xp系统后蓝屏怎么办 一键ghost断电了怎么办 如何防止cpu降频怎么办 win7 64位系统不兼容怎么办 网页无法加载打印机插件怎么办 微信提示安装了插件怎么办 电脑上不了网怎么办 win10 win10电脑突然没网了怎么办 笔记本玩战争前线发热严重怎么办 玩战争前线总是闪退怎么办 uu加速器卡在29怎么办 新ipad下载不了东西怎么办