Swift   发布时间:2022-03-31  发布网站:大佬教程  code.js-code.com
大佬教程收集整理的这篇文章主要介绍了thrift/swift:对swift2thrift-generator-cli IDL生成工具的改进大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。

概述

swift2thrift-generator-cli是thrift/swift提供的一个IDL文件命令行生成工具,它可以根据一个java服务接口类(interface,class)生成对应的IDL文件。 对于基于java做thrift框架的开发项目来说,这可是个神器,如果你的服务端是java开发的,就不需要手工写IDL文件(反正打死我也是不会手写的,太多了),使用这个命令行工具,可以一秒钟生成ID

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代码过程》

问题描述

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

比如一个服务方法:

@H_673_24@public test(Integer arg);

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

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

一个类型:

@H_673_24@@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代码时,对应的类变成了:

@H_673_24@@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服务方法可以改为

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

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

我需要自动解决办法

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

怎么办呢?
从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方法重载):

@H_673_24@/** * {@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()方法在这里完成对象转换(请求重定向)。
代码如下:

@H_673_24@/** * 重载{@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系列方法进行重载:

@H_673_24@/** * {@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

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

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

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

@H_187_579@maven 插件

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

后记

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

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

大佬总结

以上是大佬教程为你收集整理的thrift/swift:对swift2thrift-generator-cli IDL生成工具的改进全部内容,希望文章能够帮你解决thrift/swift:对swift2thrift-generator-cli IDL生成工具的改进所遇到的程序开发问题。

如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。