文档章节

记一个小小的转换工具的开发:FastConverter

wanxiangming
 wanxiangming
发布于 2018/11/13 17:26
字数 3274
阅读 59
收藏 1

背景

介绍一个新写的小东西,叫FastConverter,叫这个名字是因为,它最初是被设计用来将服务器返回给前端的数据实体转换为json字符串的。

需求背景是:服务器经过一系列计算后,最终数据被存放在一个数据实体中,经过toJSON化,输出到前端。但输出时我们对数据有一些格式化,或自定制化的需求,比如,数据实体中的Date,最终输出可能是时间戳,也可能是“yyyy-MM-dd”;数据实体中的用以表示金额的BigDecimal,在服务器端我们用元做单位,带小数点,输出时我们想让它变成以分为单位,不带小数点;用户敏感信息打码(用户名,身份证号,手机号等)等等。总之就是,直接将数据实体toJSON,不能满足我们的需求,在toJSON的同时,我们需要对数据做一些转换。

设计思路

很多初学者设计一个工具的时候,思路很散,做出来的东西不成体系。比如上面所述的功能,有的人肯定是一个 "XxxTool"类 或者 "XxxUtils"类 就实现了。

但这种散件非常丑陋,类的抽象和类的接口息息相关, "XxxTool"类 或者 "XxxUtils"类最大的问题是,它无法有效且内聚的描述自己的抽象,它的接口大多数情况下各司其职,这样的类一定违背开闭原则,依赖倒转原则,也违背内聚性。实在点说就是:1,当客户端程序员去使用这些工具类时,他们会发现,这个类有好多方法;2,每个方法似乎都是一个独立的功能点;3,当他发现缺少他需要的功能时,他会不知所措,到底如何修改这个庞大的类;4,时间久了,这个类一定是无法维护的(这个复杂的私有方法是干什么的???为什么只为那个公共方法提供服务???这个私有域又是干什么的???我可以修改它吗???还有哪些地方使用了它???算了,我自己加个新的域来实现我的功能吧);5,会有很多类依赖(紧耦合)这个工具类。

如果你在开发系统底层的时候不在乎这些小问题,等系统变得庞杂起来时,它会让你寸步难行,这是很明显的弊端,但我很惊讶如此多的人对它视而不见(也许是因为教科书上不教这些小细节吧)。

第一步,接口开发

既然是转换数据,接口的设计挺自然的:

public interface Converter<T, K> {
    K convert(T value, String tip) throws ConvertException;

    K convert(T value) throws ConvertException;

    boolean supports(T value);
}

supports这个方法的设计学习了spring框架,后续开发也证明,它非常有用。值得一提的是,supports方法接收的是一个对象,而不是Class<T>。我在开发的时候曾经将它修改为Class<T>过,但后来发现,这样做局限性很大,而且被转换对象一定是事先存在了的,此处不需要使用Class<T>来做先于数据的判断;再者,如果是接收Class<T>,任何泛型T类型一致的转换器将无法共存(后续讲解)。

convert方法中有一个String类型的tip参数,它是用来赋予转换器一定灵活性而引入的。比如要将Date转换为 “yyyy-MM-dd” 和 “yyyy.MM.dd” 你只需要一个转换器就能实现。

抛出的异常:

public class ConvertException extends Exception {
    public ConvertException(String message) {
        super(message);
    }
}

有了转换器,我们还需要一个转换器过滤器,因为在我的思路里,我们可以将多个转换器注册到一个容器中,让容器自动根据supports过滤出适合某种数据的转换器(后续你将看到,它非常有用)。

public interface ConverterFilter {
    <T> Converter getConverter(T value);
}

由于泛型擦除机制的存在,该接口就算限定返回值是Converter<T, K> ,你也无法获取到正确的能将T -> K 的Converter<T, K>转换器,你获取到的仅仅是Converter。所以此处定义的返回值是Converter,而不是Converter<T, K>。

第二步,注解开发

我在需求中提到了,要将数据实体中的数据做一些格式化或自定制化的转换。实现这一步我采用的是默认转换加自定义转换并存的策略。自定义转换如何工作?使用注解!通过在域(类中声明的字段)上打上注解来告知系统,此域如何进行转换。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public [@interface](https://my.oschina.net/u/996807) FieldConverter {
    String tip() default "";

    Class<? extends Converter> converter();
}

这个注解很简单,就不赘述了。

第三步,注解消化器:

public class BeanToMapConverterHandler extends AbstractFilterBaseConverterHandler<Object, Map<String,Object>> {

    public BeanToMapConverterHandler(ConverterFilter converterFilter) {
        super(converterFilter);
    }

    [@Override](https://my.oschina.net/u/1162528)
    protected Map<String, Object> converting(Object value, String tip) throws ConvertException {
        Map<String, Object> map = new HashMap<>();

        for (Field field : value.getClass().getDeclaredFields()) {
            // 获取域值
            Object fieldValue;
            try {
                PropertyDescriptor pd = new PropertyDescriptor(field.getName(), value.getClass());
                Method reader = pd.getReadMethod();
                if (reader != null) {
                    fieldValue = reader.invoke(value);
                } else {
                    continue;
                }
            } catch (IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
                throw new ConvertException("BeanToMapConverterHandler对数据转换过程中发生异常:" + e.getMessage());
            } catch (IntrospectionException e) {
                continue;
            }

            // 转换域值
            Object mapValue = fieldValue;
            FieldConverter annotation = field.getAnnotation(FieldConverter.class);
            if (annotation == null) {
                Converter converter = this.getConverter(fieldValue);
                if (converter != null) {
                    mapValue = converter.convert(fieldValue, tip);
                }
            } else {
                try {
                    mapValue = annotation.converter().newInstance().convert(fieldValue, annotation.tip());
                } catch (InstantiationException | IllegalAccessException e) {
                    e.printStackTrace();
                    throw new ConvertException(e.getMessage());
                }
            }

            map.put(field.getName(), mapValue);
        }

        return map;
    }

    [@Override](https://my.oschina.net/u/1162528)
    public boolean supports(Object value) {
        return value != null;
    }
}

这个类继承自AbstractFilterBaseConverterHandler,下面贴出它的代码:

public abstract class AbstractFilterBaseConverterHandler<T, K> extends AbstractConverterHandler<T, K> {

    private ConverterFilter converterFilter;

    public AbstractFilterBaseConverterHandler(ConverterFilter converterFilter) {
        this.converterFilter = converterFilter;
    }

    protected Converter getConverter(Object value) {
        return converterFilter.getConverter(value);
    }

    protected ConverterFilter getConverterFilter() {
        return converterFilter;
    }
}

AbstractFilterBaseConverterHandler继承自AbstractConverterHandler,下面贴出AbstractConverterHandler的代码:

public abstract class AbstractConverterHandler<T, K> implements Converter<T, K> {
    private String tip;

    public AbstractConverterHandler() {
        this.tip = "";
    }

    public AbstractConverterHandler(String tip) {
        this.tip = tip;
    }

    protected abstract K converting(T value, String tip) throws ConvertException;

    @Override
    public K convert(T value) throws ConvertException {
        return this.convert(value, this.tip);
    }

    @Override
    public K convert(T value, String tip) throws ConvertException {
        if (!this.supports(value)) {
            throw new ConvertException(this.getClass().getName() + " 无法转换数据 " + value);
        }

        return this.converting(value, tip);
    }
}

抽象类AbstractConverterHandler实现了Converter接口的两个convert方法,因此,实现你自己的Converter只需要继承这个抽象类并实现converting和supports两个方法就可以。

这个抽象类主要是为了填充默认的tip,以及实现两个convert方法的调用逻辑。可以看出来,最终客户端程序员使用一个Converter的时候,对convert的调用最终都会落在

public K convert(T value, String tip) throws ConvertException {}

这个方法上,而它会自动调用一次supports,并抛出异常。这给Converter的编写带来了很多便捷性和一致性。我的所有Converter都是通过继承AbstractConverterHandler实现的。

AbstractFilterBaseConverterHandler这个抽象类是用来定义一个依赖ConverterFilter的Converter的。后面我会介绍到,有一类Converter是需要依赖ConverterFilter的,例如BeanToMapConverterHandler。

BeanToMapConverterHandler这个Converter是用来将bean转换为Map的,因为Map到JSON对象的转换结果,等同于Object到JSON对象的转换结果,所以我先将数据实体转换为Map。从BeanToMapConverterHandler中可以看出来,每次获取一个域值,我会判断,它是否使用FieldConverter注解标记了转换器,如果有,用标记的转换器转换域值,否则我会将它扔到ConverterFilter中去过滤出一个转换器来,用过滤出来的转换器转换域值。

你可能已经看出来了,ConverterFilter中注册的,就是默认转换器

由于向ConverterFilter注册多少转换器,什么转换器是由你决定的,所以BeanToMapConverterHandler这个转换器的具体行为,就是可变的,由ConverterFilter决定的。

第四步,ConverterFilter开发

转换器过滤器:

public class ResponseConverterFilter extends AbstractConverterFilter {
    @Override
    protected void initConverters(List<Converter<?, ?>> converters) {
        converters.add(new StringConverterHandler());
        converters.add(new NumberToStringConverterHandler());
        converters.add(new BooleanToNumberStringConverterHandler());
        converters.add(new DateToTimeStampStringConverterHandler());
        converters.add(new EnumValueConverterHandler());
        converters.add(new NullToEmptyStringConverterHandler());

        converters.add(new ArrayToListConverterHandler(this));
        converters.add(new CollectionToListConverterHandler(this));
        converters.add(new MapToMapConverterHandler(this));

        converters.add(new BeanToMapConverterHandler(this));
    }
}

抽象转换器过滤器:

public abstract class AbstractConverterFilter implements ConverterFilter {

    private List<Converter<?, ?>> converters;

    public AbstractConverterFilter() {
        converters = new ArrayList<>();
        this.initConverters(converters);
    }

    protected abstract void initConverters(List<Converter<?, ?>> converters);

    @Override
    public <T> Converter getConverter(T value) {
        for (Converter converter : converters) {
            try {
                if (converter.supports(value)) {
                    return converter;
                }
            } catch (ClassCastException ignored) {

            }
        }

        return null;
    }
}

老套路,先是一个抽象类(AbstractConverterFilter)完成基本操作,然后是一个具体的实现类(ResponseConverterFilter)。

在ResponseConverterFilter中,你可以看到我所注册的默认转换器。前六个是Converter,后四个是基于ConverterFilter的Converter。在此我不一一介绍每个Converter是干什么的,只着重介绍一下CollectionToListConverterHandler这个基于ConverterFilter的Converter。

重要的基于ConverterFilter的Converter

首先问一个问题:如果数据实体中的某个字段是容器(List,Set,Map...)应该怎么办?

一定是需要对容器中的数据做转换处理的,否则输出到前端的数据就不符合需求。那么怎么对容器中的数据做转换呢?对容器中的数据的转换也要按照统一的规则走(使用默认转换器转换,如果是Bean,被FieldConverter注解的域要走指定的转换器)。

这一步就由三个特殊的转换器实现:ArrayToListConverterHandler,CollectionToListConverterHandler,MapToMapConverterHandler。这里我只介绍CollectionToListConverterHandler,其他两个功能和它类似。先看代码:

public class CollectionToListConverterHandler<T, K> extends AbstractFilterBaseConverterHandler<Collection<T>, List<K>> {

    public CollectionToListConverterHandler(ConverterFilter converterFilter) {
        super(converterFilter);
    }

    @Override
    protected List<K> converting(Collection<T> value, String tip) throws ConvertException {
        ArrayList<K> list = new ArrayList<>();

        for (T obj : value) {
            Converter converter = this.getConverter(obj);
            if (converter == null) {
                throw new ConvertException("没有转换器可以处理" + obj);
            } else{
                list.add((K) converter.convert(obj, tip));
            }
        }

        return list;
    }

    @Override
    public boolean supports(Collection<T> value) {
        return value != null;
    }
}

这个基于ConverterFilter的Converter从容器中取出元素,将每个元素放到ConverterFilter中去筛选出合适的Converter,然后用它转换数据。

注意观察ResponseConverterFilter中的:

converters.add(new CollectionToListConverterHandler(this));

这个this很关键,它使得整个系统可以处理容器嵌套,且行为是一致的。

最后,得到JSON字符串

ObjectToJsonStringConverterHandler:

public class ObjectToJsonStringConverterHandler extends AbstractFilterBaseConverterHandler<Object, String> {

    public ObjectToJsonStringConverterHandler(ConverterFilter converterFilter) {
        super(converterFilter);
    }

    @Override
    protected String converting(Object value, String tip) {
        try {
            Converter converter = new CommonFilterBaseConverterHandler(this.getConverterFilter());
            return JSON.toJSONString(converter.convert(value));
        } catch (ConvertException e) {
            e.printStackTrace();
            return "";
        }
    }

    @Override
    public boolean supports(Object value) {
        return true;
    }
}

CommonFilterBaseConverterHandler:

它只是简单的从ConverterFilter中取出合适的转换器转换数据。由于我们定义ResponseConverterFilter时注册了BeanToMapConverterHandler,使得它可以将Bean转换为Map,所以这个通用转换器最终将得到一个Map。配合前面的ObjectToJsonStringConverterHandler转换器,就可以得到最终的JSON字符串。

public class CommonFilterBaseConverterHandler<T, K> extends AbstractFilterBaseConverterHandler<T, K> {

    public CommonFilterBaseConverterHandler(ConverterFilter converterFilter) {
        super(converterFilter);
    }

    @Override
    public boolean supports(T value) {
        if (this.getConverter(value) == null) {
            return false;
        }

        return true;
    }

    @Override
    protected K converting(T value, String tip) throws ConvertException {
        Converter converter = this.getConverter(value);
        if (converter != null) {
            return (K) converter.convert(value);
        } else {
            throw new ConvertException("没有转换器可以处理" + value);
        }
    }
}

结果展示

public static void main(String[] a) {

    class TheEntity {
        @FieldConverter(converter = BigDecimalToAmountConverterHandler.class)
        private BigDecimal interest = new BigDecimal(100);

        @FieldConverter(converter = DateToFormatStringConverterHandler.class, tip = "yyyy-MM-dd")
        private Date now = new Date();

        private Integer id = 1;

        private Boolean bool = true;

        private Date now2 = new Date();

        ... 省略getter,setter方法
    }

    try {
        System.out.println(new ObjectToJsonStringConverterHandler(new ResponseConverterFilter()).convert(new TheEntity()));
    } catch (ConvertException e) {
        e.printStackTrace();
    }
}

// 输出结果 :{"bool":"1","interest":"10000","now":"2018-11-13","id":"1","now2":"1542102030250"}

这里只简单的展示了它的工作结果,实际系统中它的功能会比这个强大的多。每一个Converter都处理一种特定的数据转换,职责专一,多个Converter可以通过ConverterFilter组合在一起,完成一些复杂的数据转换。且你只需通过在ConverterFilter中注册Converter就可以,是一种可插拔机制。

它很灵活

这个框架可以有很多变体,看你怎么玩,比如下面这样:

/**
 * 无限转换转换器
 *
 * 该转换器将使用ConverterFilter中注册的转换器进行无限次数的转换,直到没有适配的
 * 转换器可用为止。由于此转换器会进行无限次数的转换,所以你要确保你的ConverterFilter
 * 链路中,一定会转换出一种没有任何转换器可以对它继续进行转换的数据类型,且要保证
 * 不出现某个转换器的结果是另一个转换器supports的情况。
 *
 * @param <T>
 * @param <K>
 */
public class CommonInfiniteFilterBaseConverterHandler<T, K> extends AbstractFilterBaseConverterHandler<T, K> {

    public CommonInfiniteFilterBaseConverterHandler(ConverterFilter converterFilter) {
        super(converterFilter);
    }

    @Override
    protected K converting(T value, String tip) throws ConvertException {
        Object obj = value;
        Converter converter = null;
        while ((converter = this.getConverter(obj)) != null) {
            obj = converter.convert(obj);
        }
        return (K) obj;
    }

    @Override
    public boolean supports(T value) {
        return value != null;
    }
}

配合这几个转换器:

public class RequestConverterFilter extends AbstractConverterFilter {
    @Override
    protected void initConverters(List<Converter<?, ?>> converters) {
        converters.add(new HttpInputMessageToFormStringConverterHandler());
        converters.add(new FormStringToJsonStringConverterHandler());
        converters.add(new HttpServletRequestToJsonStringConverterHandler());
        converters.add(new JsonStringToRequestCheckEntityConverterHandler());
        converters.add(new RequestCheckEntityToRequestEntityConverterHandler());
    }
}

不知道光看这几行代码你能不能发现这组转换器能实现什么(它能将多种数据最终转换为RequestEntity实体)。它优雅的地方在于,每种转换都是独立的,转换器写好后,你可以把它用在任何地方,不用局限于和这个框架配合。

最后

Github地址

结构图

© 著作权归作者所有

wanxiangming
粉丝 3
博文 24
码字总数 38833
作品 0
东城
私信 提问
小谈 FastConverter

前情 几个月前我为公司写一个用于实现RESTful API的项目基础框架,它是一个位于spring cloud项目中的open service内的基础框架,用来统一处理请求和返回。其中,为了实现数据的转换,我开发了...

wanxiangming
06/05
16
0
SpringBoot 配置FastJson

SpringBoot 配置FastJson , Google百度一下一大堆 , 我就贴一下我的方式: extends org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter Override configureMessage......

ol_O_O_lo
2018/12/18
23
0
WebMvcConfigurerAdapter详解

SpringBoot——》WebMvcConfigurerAdapter详解 一、WebMvcConfigurerAdapter是什么 二、WebMvcConfigurerAdapter常用的方法 1、addInterceptors:拦截器 2、addCorsMappings:跨域 3、addVi......

fly祥
03/12
111
0
在springboot中添加jsonConverter

spring默认支持的是jackson2的json解析器.现在大部分人都习惯了fastJson的解析器.现在简单的说下如何配置json解析器 jackson2 配置如下: 可以使用@JsonFormat(shape = JsonFormat.Shape.STRI...

流光韶逝
2018/02/02
531
0
我的友情链接

肖舸的blog 工作启示和c 坏男孩 孙继滨的博客 百度技术博客 masefee CC++游戏编程 srsunbing 互联网产品 柳记 我的未来不是梦。 linuxer 梁肖技术中心 永远的朋友 程序员在囧途 51CTO博客开发...

liqius
2017/11/22
0
0

没有更多内容

加载失败,请刷新页面

加载更多

64.监控平台介绍 安装zabbix 忘记admin密码

19.1 Linux监控平台介绍 19.2 zabbix监控介绍 19.3/19.4/19.6 安装zabbix 19.5 忘记Admin密码如何做 19.1 Linux监控平台介绍: 常见开源监控软件 ~1.cacti、nagios、zabbix、smokeping、ope...

oschina130111
今天
13
0
当餐饮遇上大数据,嗯真香!

之前去开了一场会,主题是「餐饮领袖新零售峰会」。认真听完了餐饮前辈和新秀们的分享,觉得获益匪浅,把脑子里的核心纪要整理了一下,今天和大家做一个简单的分享,欢迎感兴趣的小伙伴一起交...

数澜科技
今天
7
0
DNS-over-HTTPS 的下一代是 DNS ON BLOCKCHAIN

本文作者:PETER LAI ,是 Diode 的区块链工程师。在进入软件开发领域之前,他主要是在做工商管理相关工作。Peter Lai 也是一位活跃的开源贡献者。目前,他正在与 Diode 团队一起开发基于区块...

红薯
今天
12
0
CC攻击带来的危害我们该如何防御?

随着网络的发展带给我们很多的便利,但是同时也带给我们一些网站安全问题,网络攻击就是常见的网站安全问题。其中作为站长最常见的就是CC攻击,CC攻击是网络攻击方式的一种,是一种比较常见的...

云漫网络Ruan
今天
12
0
实验分析性专业硕士提纲撰写要点

为什么您需要研究论文的提纲? 首先当您进行研究时,您需要聚集许多信息和想法,研究论文提纲可以较好地组织你的想法, 了解您研究资料的流畅度和程度。确保你写作时不会错过任何重要资料以此...

论文辅导员
今天
8
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部