如何消除对 Java 枚举值的依赖?
[注意差距:我知道最好的解决方案是完全摆脱枚举,但这不是评论中提到的今天的选项,但计划用于(远)未来。]
我们有两个部署单元:前端和后端。前端使用枚举并以枚举作为参数调用后端的 EJB 服务。但是枚举经常变化,所以我们不希望后端知道它的值。
字符串常量
一个可能的解决方案是使用字符串常量而不是枚举,但这会导致前端发生很多小变化。我正在寻找一个解决方案,它会在前端引起尽可能少的变化。
包装类
另一种解决方案是使用与枚举具有相同接口的包装类。枚举成为包装类,枚举值成为该包装内的常量。我必须编写一些反序列化代码来确保对象标识(就像枚举一样),但我不知道这是否是正确的解决方案。如果使用不同的类加载器怎么办? 包装类将实现一个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;
}
}
您是否已经遇到了同样的问题?你是怎么解决的?
[Mind the gap: I know that the best solution would be to get rid of the enum completely, but that's not an option for today as mentioned in the comments, but it is planned for the (far) future.]
We have two deployment units: frontend and backend. The frontend uses an enum and calls an EJB service at the backend with the enum as a parameter. But the enum changes frequently, so we don't want the backend to know its values.
String constants
A possible solution would be to use String constants insteadof enums, but that would cause a lot of little changes at the frontend. I'm searching a solution, which causes as few changes as possible in the frontend.
Wrapper class
Another solution is the usage of a wrapper class with the same interface as an enum. The enum becomes an wrapper class and the enum values become constants within that wrapper. I had to write some deserialization code to ensure object identity (as enums do), but I don't know if it is a correct solution. What if different classloaders are used?
The wrapper class will implement a Java interface, which will replace the enum in the backend. But will the deserialiaztion code execute in the backend even so?
Example for a wrapper class:
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;
}
}
Did you already had the same problem? How did you solved it?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
好吧,让我看看我是否理解。你这么说
”时,我假设你指的是你在枚举构造函数中传递的数值,而不是枚举常量本身。
因此,这意味着前端和后端将有两个不同的 值枚举类的版本,但其中的枚举常量是相同的,
我只是假设通信是通过 RMI 进行的(但这在您的帖子中并不完全清楚)。
现在,枚举的序列化/反序列化工作方式与其他枚举不同。根据 Java 序列化规范,当枚举被序列化时,只有它的名称被序列化,而当它被反序列化时,它是使用 Enum.valueOf(name) 方法构建的,
因此,您原来的包装器建议将不起作用,因为由于规定的枚举序列化,服务器永远不会知道客户端中枚举的实际值。
底线是,如果您打算将枚举传递给服务器,则无法执行您假装执行的操作。如果隐含序列化,前端中的值将永远不会到达后端。
如果暗示了 RMI,一个好的解决方案是使用代码移动性,这样您可以将有问题的类放置在服务器和客户端都可以访问的存储库中,并且当前端开发人员更改类定义时,您可以将类发布到存储库和服务器可以从那里获取它。
请参阅这篇有关在 RMI 中使用代码库属性进行动态代码下载的文章
http://download.oracle.com/javase/6 /docs/technotes/guides/rmi/codebase.html
另一种可能的解决方案是,您可以停止使用 Java 枚举并使用带有最终常量的 Java 类,就像我们过去在枚举之前所做的那样,并且这样您就可以确保其值在发送到后端时能够正确序列化。
有点像这样,
这样您就可以完全控制反序列化时发生的情况,并且您的包装器模式可能会以这种方式工作。
这种类型的构造不能完全替代枚举,例如,它不能在 switch 语句中使用。但是,如果这是一个问题,您可以使用该对象作为发送到服务器的参数,并让服务器使用其枚举类版本重建枚举。
因此,您的枚举可以有两种新方法,一种是从枚举本身构建 Java 实例:
您可以使用它们来来回转换服务器参数的版本。
Ok, let me see if I understand. You said that
When you say "values" I assume you are referring to the numeric value you pass in the enum constructor and not to the enum constants themselves.
Therefore, this implies that the frontend and the backend will have two different versions of the enum class, but the enum constants in them will be the same.
I am only assuming the communication is via RMI (but this is not entirely clear in your post).
Now, serialization/deserialization of enums works different than with other objects. According to the Java Serialization Specification, when a enum is serialized, only its name is serialized. And when it is deserialized, it is built using the Enum.valueOf(name) method.
So, your original wrapper proposal would not work, because the server, due to stipulated serialization of Enums will never know the actual value of the enums in the client.
Bottom line, if you intend to pass an enum to the server there is no possible way to do what you pretend to do because the values in the frontend will never reach the backend if serialization is implied.
If RMI is implied, a good solution would be to use code mobility, this way you could place the problematic class in a repository accessible to both, server and client, and when the frontend developers change the class definition, you can publish the class in the repository and the server can get it from there.
See this article about dynamic code downloading using code base property in RMI
http://download.oracle.com/javase/6/docs/technotes/guides/rmi/codebase.html
Another possible solution is that you could stop using a Java Enum and use Java class with final constants, as we used to do in the old days before enums, and that way you can ensure that its values will be properly serialized when they are are sent to the backend.
Somewhat like this
This way you can be in full control of what happens upon deserialization and your wrapper pattern might work this way.
This type of construction cannot substitute an enum completely, for instance, it cannot be used in switch statements. But, if this is an issue, you could use this object as the parameter sent to the server, and let the server rebuild the enum out of it with its version of the enum class.
Your enum, therefore, could have two new methods, one to build Java instances out of the enum itself:
And you can use those to convert back and forth versions of the parameter for the server.
这是一个奇怪的请求,因为我认为服务器应该知道进入数据库的值,但是好的,我会配合的。也许你可以这样做
It's an odd request, as i would think the server should know about the values of what is going into the database, but ok, i'll play along. Perhaps you could do this
对于前端和后端之间的数据传输,都需要使用相同的类版本,因为在编组参数期间可能会发生序列化。因此,他们必须再次知道完全相同的枚举或您尝试使用的任何其他类。将枚举切换为不同的东西也不起作用。您必须为两者设置已知的类标识。
因此,如果服务器应该基于某种处理/计算参数值来执行操作,请使用字符串或您决定的任何其他不变类并将值放入其中:字符字符串、数字数组或其他内容。
因此,如果您将数据库 ID 放入包装对象中,服务器将能够从数据库中获取对象。但是,它们仍然需要在类路径中使用完全相同版本的包装类。
For data transfer between frontend and backend both need to use the same class versions because of possible serialization during marshalling parameters. So again they have to know exactly the same enums or whatever other classes you try to use. Switching enums to something different won't work either. You have to set on a known class identiy for both.
So if the server should do actions based on some kind of processing/calculating the values of the parameters use strings or whatever other non-changing class you decide on and put your values inside: string of characters, array of numbers or whatever.
So if you put your database id inside the wrapper object the server will be able to get the objects out of the database. But still - they both need exact the same version of the wrapper class in their classpaths.
好吧,我不能太准确,因为我没有看到你的代码,但根据我的经验,像这样改变的东西应该是外部数据,而不是枚举。
我几乎总是发现,如果我将枚举中的信息外部化,那么我还必须外部化其他一些部分,但在完成这一切之后,我最终会分解掉大量代码。
每当您实际使用枚举的值时,几乎肯定会编写重复的代码。我的意思是,如果你有像“HEARTS”、“DIAMONDS”这样的枚举......
它们在你的代码中使用的唯一方法就是像 switch 语句一样:
现在,这显然是愚蠢的,但我做了愚蠢的约束表示您使用了代码中的值。我的断言是,如果您不使用这些值,则不需要枚举 - 让我们不要在代码中使用这些值,然后看看:
哇,全部消失了。但突然间,使用枚举的全部意义就消失了——取而代之的是,您摆脱了整个类,从带有“Heart.png”和“Spade.png”等字符串的文本文件中预加载了 4 个实例的“Suit”工厂。
几乎每次我使用枚举时,我都会像这样将它们分解出来。
我并不是说没有任何代码可以从枚举中受益,但是我在分解代码和外部化数据方面做得越好,我就越无法想象真正需要它们。
Okay, I can't be too exact because I don't see your code but in my experience something that changes like that should be external data, not enums.
What I almost always find is that if I externalize the information that was in the enums, then I have to externalize a few other pieces as well, but after doing it all I end up factoring away a LOT of code.
Any time you actually use the values of an enum you are almost certainly writing duplicate code. What I mean is that if you have enums like "HEARTS", "DIAMONDS"...
The ONLY way they can be used in your code is in something like a switch statement:
Now, this is obviously stupid but I made the stupid constraint to say that you USED the values in the code. My assertion is that if you don't USE the values you don't need an enum--Let's not use the values in code and see:
Wow, all gone. But suddenly, the entire point of using an enum is gone--instead you get rid of the whole class preload a "Suit" factory with 4 instances from a text file with strings like "Heart.png" and "Spade.png".
Nearly every time I use enums I end up factoring them out like this.
I'm not saying there isn't any code that can benefit from enums--but the better that I get at factoring code and externalizing data, the less I can imagine really needing them.