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

背景

介绍一个新写的小东西,叫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地址

结构图

猜你喜欢

转载自my.oschina.net/u/3039151/blog/2875622