Using Spring IoC to set up enum values

后端 未结 13 1784
名媛妹妹
名媛妹妹 2020-11-30 06:06

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

相关标签:
13条回答
  • 2020-11-30 06:09

    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.

    0 讨论(0)
  • 2020-11-30 06:12

    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.

    0 讨论(0)
  • 2020-11-30 06:14

    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.

    0 讨论(0)
  • 2020-11-30 06:19

    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.

    0 讨论(0)
  • 2020-11-30 06:21

    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;
            }
       }
    
    }
    
    0 讨论(0)
  • 2020-11-30 06:24

    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.

    0 讨论(0)
提交回复
热议问题