Comment puis-je injecter une valeur de propriété dans un Spring Bean qui a été configuré à l'aide d'annotations?

294

J'ai un tas de haricots de printemps qui sont récupérés dans le chemin de classe via des annotations, par exemple

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

Dans le fichier XML Spring, un PropertyPlaceholderConfigurer est défini:

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

Je souhaite injecter l'une des propriétés de app.properites dans le bean illustré ci-dessus. Je ne peux pas simplement faire quelque chose comme

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

Parce que PersonDaoImpl ne figure pas dans le fichier XML Spring (il est récupéré dans le chemin de classe via des annotations). J'ai autant que ce qui suit:

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

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

Mais je ne sais pas comment j'accède à la propriété qui m'intéresse ppc?

Dónal
la source
1
J'ai posé essentiellement la même question, bien que dans un scénario légèrement différent: stackoverflow.com/questions/310271/… . Jusqu'à présent, personne n'a pu y répondre.
Spencer Kormos
Veuillez noter qu'à partir du printemps 3.1, ce PropertyPlaceholderConfigurern'est plus la classe recommandée. Préférez PropertySourcesPlaceholderConfigurerplutôt. Dans tous les cas, vous pouvez utiliser la définition XML plus courte <context:property-placeholder />.
Michael Piefel

Réponses:

292

Vous pouvez le faire au printemps 3 en utilisant le support EL. Exemple:

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

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

systemPropertiesest un objet implicite et strategyBeanest un nom de bean.

Un autre exemple, qui fonctionne lorsque vous souhaitez récupérer une propriété d'un Propertiesobjet. Il montre également que vous pouvez appliquer @Valueaux champs:

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

Voici un article de blog que j'ai écrit à ce sujet pour un peu plus d'informations.

Au revoir StackExchange
la source
8
Est-ce systemPropertiessimplement System.getProperties()? Je suppose que si je veux injecter mes propres propriétés dans un bean Spring, je dois définir une valeur <bean id="appProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">puis la lire dans un autre bean en utilisant quelque chose comme@Value("#{appProperties.databaseName}")
Dónal
11
Assurez-vous de noter dans la réponse de max que vous pouvez également utiliser des espaces réservés dans les expressions $ {db.doStuff}, alors vous n'avez pas besoin d'un PropertiesFactoryBean, juste d'un placeholderConfigurer
gtrak
9
Vous pouvez ajouter vos propres propriétés en utilisant util: properties; par exemple, <util: properties id = "config" location = "classpath: /spring/environment.properties" />. Voir la réponse modifiée pour savoir comment obtenir la valeur. (Je réalise que c'est probablement trop tard pour avoir été utile à Don, mais j'espère que d'autres en bénéficieront.)
2
Cela ne fonctionnait pour moi que lorsque j'utilisais les propriétés util: dans mon fichier appname-servlet.xml. L'utilisation de propertyConfigurer définie dans mon applicationContext.xml (pas celle de Spring MVC) n'a pas fonctionné.
Asaf Mesika
Pour un peu plus de lecture, qui élabore sur certains de cela, consultez aussi cette question SOF: stackoverflow.com/questions/6425795/…
arcseldon
143

Personnellement, j'aime cette nouvelle façon dans Spring 3.0 de la documentation :

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

Pas de getters ni de setters!

Avec les propriétés chargées via la configuration:

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

Pour poursuivre ma joie, je peux même contrôler le clic sur l'expression EL dans IntelliJ et cela m'amène à la définition de la propriété!

Il y a aussi la version totalement non xml :

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

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
barrymac
la source
9
assurez-vous et ajoutez dans l'espace de noms uri xmlns: p = " springframework.org/schema/p " pour utiliser les attributs p: préfixés.
shane lee
3
Pourquoi cette méthode fonctionne dans un contexte de test mais pas dans le contexte principal?
luksmir
9
soupir, j'ai passé une heure à essayer de faire fonctionner l'approche des annotations uniquement et j'ai découvert ce qui manquait seulement après avoir lu cette réponse-déclaration d'un haricot statique magique PropertySauceYadaYada. Amour de printemps!
Kranach
@barrymac hey barry, savez-vous quelle est la différence entre @Value (# {...}) et @Value ($ {...}). Merci
Kim
1
Cela fonctionne pour moi. Un seul conseil: l'annotation @Component est requise.
yaki_nuka
121

Il existe une nouvelle annotation @Valuedans Spring 3.0.0M3 . @Valueprendre en charge non seulement les #{...}expressions mais ${...}aussi les espaces réservés

AdrieanKhisbe
la source
20
+1 Si un exemple aide, voici - @Value (value = "# {'$ {server.env}'}") ou simplement @Value ("# {'$ {server.env}'}")
Somu
31

<context:property-placeholder ... /> est l'équivalent XML de PropertyPlaceholderConfigurer.

Exemple: applicationContext.xml

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

Classe de composants

 private @Value("${propertyName}") String propertyField;
shane lee
la source
1
Pour moi, cela ne fonctionne que si le câblage automatique est activé via <context:component-scan base-package="com.company.package" />Pour référence, j'utilise le ressort via le ApplicationContext, pas dans un contexte Web.
Mustafa
15

Une autre alternative consiste à ajouter le bean appProperties illustré ci-dessous:

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

Une fois récupéré, ce bean peut être converti en un java.util.Propertiesqui contiendra une propriété nommée results.maxdont la valeur est lue dansapp.properties . Encore une fois, ce bean peut être injecté en dépendance (comme une instance de java.util.Properties) dans n'importe quelle classe via l'annotation @Resource.

Personnellement, je préfère cette solution (à l'autre que j'ai proposée), car vous pouvez limiter exactement les propriétés exposées par appProperties et n'avez pas besoin de lire app.properties deux fois.

Dónal
la source
Fonctionne aussi pour moi. Mais n'y a-t-il pas d'autre moyen d'accéder aux propriétés d'un PropertyPlaceholderConfigurer via l'annotation @Value (lors de l'utilisation de plusieurs PropertyPlaceholderConfigurer dans plusieurs fichiers XML congif.)?
Czar
9

J'ai besoin d'avoir deux fichiers de propriétés, un pour la production et un remplacement pour le développement (qui ne sera pas déployé).

Pour avoir les deux, un bean de propriétés qui peut être câblé automatiquement et un PropertyConfigurer, vous pouvez écrire:

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

et référencer le bean de propriétés dans le PropertyConfigurer

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

Avant d'obtenir Spring 3 - qui vous permet d'injecter des constantes de propriété directement dans vos beans à l'aide d'annotations - j'ai écrit une sous-classe du bean PropertyPlaceholderConfigurer qui fait la même chose. Ainsi, vous pouvez annoter vos setters de propriétés et Spring câblera automatiquement vos propriétés dans vos beans comme ceci:

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

L'annotation est la suivante:

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

Le PropertyAnnotationAndPlaceholderConfigurer est le suivant:

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

}

N'hésitez pas à modifier au goût

Ricardo Gladwell
la source
3
Veuillez noter que j'ai créé un nouveau projet pour ce qui précède: code.google.com/p/spring-property-annotations
Ricardo Gladwell
7

Vous pouvez également annoter votre classe:

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

Et avoir une variable comme celle-ci:

@Autowired
private Environment env;

Vous pouvez maintenant accéder à toutes vos propriétés de cette manière:

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

Manière de ressort:
private @Value("${propertyName}") String propertyField;

est une nouvelle façon d'injecter la valeur en utilisant la classe "PropertyPlaceholderConfigurer" de Spring. Une autre façon est d'appeler

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

Remarque: Pour @Value, vous ne pouvez pas utiliser static propertyField, il doit être non statique uniquement, sinon il renvoie null. Pour y remédier, un setter non statique est créé pour le champ statique et @Value est appliqué au-dessus de ce setter.

hi.nitish
la source
7

Comme mentionné, @Valuele travail est fait et il est assez flexible car vous pouvez y avoir un ressort EL.

Voici quelques exemples qui pourraient être utiles:

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

Un autre pour obtenir setunlist

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

Vous pouvez également définir des valeurs pour les types primitifs.

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

Vous pouvez appeler des méthodes statiques:

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

Vous pouvez avoir de la logique

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

Une solution possible consiste à déclarer un second bean qui lit à partir du même fichier de propriétés:

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

Le bean nommé 'appProperties' est de type java.util.Properties et peut être injecté en dépendance à l'aide de l'attruibute @Resource illustré ci-dessus.

Dónal
la source
4

Si vous êtes bloqué avec Spring 2.5, vous pouvez définir un bean pour chacune de vos propriétés et les injecter à l'aide de qualificatifs. Comme ça:

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

et

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

Ce n'est pas super lisible mais il fait le travail.

Nik
la source
2

Auto-câblage des valeurs de propriété dans Spring Beans:

La plupart des gens savent que vous pouvez utiliser @Autowired pour dire à Spring d'injecter un objet dans un autre lorsqu'il charge votre contexte d'application. Une pépite d'informations moins connue est que vous pouvez également utiliser l'annotation @Value pour injecter des valeurs d'un fichier de propriétés dans les attributs d'un bean. voir cet article pour plus d'informations ...

nouveaux trucs au printemps 3.0 || valeurs du bean à câblage automatique || valeurs des propriétés de câblage automatique au printemps

Chanceux
la source
2

Pour moi, c'était la réponse de @ Lucky, et plus précisément, la ligne

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

depuis la page Captain Debug

qui a résolu mon problème. J'ai une application basée sur ApplicationContext exécutée à partir de la ligne de commande, et à en juger par un certain nombre de commentaires sur SO, Spring les relie différemment aux applications basées sur MVC.

ben3000
la source
1

Je pense que c'est le moyen le plus pratique pour injecter des propriétés dans le bean est la méthode setter.

Exemple:

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

Définition XML du bean:

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

Pour chaque propertyméthode nommée setProperty(value)sera invoquée.

Cette méthode est particulièrement utile si vous avez besoin de plus d'un bean basé sur une implémentation.

Par exemple, si nous définissons un bean de plus en xml:

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

Ensuite, codez comme ceci:

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

Imprime

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

Donc, dans votre cas, cela devrait ressembler à ceci:

@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
la source
0

Si vous avez besoin de plus de flexibilité pour les configurations, essayez le Settings4jPlaceholderConfigurer: http://settings4j.sourceforge.net/currentrelease/configSpringPlaceholder.html

Dans notre application, nous utilisons:

  • Préférences pour configurer les systèmes PreProd et Prod
  • Préférences et variables d'environnement JNDI (JNDI écrase les préférences) pour "mvn jetty: run"
  • Propriétés système pour UnitTests (annotation @BeforeClass)

L'ordre par défaut, dont la valeur-clé-Source est vérifiée en premier, est décrit dans:
http://settings4j.sourceforge.net/currentrelease/configDefault.html
Il peut être personnalisé avec un settings4j.xml (précis pour log4j.xml) dans votre chemin de classe.

Faites-moi savoir votre opinion: [email protected]

Cordialement,
Harald

brabenetz
la source
-1

Utilisez la classe "PropertyPlaceholderConfigurer" de Spring

Un exemple simple montrant un fichier de propriétés lu dynamiquement comme la propriété du 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> 

Fichier de propriétés

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