Как легко вводить строковые константы с помощью Weld? - PullRequest
15 голосов
/ 03 ноября 2010

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

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

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

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

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

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


РЕДАКТИРОВАТЬ 2013-10-16: Изучив Dagger, который работает во время компиляции, а не во время выполнения, я обнаружил, что у нас обычно 10-20 наПрограмма, которую мы могли бы использовать, имея метод @Provider для каждой строки конфигурации, который затем просматривается в карте конфигурации.Это учитывает специфическое поведение метода (включая значения по умолчанию), возможность предоставлять Javadoc и возможность помещать все эти методы в один класс.Также это хорошо работает с Weld из коробки.Я подумываю написать более полное объяснение в блоге.

Ответы [ 5 ]

12 голосов
/ 25 января 2011

Я бы хотел эту награду, пожалуйста. Понимание этого научило меня немного о внутренностях 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's 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 на используемый вами путь к классу:

weldtest.PerformSetup

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

11 голосов
/ 20 января 2011

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

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

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

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

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

@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;
0 голосов
/ 22 ноября 2010

Разве реализация пользовательской сварки InjectionServices здесь не подходит?

0 голосов
/ 04 января 2011

Может быть возможно реализовать это как метод @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.
0 голосов
/ 03 ноября 2010

а как же

@Resource(name = "server.username", type = java.lang.String.class)
private String injectTo;

Javadoc: http://download.oracle.com/javase/6/docs/api/javax/annotation/Resource.html

...