Going Bananas
Going Bananas

Reputation: 2537

Spring @Value property for custom class

Is it possible to use Spring's @Value annotation to read and write property values of a custom class type?

For example:

@Component
@PropertySource("classpath:/data.properties")
public class CustomerService {

    @Value("${data.isWaiting:#{false}}")
    private Boolean isWaiting;

    // is this possible for a custom class like Customer???
    // Something behind the scenes that converts Custom object to/from property file's string value via an ObjectFactory or something like that?
    @Value("${data.customer:#{null}}")
    private Customer customer;

    ...
}

EDITED SOLUTION

Here is how I did it using Spring 4.x APIs...

Created new PropertyEditorSupport class for Customer class:

public class CustomerPropertiesEditor extends PropertyEditorSupport {

    // simple mapping class to convert Customer to String and vice-versa.
    private CustomerMap map;

    @Override
    public String getAsText() 
    {
        Customer customer = (Customer) this.getValue();
        return map.transform(customer);
    }

    @Override
    public void setAsText(String text) throws IllegalArgumentException 
    {
        Customer customer = map.transform(text);
        super.setValue(customer);
    }
}

Then in application's ApplicationConfig class:

@Bean
public CustomEditorConfigurer customEditorConfigurer() {

    Map<Class<?>, Class<? extends PropertyEditor>> customEditors = 
            new HashMap<Class<?>, Class<? extends PropertyEditor>>(1);
    customEditors.put(Customer.class, CustomerPropertiesEditor.class);

    CustomEditorConfigurer configurer = new CustomEditorConfigurer();
    configurer.setCustomEditors(customEditors);

    return configurer;
}

Cheers, PM

Upvotes: 16

Views: 14897

Answers (5)

d_ser
d_ser

Reputation: 69

If you want to use an existing converter/constructor, you can just call it within your expression.

For example:

@Value("#{T(org.test.CustomerMap).transform('${serialized.customer}')}")
private Customer customer;

Upvotes: 1

Kike
Kike

Reputation: 1

If you want to use it with lists, there is a workaround using array instead.

Define your property as Customer[] instead of List then:

in ApplicationConfig class:

@Bean
public CustomEditorConfigurer customEditorConfigurer() {

    Map<Class<?>, Class<? extends PropertyEditor>> customEditors = 
            new HashMap<Class<?>, Class<? extends PropertyEditor>>(1);
    customEditors.put(Customer.class, CustomerPropertiesEditor.class);
    customEditors.put(Customer[].class, CustomerPropertiesEditor.class);

    CustomEditorConfigurer configurer = new CustomEditorConfigurer();
    configurer.setCustomEditors(customEditors);

    return configurer;
}

In CustomerEditor:

public class CustomerEditor extends PropertyEditorSupport {
  public static final String DEFAULT_SEPARATOR = ",";

  @Override
  public void setAsText(String text) {
    String[] array = StringUtils.delimitedListToStringArray(text, this.separator);
    if (this.emptyArrayAsNull && array.length == 0) {
        super.setValue((Object) null);
    } else {
        if (this.trimValues) {
            array = StringUtils.trimArrayElements(array);
        }
        
        // Convert String[] to Customer[]   
        super.setValue(...);
    }
  }
}

Upvotes: 0

X X
X X

Reputation: 81

Spring can read properties and load them directly into a class.
Moreover, you can add @ConfigurationProperties(prefix = "data") on top of the class, instead of wiring each nested property one by one, by making the code cleaner.

Given all that, here is the final example with explanations:

// File: CustomerConfig.java
@Configuration
// Set property source file path (optional)
@PropertySource("classpath:/data.properties")
// Put prefix = "data" here so that Spring read properties under "data.*"
@ConfigurationProperties(prefix = "data") 
public class CustomerConfig {
    // Note: Property name here is the same as in the file (data.customer)
    // Spring will automatically read and put "data.customer.*" properties into this object
    private Customer customer; 

    // Other configs can be added here too... without wiring one-by-one

    public setCustomer(Customer customer){
        this.customer = customer;
    }

    public getCustomer(){
        return this.customer;
    }
}

That's it, now you have "data.customer.*" properties, loaded and accessible via CustomerConfig.getCustomer().


To integrate it into your service (based on your example code):

// File: CustomerService.java
@Component
@PropertySource("classpath:/data.properties")
public class CustomerService {

    @Value("${data.isWaiting:#{false}}")
    private Boolean isWaiting;

    @Autowired // Inject configs, either with @Autowired or using constructor injection
    private CustomerConfig customerConfig;

    public void myMethod() {
        // Now its available for use
        System.out.println(customerConfig.getCustomer().toString());
    }
}

This way no "magical hack" is required to read configs into a class. Take a look at the @ConfigurationProperties documentation/examples, and this post for more useful info.


Note: I'd suggest against using PropertyEditorSupport, since
a) it was built for different purpose, may change in future by breaking the code
b) it requires manual "handling" code inside => possible bugs
Instead, use what was built right for that purpose (Spring already has it), in order to both make the code easier to understand, and to gain possible inner improvements/optimizations which might be done in the future (or present).

Further improvements: Your CustomerService seems to be cluttered with configs (@PropertyService) too. I'd suggest reading those properties via another class too (similarly) then wiring that class here, instead of doing all in the CustomerService.

Upvotes: 0

Efe Kahraman
Efe Kahraman

Reputation: 1438

You have to create a class extending PropertyEditorSupport.

public class CustomerEditor extends PropertyEditorSupport {
  @Override
  public void setAsText(String text) {
    Customer c = new Customer();
    // Parse text and set customer fields...
    setValue(c);
  }
}

Upvotes: 8

Pracede
Pracede

Reputation: 4361

It's possible but reading Spring documentation. You could see this example: Example usage

 @Configuration
 @PropertySource("classpath:/com/myco/app.properties")
 public class AppConfig {
     @Autowired
     Environment env;

     @Bean
     public TestBean testBean() {
         TestBean testBean = new TestBean();
         testBean.setName(env.getProperty("testbean.name"));
         return testBean;
     }
 }

See details here

Upvotes: 1

Related Questions