記一個小小的轉換工具的開發:FastConverter

背景

介紹一個新寫的小東西,叫FastConverter,叫這個名字是由於,它最初是被設計用來將服務器返回給前端的數據實體轉換爲json字符串的。前端

需求背景是:服務器通過一系列計算後,最終數據被存放在一個數據實體中,通過toJSON化,輸出到前端。但輸出時咱們對數據有一些格式化,或自定製化的需求,好比,數據實體中的Date,最終輸出多是時間戳,也多是「yyyy-MM-dd」;數據實體中的用以表示金額的BigDecimal,在服務器端咱們用元作單位,帶小數點,輸出時咱們想讓它變成以分爲單位,不帶小數點;用戶敏感信息打碼(用戶名,身份證號,手機號等)等等。總之就是,直接將數據實體toJSON,不能知足咱們的需求,在toJSON的同時,咱們須要對數據作一些轉換。git

設計思路

不少初學者設計一個工具的時候,思路很散,作出來的東西不成體系。好比上面所述的功能,有的人確定是一個 "XxxTool"類 或者 "XxxUtils"類 就實現了。程序員

但這種散件很是醜陋,類的抽象和類的接口息息相關, "XxxTool"類 或者 "XxxUtils"類最大的問題是,它沒法有效且內聚的描述本身的抽象,它的接口大多數狀況下各司其職,這樣的類必定違背開閉原則,依賴倒轉原則,也違背內聚性。實在點說就是:1,當客戶端程序員去使用這些工具類時,他們會發現,這個類有好多方法;2,每一個方法彷佛都是一個獨立的功能點;3,當他發現缺乏他須要的功能時,他會不知所措,到底如何修改這個龐大的類;4,時間久了,這個類必定是沒法維護的(這個複雜的私有方法是幹什麼的???爲何只爲那個公共方法提供服務???這個私有域又是幹什麼的???我能夠修改它嗎???還有哪些地方使用了它???算了,我本身加個新的域來實現個人功能吧);5,會有不少類依賴(緊耦合)這個工具類。github

若是你在開發系統底層的時候不在意這些小問題,等系統變得龐雜起來時,它會讓你步履維艱,這是很明顯的弊端,但我很驚訝如此多的人對它視而不見(也許是由於教科書上不教這些小細節吧)。spring

第一步,接口開發

既然是轉換數據,接口的設計挺天然的:json

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」 你只須要一個轉換器就能實現。框架

拋出的異常:ide

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地址

結構圖

相關文章
相關標籤/搜索