Как легко вводить String константы с помощью Weld?

У нас есть ситуация, когда мы предоставляем внешнюю конфигурацию в виде Карты для наших запущенных программ. Я обнаружил, что JSR-330 Dependency Injection дает гораздо более чистый способ использовать эту конфигурационную карту в коде вместо того, чтобы передавать карту или использовать JNDI для ее получения.

@Inject @Named("server.username") String username;

позволяет реализовать JSR-330 в этом поле автоматически.

С помощью Guice я могу установить значение с помощью

bindConstant().annotatedWith(Names.named(key)).to(value);

Я хотел бы иметь возможность сделать то же самое в Weld (bind "server.username", например, "foobar" ), и я понимаю, что механизм, скорее всего, beans.xml, но я бы предпочел простой "отправьте эту карту в Weld, пожалуйста," скопируйте альтернативу. Что было бы хорошим способом сделать это?


EDIT 2013-10-16: после изучения кинжала, который работает во время компиляции, а не во время выполнения, я обнаружил, что с нами обычно 10-20 на каждую программу мы могли бы жить с использованием метода @Provider для каждой строки конфигурации, затем просматривает конфигурационную карту. Это позволяет определять поведение конкретного метода (включая значения по умолчанию), возможность предоставления javadoc и возможность использовать все эти методы в одном классе. Также он отлично работает с Weld из коробки. Я рассматриваю возможность написания более подробного объяснения в блоге.

Ответы

Ответ 1

Я бы хотел, чтобы эта награда понравилась. Понимая это, я много рассказывал о внутренностях WELD, и здесь самый интересный урок: @Named является квалификатором и должен рассматриваться как таковой, если вы сможете сопоставить его.

У меня есть предупреждение для вас: если вам не хватает каких-либо значений в вашем приложении, это не удастся при развертывании или загрузке. Это может быть желательно для вас, но это специально означает, что значения по умолчанию невозможны.

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

@ApplicationScoped
public class PerformSetup implements Extension {

    Map<String, String> configMap;

    public PerformSetup() {
        configMap = new HashMap<String, String>();
        // This is a dummy initialization, do something constructive here
        configMap.put("string.value", "This is a test value");
    }

    // Add the ConfigMap values to the global bean scope
    void afterBeanDiscovery(@Observes AfterBeanDiscovery abd, BeanManager bm) {
        // Loop through each entry registering the strings.
        for (Entry<String, String> configEntry : configMap.entrySet()) {
            final String configKey = configEntry.getKey();
            final String configValue = configEntry.getValue();

            AnnotatedType<String> at = bm.createAnnotatedType(String.class);
            final InjectionTarget<String> it = bm.createInjectionTarget(at);

            /**
             * All of this is necessary so WELD knows where to find the string,
             * what it named, and what scope (singleton) it is.
             */ 
            Bean<String> si = new Bean<String>() {

                public Set<Type> getTypes() {
                    Set<Type> types = new HashSet<Type>();
                    types.add(String.class);
                    types.add(Object.class);
                    return types;
                }

                public Set<Annotation> getQualifiers() {
                    Set<Annotation> qualifiers = new HashSet<Annotation>();
                    qualifiers.add(new NamedAnnotationImpl(configKey));
                    return qualifiers;

                }

                public Class<? extends Annotation> getScope() {
                    return Singleton.class;
                }

                public String getName() {
                    return configKey;
                }

                public Set<Class<? extends Annotation>> getStereotypes() {
                    return Collections.EMPTY_SET;
                }

                public Class<?> getBeanClass() {
                    return String.class;
                }

                public boolean isAlternative() {
                    return false;
                }

                public boolean isNullable() {
                    return false;
                }

                public Set<InjectionPoint> getInjectionPoints() {
                    return it.getInjectionPoints();
                }

                @Override
                public String create(CreationalContext<String> ctx) {
                    return configValue;

                }

                @Override
                public void destroy(String instance,
                        CreationalContext<String> ctx) {
                    // Strings can't be destroyed, so don't do anything
                }
            };
            abd.addBean(si);
        }
    }

    /**
     * This is just so we can create a @Named annotation at runtime.
     */
    class NamedAnnotationImpl extends AnnotationLiteral<Named> implements Named {
        final String nameValue;

        NamedAnnotationImpl(String nameValue) {
            this.nameValue = nameValue;
        }

        public String value() {
            return nameValue;
        }

    }
}

Я тестировал, что это сработало с помощью приложения WELD-SE:

@ApplicationScoped
public class App {

    @Inject
    @Parameters
    List<String> parameters;

    @Inject
    @Named("string.value")
    String stringValue;

    public void printHello(@Observes ContainerInitialized event) {
        System.out.println("String Value is " + stringValue);
    }

}

Наконец, не забывайте /META -INF/services/javax.enterprise.inject.spi.Extension, заменив метод сварки с помощью используемого класса:

weldtest.PerformSetup

Это должно сделать всю эту работу. Дайте мне знать, если у вас возникнут какие-либо трудности, и я пришлю вам свой тестовый проект.

Ответ 2

Не все, кто интересуется щедростью, но я возьму его, если он все еще на столе. Это ОЧЕНЬ похоже на некоторый код, который я использую в $DAYJOB, и поэтому это не теория, это то, что я использую в производственном коде, но модифицированный для защиты виновных. Я не пробовал компилировать измененный код, поэтому будьте предупреждены, что я, возможно, допустил некоторые ошибки при изменении имен и т.д., Но все эти принципы были протестированы и работают.

Во-первых, вам нужен квалификатор Value Holder. Используйте @Nonbinding, чтобы WELD соответствовал ТОЛЬКО для классификаторов с одинаковыми значениями, так как мы хотим, чтобы все значения этого конкретного определителя соответствовали одной точке инъекции. Сохраняя квалификатор и значение в одной аннотации, вы не можете просто "забыть" одну из них случайно. (Принцип KISS)

@Qualifier
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface ConfigValue {
    // Excludes this value from being considered for injection point matching
    @Nonbinding 
    // Avoid specifying a default value, since it can encourage programmer error.
    // We WANT a value every time.
    String value();
}

Далее вам нужен метод производителя, который знает, как получить карту. Вероятно, вы должны иметь Named bean, который содержит метод продюсера, поэтому вы можете либо явно инициализировать значение с помощью геттеров/сеттеров, либо еще bean инициализировать его для вас.

Мы должны указать пустое значение для классификатора метода производителя, чтобы избежать ошибок времени компиляции, но никогда не использовалось на практике.

@Named
public class ConfigProducer {
    //@Inject // Initialize this parameter somehow
    Map<String,String> configurationMap;

    @PostConstructor
    public void doInit() {
         // TODO: Get the configuration map here if it needs explicit initialization
    }

    // In general, I would discourage using this method, since it can be difficult to control exactly the order in which beans initialize at runtime.
    public void setConfigurationMap(Map<String,String> configurationMap) {
        this.configurationMap = configurationMap;
    }

    @Produces
    @ConfigValue("")
    @Dependent
    public String configValueProducer(InjectionPoint ip) {
        // We know this annotation WILL be present as WELD won't call us otherwise, so no null checking is required.
        ConfigValue configValue = ip.getAnnotated().getAnnotation(ConfigValue.class);
        // This could potentially return a null, so the function is annotated @Dependent to avoid a WELD error.
        return configurationMap.get(configValue.value());
    }
}

Использование прост:

@Inject
@ConfigValue("some.map.key.here")
String someConfigValue;

Ответ 4

Не будет ли реализован пользовательский Weld InjectionServices?

Ответ 5

Возможно, это можно реализовать как метод @Dependent Producer, который сам вводит объект @InjectionPoint, который позволит вам задуматься над введенным вами полем - это позволит вам заглянуть в пользовательскую аннотацию (не член-определитель) в поле для определения значения, которое вы хотите вернуть

@Inject @ConfigMapQualifier @Val("user.name") String user;

...

@Produces @ConfigMapQualifier configProducr(...) { 
...
@Inject InjectionPoint ip;

// use e.g. ip/getJavaMember() then reflection to figure out the @Val value membr.