2010-11-03 5 views
13

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

@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: Посмотрев в Dagger, который работает во время компиляции, а не во время выполнения, я обнаружил, что с нами, как правило, имеющие 10-20 в программе мы могли бы жить с наличием метода @Provider для каждой конфигурации которая затем просматривает конфигурационную карту. Это позволяет определять поведение конкретного метода (включая значения по умолчанию), возможность предоставления javadoc и возможность использовать все эти методы в одном классе. Также он отлично работает с Weld из коробки. Я рассматриваю возможность написания более подробного объяснения в блоге.

ответ

9

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

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

+0

Спасибо за вашу тяжелую работу. Начато оформление бумажных документов. –

+0

Что касается «сбоя при развертывании, если значение отсутствует». В нашей ситуации - ввод значений конфигурации - это особенность! –

+1

@ Thorbjørn Ravn Andersen Спасибо за щедрость. Это был драматический всплеск репутации. – SplinterReality

0

бы реализации пользовательских Weld InjectionServices не быть вариантом здесь?

+0

Если вы напишете функциональный пример, который, учитывая Map , может делать то, что мне нужно, Я открою новую щедрость и дам вам. –

+0

Предложение по-прежнему открыто. –

+0

И он открыт для всех :) реализуйте это за 500 очков. –

0

Возможно, это можно реализовать как метод @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

Нет причин не комбинировать классификатор и аннотацию @Val. Часть, которую вам не хватает, заключается в том, что значение в @ConfigMapQualifier необходимо аннотировать как @Nonbinding, чтобы WELD не учитывал значение для соответствия методам инъекции/производителя. Это хорошая доля знаний, поскольку разделение значения от классификатора, подобного этому, является склонным к ошибкам. – SplinterReality

10

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

Во-первых, вам нужен квалификатор держателя ценности. Используйте @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(); 
} 

Далее вам нужен метод производителя, который знает, как получить карту. Вероятно, у вас должен быть именованный 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; 
+0

Очень, очень близко. Может ли это работать с @Named или у меня есть новая аннотация? –

+1

Я никогда не пытался помещать строки в глобальное пространство имен WELD, но это не значит, что это невозможно. Я скажу, что это потребует больше исследований и усилий, чем решение, которое я только что представил. То, что я хотел бы рассмотреть для решения @Named, это интерфейс BeanManager. Я дам вам теоретический, который я сделал с другими типами бобов, но до сих пор не пытался использовать Strings, но не вижу причин, по которым он не должен работать. – SplinterReality

+0

@ Thorbjørn Ravn Andersen Я считаю, что это правильное решение. Весной, например, у вас есть специальная аннотация - '@ Value', и это имеет смысл. – Bozho