Create an account

Very important

  • To access the important data of the forums, you must be active in each forum and especially in the leaks and database leaks section, send data and after sending the data and activity, data and important content will be opened and visible for you.
  • You will only see chat messages from people who are at or below your level.
  • More than 500,000 database leaks and millions of account leaks are waiting for you, so access and view with more activity.
  • Many important data are inactive and inaccessible for you, so open them with activity. (This will be done automatically)


Thread Rating:
  • 216 Vote(s) - 3.42 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Using Spring IoC to set up enum values

#1
Is there a way to set up such enum values via Spring IoC at construction time?

What I would like to do is to inject, at class load time, values that are hard-coded in the code snippet below:

public enum Car
{
NANO ("Very Cheap", "India"),
MERCEDES ("Expensive", "Germany"),
FERRARI ("Very Expensive", "Italy");

public final String cost;
public final String madeIn;

Car(String cost, String madeIn)
{
this.cost= cost;
this.madeIn= madeIn;
}

}

Let's say that the application must be deployed in Germany, where Nanos are "Nearly free", or in India where Ferraris are "Unaffordable". In both countries, there are only three cars (deterministic set), no more no less, hence an enum, but their "inner" values may differ. So, this is a case of contextual **initialization** of immutables.

Reply

#2
What do you need to set up? The values are created when the class loads, and as it's an enum, no other values can be created (unless you add them to the source and recompile).

That's the point of an enum, to be able to give limit a type to an explicit range of constant, immutable values. Now, anywhere in your code, you can refer to a type Car, or its values, Car.NANO, Car.MERCEDES, etc.

If, on the other hand, you have a set of values that isn't an explicit range, and you want to be able to create arbitrary objects of this type, you'd use the same ctor as in your post, but as a regular, not enum class. Then Spring provides various helper clases to read values from some source (XML file, config file, whatever) and create Lists of that type.
Reply

#3
Do you mean setting up the `enum` itself?

I don't think that's possible. You cannot instantiate enums because they have a `static` nature. So I think that Spring IoC can't *create* `enums` as well.

On the other hand, if you need to set initialize something with a `enum` please check out the [Spring IoC chapter][1]. (search for enum) There's a simple example that you can use.


[1]:

[To see links please register here]

Reply

#4
Why not provide a setter (or constructor argument) that takes a String, and simply call [Enum.valueOf(String s)][1] to convert from a String to an enum. Note an exception will get thrown if this fails, and your Spring initialisation will bail out.


[1]:

[To see links please register here]

Reply

#5
<bean id="car" class="Foo">
<property name="carString" value="NANO" />
</bean>

And then in your class Foo, you would have this setter:

public void setCar(String carString) {
this.carString = Car.valueOf(carString);
}
Reply

#6
All right, this is a bit complex but you may find a way to integrate it. Enums are not meant to change at runtime, so this is a reflection hack. Sorry I don't have the Spring implementation part, but you could just build a bean to take in the enum class or object, and another field that would be the new value or values.

Constructor con = MyEnum.class.getDeclaredConstructors()[0];
Method[] methods = con.getClass().getDeclaredMethods();
for (Method m : methods) {
if (m.getName().equals("acquireConstructorAccessor")) {
m.setAccessible(true);
m.invoke(con, new Object[0]);
}
}
Field[] fields = con.getClass().getDeclaredFields();
Object ca = null;
for (Field f : fields) {
if (f.getName().equals("constructorAccessor")) {
f.setAccessible(true);
ca = f.get(con);
}
}
Method m = ca.getClass().getMethod(
"newInstance", new Class[] { Object[].class });
m.setAccessible(true);
MyEnum v = (MyEnum) m.invoke(ca, new Object[] {
new Object[] { "MY_NEW_ENUM_VALUE", Integer.MAX_VALUE } });
System.out.println(v.getClass() + ":" + v.name() + ":" + v.ordinal());

This is taken from [this site][1].


[1]:

[To see links please register here]

Reply

#7
I don't think it can be done from Spring's <code>ApplicationContext</code> configuration. But, do you really need it done by Spring, or can you settle for simple externalization using [ResourceBundle][1]; like this:

public enum Car
{
NANO,
MERCEDES,
FERRARI;

public final String cost;
public final String madeIn;

Car()
{
this.cost = BUNDLE.getString("Car." + name() + ".cost");
this.madeIn = BUNDLE.getString("Car." + name() + ".madeIn");
}

private static final ResourceBundle BUNDLE = ResourceBundle.getBundle(...);

}

In the properties file, one for each specific locale, enter the keys describing the possible internal enum values:

Car.NANO.cost=Very cheap
Car.NANO.madeIn=India
Car.MERCEDES.cost=Expensive
...

The only drawback of this approach is having to repeat the name of enum fields (cost, madeIn) in Java code as strings. **Edit**: And on the plus side, you can stack all properties of all enums into one properties file per language/locale.

[1]:

[To see links please register here]

Reply

#8
Here is the solution I came to (thanks to Javashlook whose answer put me on track). It works, but it's most probably not a production-grade way of doing it.

But better than a thousand words, here is the code, I'll let you judge by yourself.

Let's take a look at the revised `Car` enum :

public enum Car {
NANO(CarEnumerationInitializer.getNANO()), MERCEDES(
CarEnumerationInitializer.getMERCEDES()), FERRARI(
CarEnumerationInitializer.getFERRARI());

public final String cost;
public final String madeIn;

Car(ICarProperties properties) {
this.cost = properties.getCost();
this.madeIn = properties.getMadeIn();
}
}

And here are the "plumbling" classes :

//Car's properties placeholder interface ...
public interface ICarProperties {
public String getMadeIn();
public String getCost();
}
//... and its implementation
public class CarProperties implements ICarProperties {
public final String cost;
public final String madeIn;

public CarProperties(String cost, String madeIn) {
this.cost = cost;
this.madeIn = madeIn;
}
@Override
public String getCost() {
return this.cost;
}
@Override
public String getMadeIn() {
return this.madeIn;
}
}

//Singleton that will be provide Car's properties, that will be defined at applicationContext loading.
public final class CarEnumerationInitializer {
private static CarEnumerationInitializer INSTANCE;
private static ICarProperties NANO;
private static ICarProperties MERCEDES;
private static ICarProperties FERRARI;

private CarEnumerationInitializer(ICarProperties nano,
ICarProperties mercedes, ICarProperties ferrari) {
CarEnumerationInitializer.NANO = nano;
CarEnumerationInitializer.MERCEDES = mercedes;
CarEnumerationInitializer.FERRARI = ferrari;
}

public static void forbidInvocationOnUnsetInitializer() {
if (CarEnumerationInitializer.INSTANCE == null) {
throw new IllegalStateException(CarEnumerationInitializer.class
.getName()
+ " unset.");
}
}

public static CarEnumerationInitializer build(CarProperties nano,
CarProperties mercedes, CarProperties ferrari) {
if (CarEnumerationInitializer.INSTANCE == null) {
CarEnumerationInitializer.INSTANCE = new CarEnumerationInitializer(
nano, mercedes, ferrari);
}
return CarEnumerationInitializer.INSTANCE;
}

public static ICarProperties getNANO() {
forbidInvocationOnUnsetInitializer();
return NANO;
}

public static ICarProperties getMERCEDES() {
forbidInvocationOnUnsetInitializer();
return MERCEDES;
}

public static ICarProperties getFERRARI() {
forbidInvocationOnUnsetInitializer();
return FERRARI;
}
}

Finally, the applicationContext definition :

<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="nano" class="be.vinkolat.poc.core.car.CarProperties">
<constructor-arg type="java.lang.String" value="Cheap"></constructor-arg>
<constructor-arg type="java.lang.String" value="India"></constructor-arg>
</bean>
<bean id="mercedes"
class="be.vinkolat.poc.core.car.CarProperties">
<constructor-arg type="java.lang.String" value="Expensive"></constructor-arg>
<constructor-arg type="java.lang.String" value="Germany"></constructor-arg>
</bean>
<bean id="ferrari" class="be.vinkolat.poc.core.car.CarProperties">
<constructor-arg type="java.lang.String"
value="Very Expensive">
</constructor-arg>
<constructor-arg type="java.lang.String" value="Italy"></constructor-arg>
</bean>
<bean id="carInitializer"
class="be.vinkolat.poc.core.car.CarEnumerationInitializer"
factory-method="build" lazy-init="false">
<constructor-arg type="be.vinkolat.poc.core.car.CarProperties"
ref="nano" />
<constructor-arg type="be.vinkolat.poc.core.car.CarProperties"
ref="mercedes" />
<constructor-arg type="be.vinkolat.poc.core.car.CarProperties"
ref="ferrari" />
</bean>
</beans>

It works, but there is one major weakness : `CarEnumerationInitializer` MUST be instantiated BEFORE any reference is made to `Car` enumeration, otherwise CarProperties are null, meaning that Car's properties can't be set when `Car` is loaded (hence the `IllegalStateException` thrown, to at least make it crashes in a predictable and documentated way). `carInitializer` bean's property `lazy-init` set to an explicit `false`, to put emphasis on the need to load it as soon as possible.
I would say it may be useful in a simple application, one where you can easely guess where a first call to `Car` will be made. For a larger one, it will probably be such a clutter that I didn't encourage you to use it.

Hope this help, comments and vote (up and down) very welcome :) I'll wait for a few days to make this one the accepted answer, to let you react.
Reply

#9
Attempting to mutate an Enum is well silly and goes completely against their design objectives. An enum by definition represents a distinct value within a group. If you ever need more / less values you will need to update the source. While you can change an enums state by adding setters (after all they are just objects) your hacking the system.
Reply

#10
You can't create new enum values via Spring, they must be declared in the class. However, since the enum values will be singletons anyway (created by the JVM), any configurations that should be set, or services to be injected, can be done via invoking static methods in the enum class:

[To see links please register here]


Reply



Forum Jump:


Users browsing this thread:
2 Guest(s)

©0Day  2016 - 2023 | All Rights Reserved.  Made with    for the community. Connected through