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 i
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.
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.
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.
Why not provide a setter (or constructor argument) that takes a String, and simply call Enum.valueOf(String s) to convert from a String to an enum. Note an exception will get thrown if this fails, and your Spring initialisation will bail out.
OK, it's quite fiddly, but it CAN be done.
It's true that Spring cannot instantiate enums. But that's not a problem - Spring can also use factory methods.
This is the key component:
public class EnumAutowiringBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
private final List<Class<? extends Enum>> enumClasses = new ArrayList<>();
public EnumAutowiringBeanFactoryPostProcessor(Class<? extends Enum>... enumClasses) {
Collections.addAll(this.enumClasses, enumClasses);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
for (Class<? extends Enum> enumClass : enumClasses) {
for (Enum enumVal : enumClass.getEnumConstants()) {
BeanDefinition def = new AnnotatedGenericBeanDefinition(enumClass);
def.setBeanClassName(enumClass.getName());
def.setFactoryMethodName("valueOf");
def.getConstructorArgumentValues().addGenericArgumentValue(enumVal.name());
((BeanDefinitionRegistry) beanFactory).registerBeanDefinition(enumClass.getName() + "." + enumVal.name(), def);
}
}
}
}
Then the following test class shows that it works:
@Test
public class AutowiringEnumTest {
public void shouldAutowireEnum() {
new AnnotationConfigApplicationContext(MyConig.class);
assertEquals(AutowiredEnum.ONE.myClass.field, "fooBar");
assertEquals(AutowiredEnum.TWO.myClass.field, "fooBar");
assertEquals(AutowiredEnum.THREE.myClass.field, "fooBar");
}
@Configuration
public static class MyConig {
@Bean
public MyClass myObject() {
return new MyClass("fooBar");
}
@Bean
public BeanFactoryPostProcessor postProcessor() {
return new EnumAutowiringBeanFactoryPostProcessor(AutowiredEnum.class);
}
}
public enum AutowiredEnum {
ONE,
TWO,
THREE;
@Resource
private MyClass myClass;
}
public static class MyClass {
private final String field;
public MyClass(String field) {
this.field = field;
}
}
}
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. (search for enum) There's a simple example that you can use.