1. 程式人生 > >記一個小小的轉換工具的開發:FastConverter

記一個小小的轉換工具的開發: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地址

結構圖