Использование Spring IoC для установки значений перечисления - PullRequest
27 голосов
/ 02 апреля 2009

Есть ли способ установить такие значения перечисления через Spring IoC во время сборки?

Что я хотел бы сделать, так это ввести во время загрузки класса значения, жестко запрограммированные во фрагменте кода ниже:

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;
        }

}

Допустим, приложение должно быть развернуто в Германии, где Nanos "почти свободны", или в Индии, где Ferrari "недоступны". В обеих странах есть только три автомобиля (детерминированный набор), не больше, не меньше, отсюда и enum, но их «внутренние» значения могут отличаться. Итак, это случай контекстной инициализации неизменяемых.

Ответы [ 12 ]

0 голосов
/ 08 апреля 2009

Вот решение, к которому я пришел (благодаря Javashlook, чей ответ поставил меня на правильный путь). Это работает, но, скорее всего, это не способ производства.

Но лучше, чем тысяча слов, вот код, я вам позволю судить сам.

Давайте посмотрим на пересмотренное перечисление Car:

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();
    }
}

А вот и "отвесные" классы:

//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;
    }
}

Наконец, определение контекста приложения:

<?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>

Это работает, но есть один существенный недостаток: CarEnumerationInitializer ДОЛЖЕН создаваться экземпляр ДО ТОГО, как делается ссылка на перечисление Car, в противном случае CarProperties имеют значение null, что означает, что свойства Car не могут быть установлены при загрузке Car (следовательно, IllegalStateException брошено, чтобы, по крайней мере, заставить его падать предсказуемо и задокументировано). Для свойства carInitializer bean lazy-init установлено явное значение false, чтобы подчеркнуть необходимость его загрузки как можно скорее. Я бы сказал, что это может быть полезно в простом приложении, в котором можно легко угадать, где будет сделан первый вызов Car. Для большего размера это, вероятно, будет такой беспорядок, что я не рекомендовал вам использовать его.

Надеюсь, эта помощь, комментарии и голосование (вверх и вниз) очень приветствуются :) Я подожду несколько дней, чтобы сделать этот ответ приемлемым, чтобы вы могли среагировать.

0 голосов
/ 02 апреля 2009

Хорошо, это немного сложно, но вы можете найти способ интегрировать его. Перечисления не предназначены для изменения во время выполнения, так что это взлом отражения. Извините, у меня нет части реализации Spring, но вы могли бы просто создать bean-компонент для использования в классе или объекте enum и в другом поле, которое будет новым значением или значениями.

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());

Это взято с этого сайта .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...