¿Cómo puedo inyectar un valor de propiedad en un Spring Bean que se configuró usando anotaciones?

294

Tengo un montón de frijoles Spring que se recogen del classpath a través de anotaciones, por ejemplo

@Repository("personDao")
public class PersonDaoImpl extends AbstractDaoImpl implements PersonDao {
    // Implementation omitted
}

En el archivo Spring XML, hay un PropertyPlaceholderConfigurer definido:

<bean id="propertyConfigurer" 
  class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="location" value="/WEB-INF/app.properties" />
</bean> 

Quiero inyectar una de las propiedades de app.properites en el bean que se muestra arriba. No puedo simplemente hacer algo como

<bean class="com.example.PersonDaoImpl">
    <property name="maxResults" value="${results.max}"/>
</bean>

Debido a que PersonDaoImpl no aparece en el archivo Spring XML (se recoge del classpath a través de anotaciones). Llegué a lo siguiente:

@Repository("personDao")
public class PersonDaoImpl extends AbstractDaoImpl implements PersonDao {

    @Resource(name = "propertyConfigurer")
    protected void setProperties(PropertyPlaceholderConfigurer ppc) {
    // Now how do I access results.max? 
    }
}

¿Pero no tengo claro cómo accedo a la propiedad que me interesa ppc?

Dónal
fuente
1
He hecho esencialmente la misma pregunta, aunque en un escenario ligeramente diferente: stackoverflow.com/questions/310271/… . Hasta ahora, nadie ha podido responder.
Spencer Kormos el
Tenga en cuenta que a partir de Spring 3.1, PropertyPlaceholderConfigurerya no es la clase recomendada. Prefiero en su PropertySourcesPlaceholderConfigurerlugar. En cualquier caso, puede usar la definición XML más corta <context:property-placeholder />.
Michael Piefel

Respuestas:

292

Puedes hacer esto en Spring 3 usando el soporte EL. Ejemplo:

@Value("#{systemProperties.databaseName}")
public void setDatabaseName(String dbName) { ... }

@Value("#{strategyBean.databaseKeyGenerator}")
public void setKeyGenerator(KeyGenerator kg) { ... }

systemPropertieses un objeto implícito y strategyBeanes un nombre de bean.

Un ejemplo más, que funciona cuando quieres tomar una propiedad de un Propertiesobjeto. También muestra que puede aplicar @Valuea los campos:

@Value("#{myProperties['github.oauth.clientId']}")
private String githubOauthClientId;

Aquí hay una publicación de blog que escribí sobre esto para obtener un poco más de información.

Adiós StackExchange
fuente
8
Es systemPropertiessimplemente System.getProperties()? Supongo que si quiero inyectar mis propias propiedades en un Spring Bean, necesito definir un <bean id="appProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">valor de lectura de eso en otro Bean usando algo como@Value("#{appProperties.databaseName}")
Dónal
11
Asegúrese de tener en cuenta en la respuesta de max que también puede usar marcadores de posición en las expresiones $ {db.doStuff}, entonces no necesita un PropertiesFactoryBean, solo un marcador de posiciónConfigurer
gtrak
99
Puede agregar sus propias propiedades usando util: properties; por ejemplo, <util: properties id = "config" location = "classpath: /spring/environment.properties" />. Vea la respuesta editada para saber cómo obtener el valor. (Me di cuenta que es probable que sea demasiado tarde para haber sido útiles a Don, pero otros con suerte se beneficiará.)
2
Solo funcionó para mí cuando usé util: properties en mi archivo appname-servlet.xml. El uso de propertyConfigurer definido en mi applicationContext.xml (no el Spring MVC) no funcionó.
Asaf Mesika
Para un poco más de lectura, que desarrolla algo de esto, vea también esta pregunta de SOF: stackoverflow.com/questions/6425795/…
arcseldon
143

Personalmente, me encanta esta nueva forma en Spring 3.0 de los documentos :

private @Value("${propertyName}") String propertyField;

¡Sin captadores ni colocadores!

Con las propiedades que se cargan a través de la configuración:

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"
      p:location="classpath:propertyFile.properties" name="propertiesBean"/>

Para aumentar mi alegría, incluso puedo controlar hacer clic en la expresión EL en IntelliJ y me lleva a la definición de la propiedad.

También existe la versión totalmente no xml :

@PropertySource("classpath:propertyFile.properties")
public class AppConfig {

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
barrymac
fuente
99
asegúrese de agregar en el espacio de nombres uri xmlns: p = " springframework.org/schema/p " para usar los atributos prefijados p:
Shane Lee
3
¿Por qué este método funciona en un contexto de prueba pero no en el contexto principal?
luksmir
99
suspiro, pasé una hora tratando de hacer que el enfoque de solo anotaciones funcionara y descubrí lo que falta solo después de leer esta declaración de respuesta de un bean estático mágico PropertySauceYadaYada. ¡Amor de primavera!
Kranach
@barrymac hey barry, ¿sabes cuál es la diferencia entre @Value (# {...}) y @Value ($ {...})? Gracias
Kim
1
Esto funciona para mi. Solo un consejo: se requiere anotación @Component.
yaki_nuka
121

Hay una nueva anotación @Valueen Spring 3.0.0M3 . @Valueno solo admite #{...}expresiones, sino también ${...}marcadores de posición

AdrieanKhisbe
fuente
20
+1 Si un ejemplo ayuda, aquí está: @Value (value = "# {'$ {server.env}'}") o simplemente @Value ("# {'$ {server.env}'}")
Somu
31

<context:property-placeholder ... /> es el equivalente XML al PropertyPlaceholderConfigurer.

Ejemplo: applicationContext.xml

<context:property-placeholder location="classpath:test.properties"/>  

Clase de componentes

 private @Value("${propertyName}") String propertyField;
Shane Lee
fuente
1
Para mí, esto funcionó solo si el cableado automático está habilitado a través de <context:component-scan base-package="com.company.package" />Para referencia, estoy usando spring a través de ApplicationContext, no en un contexto web.
Mustafa
15

Otra alternativa es agregar el bean appProperties que se muestra a continuación:

<bean id="propertyConfigurer"   
  class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="/WEB-INF/app.properties" />
</bean> 


<bean id="appProperties" 
          class="org.springframework.beans.factory.config.PropertiesFactoryBean">
        <property name="singleton" value="true"/>

        <property name="properties">
                <props>
                        <prop key="results.max">${results.max}</prop>
                </props>
        </property>
</bean>

Cuando se recupera, este bean se puede convertir en un java.util.Propertiesque contendrá una propiedad results.maxcuyo valor se lee app.properties. Nuevamente, este bean se puede inyectar dependencia (como una instancia de java.util.Properties) en cualquier clase a través de la anotación @Resource.

Personalmente, prefiero esta solución (a la otra que propuse), ya que puede limitar exactamente qué propiedades están expuestas por appProperties y no es necesario leer app.properties dos veces.

Dónal
fuente
A mí también me funciona. Pero, ¿no hay otra forma de acceder a las propiedades desde un PropertyPlaceholderConfigurer a través de la anotación @Value (cuando se utilizan múltiples PropertyPlaceholderConfigurer en varios archivos XML congif)?
Zar
9

Necesito tener dos archivos de propiedades, uno para producción y una anulación para desarrollo (que no se implementará).

Para tener ambos, un bean de propiedades que se puede conectar automáticamente y un PropertyConfigurer, puede escribir:

<bean id="appProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
    <property name="singleton" value="true" />

    <property name="ignoreResourceNotFound" value="true" />
    <property name="locations">
        <list>
            <value>classpath:live.properties</value>
            <value>classpath:development.properties</value>
        </list>
    </property>
</bean>

y haga referencia al bean de propiedades en el PropertyConfigurer

<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="properties" ref="appProperties" />
</bean>
Willi aus Rohr
fuente
7

Antes de obtener Spring 3, que le permite inyectar constantes de propiedad directamente en sus beans mediante anotaciones, escribí una subclase del bean PropertyPlaceholderConfigurer que hace lo mismo. Por lo tanto, puede marcar sus establecedores de propiedades y Spring conectará automáticamente sus propiedades a sus beans de la siguiente manera:

@Property(key="property.key", defaultValue="default")
public void setProperty(String property) {
    this.property = property;
}

La anotación es la siguiente:

@Retention(RetentionPolicy.RUNTIME) 
@Target({ElementType.METHOD, ElementType.FIELD})
public @interface Property {
    String key();
    String defaultValue() default "";
}

El PropertyAnnotationAndPlaceholderConfigurer es el siguiente:

public class PropertyAnnotationAndPlaceholderConfigurer extends PropertyPlaceholderConfigurer {

    private static Logger log = Logger.getLogger(PropertyAnnotationAndPlaceholderConfigurer.class);

    @Override
    protected void processProperties(ConfigurableListableBeanFactory beanFactory, Properties properties) throws BeansException {
        super.processProperties(beanFactory, properties);

        for (String name : beanFactory.getBeanDefinitionNames()) {
            MutablePropertyValues mpv = beanFactory.getBeanDefinition(name).getPropertyValues();
            Class clazz = beanFactory.getType(name);

            if(log.isDebugEnabled()) log.debug("Configuring properties for bean="+name+"["+clazz+"]");

            if(clazz != null) {
                for (PropertyDescriptor property : BeanUtils.getPropertyDescriptors(clazz)) {
                    Method setter = property.getWriteMethod();
                    Method getter = property.getReadMethod();
                    Property annotation = null;
                    if(setter != null && setter.isAnnotationPresent(Property.class)) {
                        annotation = setter.getAnnotation(Property.class);
                    } else if(setter != null && getter != null && getter.isAnnotationPresent(Property.class)) {
                        annotation = getter.getAnnotation(Property.class);
                    }
                    if(annotation != null) {
                        String value = resolvePlaceholder(annotation.key(), properties, SYSTEM_PROPERTIES_MODE_FALLBACK);
                        if(StringUtils.isEmpty(value)) {
                            value = annotation.defaultValue();
                        }
                        if(StringUtils.isEmpty(value)) {
                            throw new BeanConfigurationException("No such property=["+annotation.key()+"] found in properties.");
                        }
                        if(log.isDebugEnabled()) log.debug("setting property=["+clazz.getName()+"."+property.getName()+"] value=["+annotation.key()+"="+value+"]");
                        mpv.addPropertyValue(property.getName(), value);
                    }
                }

                for(Field field : clazz.getDeclaredFields()) {
                    if(log.isDebugEnabled()) log.debug("examining field=["+clazz.getName()+"."+field.getName()+"]");
                    if(field.isAnnotationPresent(Property.class)) {
                        Property annotation = field.getAnnotation(Property.class);
                        PropertyDescriptor property = BeanUtils.getPropertyDescriptor(clazz, field.getName());

                        if(property.getWriteMethod() == null) {
                            throw new BeanConfigurationException("setter for property=["+clazz.getName()+"."+field.getName()+"] not available.");
                        }

                        Object value = resolvePlaceholder(annotation.key(), properties, SYSTEM_PROPERTIES_MODE_FALLBACK);
                        if(value == null) {
                            value = annotation.defaultValue();
                        }
                        if(value == null) {
                            throw new BeanConfigurationException("No such property=["+annotation.key()+"] found in properties.");
                        }
                        if(log.isDebugEnabled()) log.debug("setting property=["+clazz.getName()+"."+field.getName()+"] value=["+annotation.key()+"="+value+"]");
                        mpv.addPropertyValue(property.getName(), value);
                    }
                }
            }
        }
    }

}

Siéntase libre de modificar al gusto

Ricardo Gladwell
fuente
3
Tenga en cuenta que he creado un nuevo proyecto para lo anterior: code.google.com/p/spring-property-annotations
Ricardo Gladwell el
7

También puedes anotar tu clase:

@PropertySource("classpath:/com/myProject/config/properties/database.properties")

Y tener una variable como esta:

@Autowired
private Environment env;

Ahora puede acceder a todas sus propiedades de esta manera:

env.getProperty("database.connection.driver")
Alexis Gamarra
fuente
7

Camino de primavera:
private @Value("${propertyName}") String propertyField;

es una nueva forma de inyectar el valor utilizando la clase "PropertyPlaceholderConfigurer" de Spring. Otra forma es llamar

java.util.Properties props = System.getProperties().getProperty("propertyName");

Nota: Para @Value, no puede usar static propertyField, debe ser no estático solamente, de lo contrario, devuelve nulo. Para solucionarlo, se crea un setter no estático para el campo estático y @Value se aplica por encima de ese setter.

hola.nitish
fuente
7

Como se mencionó @Valuehace el trabajo y es bastante flexible, ya que puede tener Spring EL en él.

Aquí hay algunos ejemplos que podrían ser útiles:

//Build and array from comma separated parameters 
//Like currency.codes.list=10,11,12,13
@Value("#{'${currency.codes.list}'.split(',')}") 
private List<String> currencyTypes;

Otro para obtener un setde unlist

//If you have a list of some objects like (List<BranchVO>) 
//and the BranchVO has areaCode,cityCode,...
//You can easily make a set or areaCodes as below
@Value("#{BranchList.![areaCode]}") 
private Set<String> areas;

También puede establecer valores para tipos primitivos.

@Value("${amount.limit}")
private int amountLimit;

Puedes llamar a métodos estáticos:

@Value("#{T(foo.bar).isSecurityEnabled()}")
private boolean securityEnabled;

Puedes tener lógica

@Value("#{T(foo.bar).isSecurityEnabled() ? '${security.logo.path}' : '${default.logo.path}'}")
private String logoPath;
Alireza Fattahi
fuente
5

Una posible solución es declarar un segundo bean que lee del mismo archivo de propiedades:

<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="location" value="/WEB-INF/app.properties" />
</bean> 

<util:properties id="appProperties" location="classpath:/WEB-INF/app.properties"/>

El bean llamado 'appProperties' es del tipo java.util.Properties y se puede inyectar dependencia usando el atributo @Resource que se muestra arriba.

Dónal
fuente
4

Si está atascado con Spring 2.5, puede definir un bean para cada una de sus propiedades e inyectarlas con calificadores. Me gusta esto:

  <bean id="someFile" class="java.io.File">
    <constructor-arg value="${someFile}"/>
  </bean>

y

@Service
public class Thing
      public Thing(@Qualifier("someFile") File someFile) {
...

No es súper legible pero hace el trabajo.

Nik
fuente
2

Autoconexión de valores de propiedad en frijoles de primavera:

La mayoría de la gente sabe que puede usar @Autowired para decirle a Spring que inyecte un objeto en otro cuando carga el contexto de su aplicación. Una pepita de información menos conocida es que también puede usar la anotación @Value para inyectar valores de un archivo de propiedades en los atributos de un bean. mira esta publicación para más información ...

cosas nuevas en Spring 3.0 || valores de bean de cableado automático || valores de propiedad de autoconexión en primavera

Suerte
fuente
2

Para mí, fue la respuesta de @ Lucky, y específicamente, la línea

AutowiredFakaSource fakeDataSource = ctx.getBean(AutowiredFakaSource.class);

de la página Capitán Depuración

Eso solucionó mi problema. Tengo una aplicación basada en ApplicationContext que se ejecuta desde la línea de comandos y, a juzgar por una serie de comentarios sobre SO, Spring los conecta de manera diferente a las aplicaciones basadas en MVC.

ben3000
fuente
1

Creo que la forma más conveniente de inyectar propiedades en el bean es el método setter.

Ejemplo:

package org.some.beans;

public class MyBean {
    Long id;
    String name;

    public void setId(Long id) {
        this.id = id;
    }

    public Long getId() {
        return id;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

Definición xml de frijol:

<bean id="Bean1" class="org.some.beans.MyBean">
    <property name="id" value="1"/>
    <property name="name" value="MyBean"/>
</bean>

Por cada propertymétodo nombrado setProperty(value)se invocará.

De esta manera es especialmente útil si necesita más de un bean basado en una implementación.

Por ejemplo, si definimos un bean más en xml:

<bean id="Bean2" class="org.some.beans.MyBean">
    <property name="id" value="2"/>
    <property name="name" value="EnotherBean"/>
</bean>

Entonces código como este:

MyBean b1 = appContext.getBean("Bean1");
System.out.println("Bean id = " + b1.getId() + " name = " + b1.getName());
MyBean b2 = appContext.getBean("Bean2");
System.out.println("Bean id = " + b2.getId() + " name = " + b2.getName());

Imprimirá

Bean id = 1 name = MyBean
Bean id = 2 name = AnotherBean

Entonces, en su caso, debería verse así:

@Repository("personDao")
public class PersonDaoImpl extends AbstractDaoImpl implements PersonDao {

    Long maxResults;

    public void setMaxResults(Long maxResults) {
        this.maxResults = maxResults;
    }

    // Now use maxResults value in your code, it will be injected on Bean creation
    public void someMethod(Long results) {
        if (results < maxResults) {
            ...
        }
    }
}
Sergei Pikalev
fuente
0

Si necesita más flexibilidad para las configuraciones, pruebe el Settings4jPlaceholderConfigurer: http://settings4j.sourceforge.net/currentrelease/configSpringPlaceholder.html

En nuestra aplicación utilizamos:

  • Preferencias para configurar el sistema PreProd y Prod
  • Preferencias y variables de entorno JNDI (JNDI sobrescribe las preferencias) para "mvn jetty: run"
  • Propiedades del sistema para UnitTests (anotación @BeforeClass)

El orden predeterminado en el que se verifica primero la fuente-valor-clave se describe en:
http://settings4j.sourceforge.net/currentrelease/configDefault.html
Se puede personalizar con un settings4j.xml (preciso para log4j.xml) en su classpath

Déjame saber tu opinión: [email protected]

con saludos amistosos,
Harald

brabenetz
fuente
-1

Utilice la clase "PropertyPlaceholderConfigurer" de Spring

Un ejemplo simple que muestra el archivo de propiedades leído dinámicamente como propiedad del bean

<bean id="placeholderConfig"
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
        <list>
            <value>/WEB-INF/classes/config_properties/dev/database.properties</value>
        </list>
    </property> 
</bean>

<bean id="devDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
    <property name="driverClass" value="${dev.app.jdbc.driver}"/>
    <property name="jdbcUrl" value="${dev.app.jdbc.url}"/>
    <property name="user" value="${dev.app.jdbc.username}"/>
    <property name="password" value="${dev.app.jdbc.password}"/>
    <property name="acquireIncrement" value="3"/>
    <property name="minPoolSize" value="5"/>
    <property name="maxPoolSize" value="10"/>
    <property name="maxStatementsPerConnection" value="11000"/>
    <property name="numHelperThreads" value="8"/>
    <property name="idleConnectionTestPeriod" value="300"/>
    <property name="preferredTestQuery" value="SELECT 0"/>
</bean> 

Archivo de propiedades

dev.app.jdbc.driver = com.mysql.jdbc.Driver

dev.app.jdbc.url = jdbc: mysql: // localhost: 3306 / addvertisement

dev.app.jdbc.username = root

dev.app.jdbc.password = root

ravi ranjan
fuente