Using Spring IoC to set up enum values

后端 未结 13 1786
名媛妹妹
名媛妹妹 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:25

    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.

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

    You can use Enum class as factory bean. Example: setting serializationInclusion field with enum value:

                <property name="serializationInclusion">
                    <bean class="org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion" factory-method="valueOf">
                        <constructor-arg>   
                            <value>NON_NULL</value>
                        </constructor-arg>
                    </bean>
                </property>
    

    But actually (Spring 3.1) a simpler solution works: you just write the enum value and Spring recognizes what to do:

    <property name="serializationInclusion" value="NON_NULL"/>
    
    0 讨论(0)
  • 2020-11-30 06:27

    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:

    http://static.springsource.org/spring/docs/2.5.x/api/org/springframework/beans/factory/config/MethodInvokingFactoryBean.html

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

    I have done it in the following way:

    @Component
    public class MessageSourceHelper {
        @Inject
        public MessageSource injectedMessageSource;
    
        public static MessageSource messageSource;
    
        public static String getMessage(String messageKey, Object[] arguments, Locale locale) {
            return messageSource.getMessage(messageKey, arguments, locale);
        }
    
        @PostConstruct
        public void postConstruct() {
            messageSource = injectedMessageSource;
        }
    

    }

    That way you can easily use it in the enum to get messages in the following way:

    MessageSourceHelper.getMessage(key, arguments, locale);
    
    0 讨论(0)
  • 2020-11-30 06:31
    <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);
    }
    
    0 讨论(0)
  • 2020-11-30 06:33

    I have faced the same issue when I was working to localize my enum label in different locales.

    Enum Code:

    public enum Type {
       SINGLE("type.single_entry"),
       MULTIPLE("type.multiple_entry"),
       String label;
    
       Type(String label) {
         this.label = label;
       }
    
       public String getLabel() {
         String translatedString = I18NTranslator.getI18NValue(getLocale(), label);
         return StringUtils.isEmpty(translatedString) ? label : translatedString;
       }
    }
    

    My I18NTranslator class which basically load the message source to get localized content. I18Ntransalator class depends on springContext if you don't write you might face a peculiar bug. Some time might face a dependency related which causes null pointer exception. I had put a lot of effort to resolve this issue.

    @Component
    @DependsOn({"springContext"})
    public class I18NTranslator {
    
        private static MessageSource i18nMessageSource;
    
        public static String getI18NValue(Locale locale, String key) {
            if (i18nMessageSource != null)
                return i18nMessageSource.getMessage(key, null, locale);
            return key;
        }
    
        @PostConstruct
        public void initialize() {
            i18nMessageSource = SpringContext.getBean("i18nMessageSource", MessageSource.class);
        }
    }
    

    We have to set the spring context

    @Component
    @Slf4j
    public class SpringContext implements ApplicationContextAware {
    
        private static ApplicationContext context;
    
        public static <T extends Object> T getBean(Class<T> beanClass) {
            return context.getBean(beanClass);
        }
    
        public static <T extends Object> T getBean(String beanClassName, Class<T> beanClass) {
            return context.getBean(beanClassName, beanClass);
        }
    
        @Override
        public void setApplicationContext(ApplicationContext context) throws BeansException {
            SpringContext.context = context;
        }
    }
    

    Now it is time to define the bean for I18NMessageSource.

    @Configuration
    public class LocaleConfiguration implements WebMvcConfigurer {
    
        @Bean(name = "i18nMessageSource")
        public MessageSource getMessageResource() {
            ReloadableResourceBundleMessageSource messageResource = new ReloadableResourceBundleMessageSource();
            messageResource.setBasename("classpath:i18n/messages");
            messageResource.setCacheSeconds(3600);
            messageResource.setDefaultEncoding("UTF-8");
            return messageResource;
        }
    
        @Bean(name = "localeResolver")
        public LocaleResolver getLocaleResolver() {
            return new UrlLocaleResolver();
        }
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            //UrlLocalInterceptor is custom locale resolver based on header paramter.
            UrlLocaleInterceptor localeInterceptor = new UrlLocaleInterceptor();
            registry.addInterceptor(localeInterceptor);
        }
    }
    

    PS: if you need the custom interceptor code I can share in the comment. Defines all local properties files inside resources/i18n folder with messages prefix like messages_en.properties for english and messages_fr.properties fro french.

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