Bagaimana saya bisa menyuntikkan nilai properti ke dalam Spring Bean yang dikonfigurasi menggunakan anotasi?

294

Saya memiliki banyak kacang Spring yang diambil dari classpath melalui penjelasan, misalnya

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

Di file Spring XML, didefinisikan PropertyPlaceholderConfigurer :

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

Saya ingin menyuntikkan salah satu properti dari app.properites ke dalam kacang yang ditunjukkan di atas. Saya tidak bisa begitu saja melakukan sesuatu

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

Karena PersonDaoImpl tidak memiliki fitur dalam file Spring XML (ini diambil dari classpath melalui anotasi). Saya punya sejauh berikut:

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

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

Tetapi tidak jelas bagi saya bagaimana saya mengakses properti yang saya minati ppc?

Dónal
sumber
1
Pada dasarnya saya telah mengajukan pertanyaan yang sama, meskipun dalam skenario yang sedikit berbeda: stackoverflow.com/questions/310271/… . Sejauh ini, belum ada yang bisa menjawabnya.
Spencer Kormos
Harap dicatat bahwa pada Musim Semi 3.1, PropertyPlaceholderConfigurertidak lagi kelas yang direkomendasikan. Lebih suka PropertySourcesPlaceholderConfigurersebagai gantinya. Bagaimanapun, Anda dapat menggunakan definisi XML yang lebih pendek <context:property-placeholder />.
Michael Piefel

Jawaban:

292

Anda dapat melakukan ini di Spring 3 menggunakan dukungan EL. Contoh:

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

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

systemProperties adalah objek implisit dan strategyBean merupakan nama kacang.

Satu lagi contoh, yang berfungsi saat Anda ingin mengambil properti dari Propertiesobjek. Itu juga menunjukkan bahwa Anda dapat menerapkan @Valueke bidang:

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

Berikut adalah posting blog yang saya tulis tentang ini untuk info lebih lanjut.

Selamat tinggal StackExchange
sumber
8
Apakah itu systemPropertiessederhana System.getProperties()? Saya kira jika saya ingin menyuntikkan properti saya sendiri ke dalam kacang Spring, saya perlu mendefinisikan nilai <bean id="appProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">kemudian membaca dari itu ke kacang lain menggunakan sesuatu seperti@Value("#{appProperties.databaseName}")
Dónal
11
Pastikan untuk mencatat dari jawaban max bahwa Anda juga dapat menggunakan placeholder dalam ekspresi $ {db.doStuff}, maka Anda tidak memerlukan PropertiesFactoryBean, hanya placeholderConfigurer
gtrak
9
Anda dapat menambahkan properti Anda sendiri menggunakan util: properties; misal, <util: properties id = "config" location = "classpath: /spring/environment.properties" />. Lihat jawaban yang diedit untuk cara mendapatkan nilai. (Saya menyadari ini mungkin sudah terlambat untuk membantu Don, tetapi yang lain diharapkan akan mendapat manfaat.)
2
Itu hanya bekerja untuk saya ketika saya menggunakan util: properti di file appname-servlet.xml saya. Menggunakan propertyConfigurer yang didefinisikan di applicationContext.xml saya (bukan Spring MVC) tidak berfungsi.
Asaf Mesika
Untuk sedikit bacaan lebih lanjut, yang menguraikan beberapa di antaranya, periksa pertanyaan SOF ini juga: stackoverflow.com/questions/6425795/…
arcseldon
143

Secara pribadi saya suka cara baru ini di Spring 3.0 dari dokumen :

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

Tidak ada getter atau setter!

Dengan properti yang dimuat melalui konfigurasi:

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

Untuk menambah kegembiraan saya, saya bahkan dapat mengontrol klik pada ekspresi EL di IntelliJ dan itu membawa saya ke definisi properti!

Ada juga versi yang sepenuhnya non xml :

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

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
barrymac
sumber
9
pastikan dan tambahkan namespace uri xmlns: p = " springframework.org/schema/p " untuk menggunakan p: atribut awalan.
shane lee
3
Mengapa metode ini bekerja dalam konteks pengujian tetapi tidak dalam konteks utama?
luksmir
9
huh, aku menghabiskan waktu berjam-jam mencoba membuat pendekatan hanya-anotasi bekerja dan menemukan apa yang hilang hanya setelah membaca jawaban ini- deklarasi kacang statis ajaib PropertySauceYadaYada. Cinta musim semi!
Kranach
@barrymac hei barry, apakah Anda tahu apa perbedaan antara @Value (# {...}) dan @Value ($ {...}). Terima kasih
Kim
1
Ini bekerja untuk saya. Hanya satu tip: diperlukan penjelasan @Component.
yaki_nuka
121

Ada anotasi baru @Valuedi Spring 3.0.0M3 . @Valuemendukung tidak hanya #{...}ekspresi tetapi ${...}placeholder juga

AdrieanKhisbe
sumber
20
+1 Jika contoh membantu, ini dia - @Value (value = "# {'$ {server.env}'}") atau cukup @Value ("# {'$ {server.env}'}")
Somu
31

<context:property-placeholder ... /> adalah XML yang setara dengan PropertyPlaceholderConfigurer.

Contoh: applicationContext.xml

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

Kelas komponen

 private @Value("${propertyName}") String propertyField;
shane lee
sumber
1
Bagi saya, ini hanya berfungsi jika autowiring diaktifkan melalui <context:component-scan base-package="com.company.package" />Untuk referensi, saya menggunakan pegas melalui ApplicationContext, bukan dalam konteks web.
Mustafa
15

Alternatif lain adalah menambahkan kacang appProperties yang ditunjukkan di bawah ini:

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

Ketika diambil, kacang ini dapat dilemparkan ke java.util.Propertiesyang akan berisi properti bernama results.maxyang nilainya dibacaapp.properties . Sekali lagi, kacang ini dapat disuntikkan ketergantungan (sebagai instance dari java.util.Properties) ke dalam kelas apa pun melalui anotasi @Resource.

Secara pribadi, saya lebih suka solusi ini (daripada yang saya usulkan), karena Anda dapat membatasi properti mana yang diekspos oleh appProperties, dan tidak perlu membaca app.properties dua kali.

Dónal
sumber
Bekerja untuk saya juga. Tetapi apakah tidak ada cara lain untuk mengakses properti dari PropertyPlaceholderConfigurer melalui anotasi @Value (saat menggunakan beberapa PropertyPlaceholderConfigurer di beberapa file XML kongif.)?
Tsar
9

Saya perlu memiliki dua file properti, satu untuk produksi dan override untuk pengembangan (yang tidak akan digunakan).

Untuk memiliki keduanya, Properties Bean yang dapat diotomatiskan dan PropertyConfigurer, Anda dapat menulis:

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

dan referensi Properties Bean di PropertyConfigurer

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

Sebelum kita mendapatkan Spring 3 - yang memungkinkan Anda untuk menyuntikkan konstanta properti langsung ke kacang Anda menggunakan anotasi - saya menulis sub-kelas kacang PropertyPlaceholderConfigurer yang melakukan hal yang sama. Jadi, Anda dapat menandai setter properti Anda dan Spring akan mengotomatiskan properti Anda ke dalam kacang Anda seperti:

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

Anotasi adalah sebagai berikut:

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

PropertyAnnotationAndPlaceholderConfigurer adalah sebagai berikut:

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

}

Jangan ragu untuk memodifikasi sesuai selera

Ricardo Gladwell
sumber
3
Harap dicatat, bahwa saya telah membuat proyek baru untuk yang di atas: code.google.com/p/spring-property-annotations
Ricardo Gladwell
7

Anda juga dapat membubuhi keterangan kelas Anda:

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

Dan memiliki variabel seperti ini:

@Autowired
private Environment env;

Sekarang Anda dapat mengakses semua properti Anda dengan cara ini:

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

Cara musim semi:
private @Value("${propertyName}") String propertyField;

adalah cara baru untuk menyuntikkan nilai menggunakan kelas "PropertyPlaceholderConfigurer" Spring. Cara lain adalah menelepon

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

Catatan: Untuk @Nilai, Anda tidak dapat menggunakan propertyField statis , itu harus non-statis saja, jika tidak mengembalikan null. Untuk memperbaikinya setter non-statis dibuat untuk bidang statis dan @Nilai diterapkan di atas setter itu.

Hai, India
sumber
7

Seperti disebutkan @Valuemelakukan pekerjaan dan cukup fleksibel karena Anda dapat memiliki pegas EL di dalamnya.

Berikut adalah beberapa contoh, yang dapat membantu:

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

Lain untuk mendapatkan setdarilist

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

Anda juga dapat mengatur nilai untuk tipe primitif.

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

Anda dapat memanggil metode statis:

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

Anda dapat memiliki logika

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

Solusi yang mungkin adalah dengan mendeklarasikan kacang kedua yang dibaca dari file properti yang sama:

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

Kacang bernama 'appProperties' adalah tipe java.util.Properties dan dapat disuntikkan ketergantungan menggunakan attruibute @Resource yang ditunjukkan di atas.

Dónal
sumber
4

Jika Anda terjebak menggunakan Spring 2.5, Anda bisa menentukan kacang untuk masing-masing properti Anda dan menyuntikkannya menggunakan kualifikasi. Seperti ini:

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

dan

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

Tidak super mudah dibaca tetapi menyelesaikan pekerjaan.

Nik
sumber
2

Nilai Properti Autowiring ke dalam Kacang Musim Semi:

Sebagian besar orang tahu bahwa Anda dapat menggunakan @Autowired untuk memberi tahu Spring untuk menyuntikkan satu objek ke objek lain saat memuat konteks aplikasi Anda. Nugget informasi yang kurang diketahui adalah Anda juga dapat menggunakan anotasi @Value untuk menyuntikkan nilai dari file properti ke atribut bean. lihat posting ini untuk info lebih lanjut ...

barang baru di Spring 3.0 || nilai kacang autowiring || nilai properti autowiring di musim semi

Beruntung
sumber
2

Bagi saya, itu adalah jawaban @ Lucky, dan khususnya, garis

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

dari halaman Kapten Debug

itu memperbaiki masalah saya. Saya memiliki aplikasi berbasis ApplicationContext yang berjalan dari command-line, dan menilai dari sejumlah komentar di SO, Spring memasang ini secara berbeda ke aplikasi berbasis MVC.

ben3000
sumber
1

Saya pikir ini cara paling mudah untuk menyuntikkan properti ke dalam kacang adalah metode setter.

Contoh:

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

Definisi kacang xml:

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

Untuk setiap propertymetode yang disebutkansetProperty(value) akan dipanggil.

Cara ini sangat membantu jika Anda membutuhkan lebih dari satu kacang berdasarkan satu implementasi.

Misalnya, jika kita mendefinisikan satu kacang lagi dalam xml:

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

Maka kode seperti ini:

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

Akan dicetak

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

Jadi, dalam kasus Anda seharusnya terlihat seperti ini:

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

Jika Anda membutuhkan fleksibilitas lebih untuk konfigurasi, coba Settings4jPlaceholderConfigurer: http://settings4j.sourceforge.net/currentrelease/configSpringPlaceholder.html

Dalam aplikasi kami, kami menggunakan:

  • Preferensi untuk mengkonfigurasi PreProd- dan Prod-System
  • Preferensi dan variabel Lingkungan JNDI (JNDI menimpa preferensi) untuk "mvn jetty: run"
  • System Properties untuk UnitTests (penjelasan @BeforeClass)

Urutan default yang sumber kunci-nilai diperiksa pertama kali, dijelaskan di:
http://settings4j.sourceforge.net/currentrelease/configDefault.html
Ia dapat dikustomisasi dengan settings4j.xml (akurat untuk log4j.xml) di jalan setapak kelas.

Beri tahu saya pendapat Anda: [email protected]

dengan hormat,
Harald

brabenetz
sumber
-1

Gunakan kelas "PropertyPlaceholderConfigurer" Spring

Contoh sederhana yang memperlihatkan file properti dibaca secara dinamis sebagai properti 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> 

File Properti

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
sumber