如何删除对Java枚举值的依赖?

Chr*_*fer 5 java enums serialization classloader dependency-management

[ 注意差距:我知道最好的解决方案是完全摆脱枚举,但这不是评论中提到的今天的选择,但已计划在(很远的将来)使用。]

我们有两个部署单元:前端和后端。前端使用枚举,并使用枚举作为参数在后端调用EJB服务。但是枚举经常更改,因此我们不希望后端知道它的值。

字符串常量

一种可能的解决方案是使用String常量而不是枚举,但这将在前端造成很多小的变化。我正在寻找一种解决方案,该解决方案会导致前端的更改尽可能少。

包装类

另一个解决方案是使用具有与枚举相同接口的包装器类。枚举将成为包装类,而枚举值将成为该包装内的常量。我必须编写一些反序列化代码以确保对象身份(例如枚举),但是我不知道这是否是正确的解决方案。如果使用不同的类加载器怎么办?包装器类将实现Java接口,该接口将替换后端中的枚举。但是反序列化代码还会在后端执行吗?

包装类的示例:

public class Locomotion implements Serializable {
    private static final long serialVersionUID = -6359307469030924650L;

    public static final List<Locomotion> list = new ArrayList<Locomotion>();

    public static final Locomotion CAR = createValue(4654L);
    public static final Locomotion CYCLE = createValue(34235656L);
    public static final Locomotion FEET = createValue(87687L);

    public static final Locomotion createValue(long type) {
        Locomotion enumValue = new Locomotion(type);
        list.add(enumValue);
        return enumValue;
    }

    private final long ppId;

    private Locomotion(long type) {
        this.ppId = type;
    }

    private Object readResolve() throws ObjectStreamException {
        for (Locomotion enumValue : list) {
            if (this.equals(enumValue)) {
                return enumValue;
            }
        }
        throw new InvalidObjectException("Unknown enum value '" + ppId + "'");
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + (int) (ppId ^ (ppId >>> 32));
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (!(obj instanceof Locomotion)) {
            return false;
        }
        Locomotion other = (Locomotion) obj;
        if (ppId != other.ppId) {
            return false;
        }
        return true;
    }
}
Run Code Online (Sandbox Code Playgroud)

你已经有同样的问题了吗?您是如何解决的?

Edw*_*rzo 2

好吧,让我看看我是否理解。你之前这么说

“前端使用枚举,并以枚举作为参数调用后端的EJB服务。但是枚举经常变化,所以我们不希望后端知道它的值”

当您说“值”时,我假设您指的是在枚举构造函数中传递的数值,而不是枚举常量本身。

因此,这意味着前端和后端将有两个不同版本的枚举类,但它们中的枚举常量将是相同的。

我只是假设通信是通过 RMI 进行的(但这在您的帖子中并不完全清楚)。

现在,枚举的序列化/反序列化的工作方式与其他对象不同。根据 Java 序列化规范,当序列化枚举时,仅序列化其名称。当它被反序列化时,它是使用 Enum.valueOf(name) 方法构建的。

因此,您最初的包装器提案将不起作用,因为由于规定的枚举序列化,服务器永远不会知道客户端中枚举的实际值。

最重要的是,如果您打算将枚举传递到服务器,则无法执行您假装执行的操作,因为如果隐含序列化,前端中的值将永远不会到达后端。

如果暗示了 RMI,一个好的解决方案是使用代码移动性,这样您可以将有问题的类放置在服务器和客户端都可以访问的存储库中,并且当前端开发人员更改类定义时,您可以将类发布到存储库和服务器可以从那里获取它。

请参阅这篇有关在 RMI 中使用代码库属性进行动态代码下载的文章 http://download.oracle.com/javase/6/docs/technotes/guides/rmi/codebase.html

另一种可能的解决方案是,您可以停止使用 Java 枚举并使用带有最终常量的 Java 类,就像我们过去在枚举之前所做的那样,这样您就可以确保其值在发送时能够正确序列化到后端。

有点像这样

public class Fruit implements Serializable{

    private static final long serialVersionUID = 1L;
    public final Fruit ORANGE = new Fruit("orange");
    public final Fruit LEMON = new Fruit("lemon");

    private String name;

    private Fruit(String name){
        this.name = name;
    }
}
Run Code Online (Sandbox Code Playgroud)

这样您就可以完全控制反序列化时发生的情况,并且您的包装器模式可能会以这种方式工作。

这种类型的构造不能完全替代枚举,例如,它不能在 switch 语句中使用。但是,如果这是一个问题,您可以使用该对象作为发送到服务器的参数,并让服务器使用其枚举类版本重建枚举。

因此,您的枚举可以有两种新方法,一种是从枚举本身构建 Java 实例:

public static Fruit toFruit(FruitEnum enum);
public FruitEnum valueOf(Fruit fruit);
Run Code Online (Sandbox Code Playgroud)

您可以使用它们来来回转换服务器参数的版本。