2010-11-03 9 views
13

Tenemos una situación en la que proporcionamos una configuración externa en forma de un mapa para nuestros programas en ejecución. He descubierto que JSR-330 Dependency Injection ofrece una manera mucho más limpia de usar ese mapa de configuración en el código en lugar de pasar el mapa o usar JNDI para obtenerlo.Cómo inyectar constantes de cadenas fácilmente con Weld?

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

permite que la implementación de JSR-330 llene este campo automáticamente.

Con Guice I puede establecer el valor con

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

me gustaría ser capaz de hacer lo mismo en Weld ("server.username" se unen a, por ejemplo "foobar") y entiendo que el mecanismo lo más probable es beans.xml, pero prefiero una alternativa de código "alimentar este mapa a Weld, por favor". ¿Cuál sería una buena manera de hacer esto?


EDITAR 10/16/2013: Después de mirar en la daga que funciona en tiempo de compilación y no tiempo de ejecución, que encontró que con nosotros por lo general con un 10-20 por programa que podría vivir con tener un método para cada configuración @Provider cadena que luego busca en el mapa de configuración. Esto permite el comportamiento específico del método (incluidos los valores predeterminados), la capacidad de proporcionar javadoc y la capacidad de poner todos estos métodos en la misma clase. También funciona bien con Weld fuera de la caja. Estoy considerando escribir una explicación más completa en una entrada de blog.

Respuesta

9

me gustaría que recompensa ahora, por favor. Entender esto me enseñó bastante sobre las entrañas de WELD, y esta es la lección más interesante: @Named es un calificador, y debe tratarse como tal si vas a poder enfrentarte.

Tengo una advertencia para usted: si le falta algún valor en su aplicación, fallará en la implementación o en el tiempo de carga. Esto puede ser conveniente para usted, pero significa específicamente que los valores "predeterminados" no son posibles.

El punto de inyección se especifica exactamente como usted tiene arriba, y aquí está el código de extensión necesaria para hacer que funcione:

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

    } 
} 

He probado que este trabajó haciendo una aplicación 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); 
    } 

} 

Por último, no se olvide /META-INF/services/javax.enterprise.inject.spi.Extension, en sustitución de weldtest con la ruta de clase que utilice:

weldtest.PerformSetup 

Eso debería hacer que todo esto funcione. Avíseme si encuentra dificultades y le enviaré mi proyecto de prueba.

+0

Gracias por su arduo trabajo. Bounty papeleo iniciado. –

+0

Con respecto al "error en el tiempo de implementación si falta el valor". En nuestra situación, inyectando valores de configuración, ¡eso es una característica! –

+1

@ Thorbjørn Ravn Andersen Gracias por la recompensa. Ese fue el pico más dramático en la reputación. – SplinterReality

0

manera la ejecución de una costumbre de soldadura InjectionServices no ser una opción aquí?

+0

Si escribe un ejemplo funcional que, dado un Map , puede hacer lo que necesito, Abriré una nueva recompensa y te daré. –

+0

La oferta aún está abierta. –

+0

Y está abierto a todo el mundo :) implementar esto para una recompensa de 500 puntos. –

0

Puede ser posible implementar esto como un método @Dependent Producer que a su vez inyecta un @InjectionPoint que le permitiría reflexionar sobre el campo en el que se está inyectando, esto le permitiría echar un vistazo a una anotación personalizada (no un partido de clasificación) miembro en el campo de averiguar el val desea volver

@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

No hay ninguna razón para no combinar el calificador y la anotación @Val. La pieza que te falta es que el valor del @ConfigMapQualifier debe anotarse como @Nonbinding para que WELD no tenga en cuenta el valor para la coincidencia de punto de inyección/productor. Es una buena pieza de conocimiento, ya que separar el valor del calificador como este es propenso a errores. – SplinterReality

10

no todo lo que interesa en la abundancia, pero yo lo llevo si todavía está sobre la mesa. Esto es MUY similar a un código que estoy usando en $ DAYJOB, por lo que esto no es una teoría, es lo que uso en el código de producción, pero modificado para proteger al culpable. No intenté compilar el código modificado, así que ten en cuenta que puedo haber cometido algunos errores al cambiar nombres y cosas por el estilo, pero los principios que intervienen aquí han sido probados y funcionan.

En primer lugar, necesita un calificador de titular de valor. Utilice @Nonbinding para evitar que WELD coincida SOLAMENTE con calificadores con valores idénticos, ya que queremos que todos los valores de este calificador en particular coincidan con un único punto de inyección. Al mantener el calificador y el valor en la misma anotación, no puede simplemente "olvidar" uno de ellos por accidente. (Principio de 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(); 
} 

A continuación, necesita un método de productor que sepa cómo obtener el mapa. Probablemente deberías tener un bean Nombrado que contenga el método de productor, por lo que puedes inicializar explícitamente el valor usando getters/setters, o bien que el bean lo inicialice por ti.

Debemos especificar un valor en blanco para el calificador en el método del productor para evitar errores de tiempo de compilación, pero nunca se utiliza en la práctica.

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

uso es simple:

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

Muy, muy cerca. ¿Esto puede funcionar con @Named o debo tener una nueva anotación? –

+1

Nunca he tratado de poner cadenas en el espacio de nombres de WELD global, pero eso no significa que no sea posible. Lo que diré es que requerirá más investigación y esfuerzo que la solución que acabo de presentar. Lo que buscaría para la solución @Named es la interfaz de BeanManager. Te daré uno teórico que he hecho con otros tipos de frijoles, pero no lo he intentado con Strings hasta el momento, pero no veo razón para que no funcione. – SplinterReality

+0

@ Thorbjørn Ravn Andersen Creo que esta es la solución correcta. En primavera, por ejemplo, tiene una anotación especial - '@ Value', y tiene sentido. – Bozho

Cuestiones relacionadas