Como injetar um valor de propriedade em um Spring Bean que foi configurado usando anotações?

294

Eu tenho um monte de beans Spring que são retirados do caminho de classe por meio de anotações, por exemplo

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

No arquivo XML Spring, há um PropertyPlaceholderConfigurer definido:

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

Desejo injetar uma das propriedades de app.properites no bean mostrado acima. Não posso simplesmente fazer algo como

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

Como PersonDaoImpl não aparece no arquivo XML Spring (ele é obtido no caminho de classe por meio de anotações). Eu tenho o seguinte:

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

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

Mas não está claro para mim como acesso o imóvel em que estou interessado ppc?

Dónal
fonte
1
Fiz essencialmente a mesma pergunta, embora em um cenário um pouco diferente: stackoverflow.com/questions/310271/… . Até agora, ninguém foi capaz de responder.
Spencer Kormos
Observe que a partir do Spring 3.1, PropertyPlaceholderConfigurernão é mais a classe recomendada. Prefira PropertySourcesPlaceholderConfigurer. Em qualquer caso, você pode usar a definição XML mais curta <context:property-placeholder />.
Michael Piefel 30/10

Respostas:

292

Você pode fazer isso no Spring 3 usando o suporte do EL. Exemplo:

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

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

systemPropertiesé um objeto implícito e strategyBeané um nome de feijão.

Mais um exemplo, que funciona quando você deseja obter uma propriedade de um Propertiesobjeto. Também mostra que você pode aplicar @Valueaos campos:

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

Aqui está um post que escrevi sobre isso para um pouco mais de informação.

Adeus StackExchange
fonte
8
É systemPropertiessimplesmente System.getProperties()? Eu acho que se eu quero para injetar minhas próprias propriedades em um bean Spring eu preciso definir um <bean id="appProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">valor, em seguida, ler a partir de que em outro feijão usando algo parecido@Value("#{appProperties.databaseName}")
Donal
11
Certifique-se de nota de resposta de máximo que você também pode usar espaços reservados nas expressões $ {db.doStuff}, então você não precisa de um PropertiesFactoryBean, apenas um placeholderConfigurer
gtrak
9
Você pode adicionar suas próprias propriedades usando util: properties; por exemplo, <util: properties id = "config" location = "classpath: /spring/environment.properties" />. Veja a resposta editada para obter o valor. (Sei que esta é provavelmente tarde demais para ter sido útil para Don, mas outros, esperamos beneficiar.)
2
Só funcionou para mim quando usei util: properties no meu arquivo appname-servlet.xml. O uso do propertyConfigurer definido em meu applicationContext.xml (não o Spring MVC) não funcionou.
Asaf Mesika
Para um pouco mais de leitura, que elabora em algumas das isso, confira esta pergunta SOF também: stackoverflow.com/questions/6425795/...
arcseldon
143

Pessoalmente, adoro essa nova maneira no Spring 3.0 a partir dos documentos :

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

Sem getters ou setters!

Com as propriedades sendo carregadas através da configuração:

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

Para aumentar ainda mais minha alegria, posso controlar o clique na expressão EL no IntelliJ e isso me leva à definição da propriedade!

Há também a versão totalmente não xml :

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

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
barrymac
fonte
9
certifique-se de adicionar no namespace uri xmlns: p = " springframework.org/schema/p " para usar os atributos prefixados p:
quer
3
Por que esses métodos funcionam em um contexto de teste, mas não no contexto principal?
luksmir 5/09/2013
9
suspiro, passei uma hora tentando fazer com que a abordagem apenas com anotações funcionasse e descobri o que falta apenas depois de ler esta resposta-declaração de um feijão estático mágico PropertySauceYadaYada. Amor de Primavera!
Kranach
@barrymac hey barry, você sabe qual é a diferença entre @Value (# {...}) e @Value ($ {...}). Obrigado
Kim
1
Isso funciona para mim. Apenas uma dica: a anotação @Component é necessária.
precisa saber é o seguinte
121

Há uma nova anotação @Valueno Spring 3.0.0M3 . @Valuesuporta não apenas #{...}expressões, mas também ${...}espaços reservados

AdrieanKhisbe
fonte
20
+1 Se um exemplo ajudar, aqui está - @Value (value = "# {'$ {server.env}'}") ou simplesmente @Value ("# {'$ {server.env}'}")
Somu
31

<context:property-placeholder ... /> é o XML equivalente ao PropertyPlaceholderConfigurer.

Exemplo: applicationContext.xml

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

Classe de componente

 private @Value("${propertyName}") String propertyField;
shane lee
fonte
1
Para mim, isso funcionou apenas se a fiação automática estiver ativada via <context:component-scan base-package="com.company.package" />Para referência, estou usando a mola via ApplicationContext, não em um contexto da web.
Mustafa
15

Outra alternativa é adicionar o bean appProperties mostrado abaixo:

<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>

Quando recuperado, esse bean pode ser convertido para um java.util.Propertiesque conterá uma propriedade chamada results.maxcujo valor é lido app.properties. Novamente, esse bean pode ser injetado por dependência (como uma instância de java.util.Properties) em qualquer classe por meio da anotação @Resource.

Pessoalmente, prefiro essa solução (à outra que propus), pois você pode limitar exatamente quais propriedades são expostas pelo appProperties e não precisa ler o app.properties duas vezes.

Dónal
fonte
Funciona para mim também. Mas não há outra maneira de acessar as propriedades de um PropertyPlaceholderConfigurer por meio da anotação @Value (ao usar vários PropertyPlaceholderConfigurer em vários arquivos XML congif.)?
Czar
9

Preciso ter dois arquivos de propriedades, um para produção e uma substituição para desenvolvimento (que não serão implantados).

Para ter os dois, um Bean de Propriedades que pode ser conectado automaticamente e um PropertyConfigurer, você pode escrever:

<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>

e faça referência ao Bean de Propriedades no PropertyConfigurer

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

Antes de chegarmos ao Spring 3 - que permite injetar constantes de propriedade diretamente nos seus beans usando anotações - escrevi uma subclasse do bean PropertyPlaceholderConfigurer que faz a mesma coisa. Portanto, você pode marcar seus configuradores de propriedades e o Spring transferirá suas propriedades automaticamente para seus beans da seguinte forma:

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

A anotação é a seguinte:

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

O PropertyAnnotationAndPlaceholderConfigurer é o seguinte:

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

}

Sinta-se livre para modificar a gosto

Ricardo Gladwell
fonte
3
Observe que eu criei um novo projeto para o acima: code.google.com/p/spring-property-annotations
Ricardo Gladwell
7

Você também pode anotar sua classe:

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

E tem uma variável como esta:

@Autowired
private Environment env;

Agora você pode acessar todas as suas propriedades desta maneira:

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

Maneira da primavera:
private @Value("${propertyName}") String propertyField;

é uma nova maneira de injetar o valor usando a classe "PropertyPlaceholderConfigurer" do Spring. Outra maneira é ligar

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

Nota: Para @Value, você não pode usar o propertyField estático , ele deve ser apenas não estático; caso contrário, ele retornará nulo. Para corrigi-lo, é criado um setter não estático para o campo estático e @Value é aplicado acima desse setter.

hi.nitish
fonte
7

Como mencionado @Value, o trabalho é bastante flexível, pois você pode ter um EL de mola.

Aqui estão alguns exemplos que podem ser úteis:

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

Outro para obter um setde umlist

//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;

Você também pode definir valores para tipos primitivos.

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

Você pode chamar métodos estáticos:

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

Você pode ter lógica

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

Uma solução possível é declarar um segundo bean que lê do mesmo arquivo de propriedades:

<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"/>

O bean chamado 'appProperties' é do tipo java.util.Properties e pode ser injetado por dependência usando o atributo @Resource mostrado acima.

Dónal
fonte
4

Se você estiver preso com o Spring 2.5, poderá definir um bean para cada uma de suas propriedades e injetá-los usando qualificadores. Como isso:

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

e

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

Não é super legível, mas faz o trabalho.

Nik
fonte
2

Valores da propriedade de fiação automática nos feijões da primavera:

A maioria das pessoas sabe que você pode usar o @Autowired para solicitar ao Spring que injete um objeto em outro quando ele carregar o contexto do aplicativo. Um nugget de informações menos conhecido é que você também pode usar a anotação @Value para injetar valores de um arquivo de propriedades nos atributos de um bean. veja este post para mais informações ...

novidades no Spring 3.0 || valores do bean de ligação automática || valores de propriedade de fiação automática na primavera

Por sorte
fonte
2

Para mim, foi a resposta da @ Lucky e, especificamente, a linha

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

a partir da página Capitão Debug

isso resolveu meu problema. Eu tenho um aplicativo baseado em ApplicationContext em execução na linha de comando e, a julgar por vários comentários sobre o SO, o Spring os liga de maneira diferente aos aplicativos baseados em MVC.

ben3000
fonte
1

Eu acho que é a maneira mais conveniente de injetar propriedades no método setter do bean.

Exemplo:

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

Definição de xml do Bean:

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

Para cada nome property métodosetProperty(value) será chamado.

Dessa forma, é especialmente útil se você precisar de mais de um bean com base em uma implementação.

Por exemplo, se definirmos mais um bean em xml:

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

Então codifique assim:

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

Vai imprimir

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

Portanto, no seu caso, deve ficar assim:

@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
fonte
0

Se você precisar de mais flexibilidade para as configurações, tente o Settings4jPlaceholderConfigurer: http://settings4j.sourceforge.net/currentrelease/configSpringPlaceholder.html

Em nossa aplicação, usamos:

  • Preferências para configurar o sistema PreProd e Prod
  • Preferências e variáveis ​​de ambiente JNDI (JNDI substitui as preferências) para "mvn jetty: run"
  • Propriedades do sistema para UnitTests (anotação @BeforeClass)

A ordem padrão pela qual o valor-chave-Source é verificado primeiro está descrita em:
http://settings4j.sourceforge.net/currentrelease/configDefault.html
Ele pode ser personalizado com um settings4j.xml (preciso para log4j.xml) no seu caminho de classe.

Deixe-me saber sua opinião: [email protected]

Atenciosamente,
Harald

brabenetz
fonte
-1

Use a classe "PropertyPlaceholderConfigurer" do Spring

Um exemplo simples mostrando o arquivo de propriedades lido dinamicamente como propriedade do 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> 

Arquivo de Propriedade

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
fonte