對於一些狀態字段以前時興用常量,現在時興用枚舉,雖然閱讀體驗極佳,但是傳值的時候還是會有些麻煩,需要設置一下轉換器.比如:
class A{ @Convert(converter=TestTypeConverter.class) private TestType test; public TestType getTestType() { return test; } public void setTestType(TestType test) { this.test= test; } }
我們定義了如上一個類,其中的一個成員變量是枚舉,為了能正常的接收前端的值,一般會給這個枚舉定義個轉換器來實現String到枚舉的轉換.但是呢同事發現不定義這個轉換器依然可以接收前端的值,這引起了我的興趣,所以打算一探究竟..
項目使用的Spring推薦的Jackson作為json的編解碼,因為前后端都是用json傳值,所以這個問題也就轉化成Jackson究竟做了什么內部的優化能達到不定義轉換器的情況下依然可以正確的反序列化枚舉.然后我們看一下這個示例的枚舉TestType:
public enum TestType { ZERO(0,"0頁"), ONE(1,"1頁"), TWO(2,"2頁"); @JsonCreator public static IsAllType get(int value) { try { return map.get(value); } catch (Exception e) { return null; } } private int value; private String text; IsAllType(int value, String text){ this.value=value; this.text=text; } @JsonValue public int getValue() { return value; } }
示例,所以刪減了部分無關代碼.要debug跟蹤,所以就通過json字符串轉枚舉這一過程來模擬前端傳值這一操作,例子如下:
public static void main(String[] args) { A a = JSONUtils.readValue("{\"test\": \"1\"}", A.class); }
經過一頓debug發現,Jackson會優先使用@JsonCreator注解定義的方法進行構造枚舉值,由於定義了構造方法,某種意義上這就是一個簡易的枚舉轉換器.
到此那個不定義converter就能接收值的問題看似已經結束,但是我的好奇心是很旺盛的..我把這個@JsonCreator注解注釋掉看看會怎么樣?然后發現依然可以正常接收值..這就有點意思了..又是一頓debug發現如下關鍵代碼
public static EnumResolver constructUsingMethod(Class<Enum<?>> enumCls, Method accessor) { Enum<?>[] enumValues = enumCls.getEnumConstants(); HashMap<String, Enum<?>> map = new HashMap<String, Enum<?>>(); // from last to first, so that in case of duplicate values, first wins for (int i = enumValues.length; --i >= 0; ) { Enum<?> en = enumValues[i]; try { Object o = accessor.invoke(en); if (o != null) { map.put(o.toString(), en); } } catch (Exception e) { throw new IllegalArgumentException("Failed to access @JsonValue of Enum value "+en+": "+e.getMessage()); } } return new EnumResolver(enumCls, enumValues, map); }
jackson會構造出一個map,這個map的key是枚舉值的value值,value是枚舉類中對應的枚舉值,然后通過這個map依然可以實現值到枚舉類的轉換.那么何為枚舉值的value值?
@JsonValue public int getValue() { return value; }
jackson通過@JsonValue注解定義的方法返回值作為是枚舉值的value值,通過這個value值又反向建立了關聯,那我把這個@JsonValue注解也注釋掉看看會怎么樣?一運行發現還是可以接收值..
接着一頓debug發現如下關鍵代碼:
public static EnumResolver constructUsingToString(Class<Enum<?>> enumCls) { Enum<?>[] enumValues = enumCls.getEnumConstants(); HashMap<String, Enum<?>> map = new HashMap<String, Enum<?>>(); // from last to first, so that in case of duplicate values, first wins for (int i = enumValues.length; --i >= 0; ) { Enum<?> e = enumValues[i]; map.put(e.toString(), e); } return new EnumResolver(enumCls, enumValues, map); }
通過獲取枚舉類中所有的枚舉值,然后它構建了一個map,只不過這個map的key有點特殊,是枚舉值的ordinal值,value為枚舉值,所以此時如果枚舉值的value是從0開始,也就是恰巧和ordinal值重合時,這樣轉換不會有問題,否則則會大錯特錯..debug的途中發現一些好玩的開發者留言..
public static EnumResolver constructUnsafe(Class<?> rawEnumCls, AnnotationIntrospector ai) { /* This is oh so wrong... but at least ugliness is mostly hidden in just * this one place. */ Class<Enum<?>> enumCls = (Class<Enum<?>>) rawEnumCls; return constructFor(enumCls, ai); } /** * Method that needs to be used instead of {@link #constructUsingToString} * if static type of enum is not known. */ @SuppressWarnings({ "unchecked" }) public static EnumResolver constructUnsafeUsingToString(Class<?> rawEnumCls) { // oh so wrong... not much that can be done tho Class<Enum<?>> enumCls = (Class<Enum<?>>) rawEnumCls; return constructUsingToString(enumCls); } /** * Method used when actual String serialization is indicated using @JsonValue * on a method. */ @SuppressWarnings({ "unchecked" }) public static EnumResolver constructUnsafeUsingMethod(Class<?> rawEnumCls, Method accessor) { // wrong as ever but: Class<Enum<?>> enumCls = (Class<Enum<?>>) rawEnumCls; return constructUsingMethod(enumCls, accessor); }
到此基本上我的好奇心也耗盡了...
總結: 1.jackson會優先使用@JsonCreator注解標注的構造方法構造枚舉值
2.jackson會通過@JsonValue注解標注的方法作為value值構建value與枚舉值的map映射
3.jackson會通過枚舉值的ordinal值與枚舉值構建map映射
ps:看來spring推薦jackson也並無道理,的確做了一些看不見的內部優化..