xiaobaoqiu Blog

Think More, Code Less

Enum反序列化问题

1.Enum原理

定义一个Enum,通过编译之后的字节码,我们可以发现其实现原理:

1
2
3
public enum FruitEnum {  
    APPLE, ORAGE  
}

编译器是在为我们创建一个类,这个类继承自 java.lang.Enum,有两个公共的、静态的、被声明成final的属性,它们的类型就是我们定义的FruitEnum。

编译器还生成了一个静态初始话器,就是字节码中static{};这一行下面的代码,其中的字节码创建了两个FruitEnum对象,同时分别赋值给APPLE和ORANGE这两个属性,调用的构造函数是定义在java.lang.Enum中的protected Enum(String name, int ordinal)方法。

在创建完成两个FruitEnum对象并且分别赋值给APPLE和ORIGIN之后,还创建了一个名叫ENUM$VALUES的数组,然后把APPLE和ORIGIN按照定义的顺序放如这个数组中。

除了这个静态初始化器之外,编译器还为我们生成了两个静态方法,values()和 valueOf(java.lang.String)方法。其中values()方法将ENUM$VALUES数组拷贝一份然后返回,而valueOf(java.lang.String)方法则会调用java.lang.Enum类中的valueOf方法,其作用是根据参数名找到对应的具体的枚举对象,如果找不到的话会抛出一个IllegalArgumentException异常。

2.Enum序列化反序列化原理及问题

2.1原理

序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。

同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。

2.2问题

在系统或者类库升级时,对其中定义的枚举类型多加注意,为了保持代码上的兼容性,如果我们定义的枚举类型有可能会被序列化保存(放到文件中、保存到数据库中,进入分布式内存缓存中),那么我们是不能够删除原来枚举类型中定义的任何枚举对象的,否则程序在运行过程中,JVM就会抱怨找不到与某个名字对应的枚举对象了。

另外,在远程方法调用过程中,如果我们发布的客户端接口返回值中使用了枚举类型,那么服务端在升级过程中就需要特别注意。如果在接口的返回结果的枚举类型中添加了新的枚举值,那就会导致仍然在使用老的客户端的那些应用出现调用失败的情况。

3.Enum序列化反序列化问题解决

使用class代替Enum,原来的枚举使用static对象替换,valueOf()方法使用一个Map实现,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public final class FruitEnum implements Serializable {

    private static final long serialVersionUID = -7230925342774763449L;

    private static final Map<Integer, FruitEnum> MAP = new HashMap<Integer, FruitEnum>();

    public static final FruitEnum APPLE = new FruitEnum("Apple", 0);
    public static final FruitEnum ORAGE = new FruitEnum("Orige", 1);

    private String text;
    private int code;

    private FruitEnum(String text, int code) {
        this.text = text;
        this.code = code;

        MAP.put(code, this);
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    @Override
    public int getCode() {
        return code;
    }

    /**
     * 根据code获取FruitEnum
     *
     * @param code
     * @return
     */
    public static FruitEnum valueOf(int code) {
        return MAP.get(code);
    }
}

参考:http://mysun.iteye.com/blog/1581119