Issue Details (XML | Word | Printable)

Key: SJC-16
Type: New Feature New Feature
Status: Resolved Resolved
Resolution: Duplicate
Priority: Major Major
Assignee: Chris Beams
Reporter: Guillaume Duchesneau
Votes: 0
Watchers: 4
Operations

If you were logged in you would be able to see more operations.
Spring JavaConfig

PropertyPlaceholderConfigurer-like feature for java configurations

Created: 03/May/07 08:38 PM   Updated: 05/Sep/08 09:09 PM   Resolved: 13/Apr/08 02:09 PM
Component/s: Public API
Affects Version/s: 1.0 M1
Fix Version/s: None

Time Tracking:
Not Specified

Issue Links:
Depends
 
Related
 
Supersede
 


 Description  « Hide
As it is possible with PropertyPlaceholderConfigurer for xml configurations, it would be nice if there was some way to provide properties to java configurated beans.

A proposition has been made with issue SJC-2 but I am not sure if using annotated method arguments is the right way to do it. Maybe. Do you already have a strategy for this?

Anyways, I was thinking about it and maybe it could be done like this (see example below). Methods (or protected fields?) annotated with @Property could be implemented by the cglib-enhanced configuration class (like @AutoBean works) and return the property value. That would replace the property placeholders like ${some.property} in xml bean definitions. Then we would need something to tell Spring where our properties are defined. There could exist an annotation such as @PropertyConfigurer to place on @Bean methods to indicate that this bean provides properties to be injected for @Property annotated methods... or maybe there would be no need for the @Bean annotation... or maybe there is no need for any annotation at all: just detecting that the return type of the method implements some interface could be enough.

...just brainstorming... :-)

@Configuration
public class SomeConfig {

    @Bean
    @PropertyConfigurer
    public ResourcePropertyConfigurer myProperties()
    {
        return new ResourcePropertyProvider("classpath*:META-INF/*.properties");
    }
}

@Configuration
public class SomeOtherConfig {

    // type conversion would occur here to parse the property as an integer
    @Property(name = "some.property", default="1")
    public abstract int someIntegerProperty();

    @Bean
    public SomeBean someBean()
    {
        return new SomeBean(someIntegerProperty());
    }

}

zhouyanming added a comment - 05/Jun/07 11:54 PM
when use org.springframework.config.java.process.ConfigurationPostProcessor in xml configuration,you could inject 'propertyPlaceholderConfigurer' to config class

@Configuration(defaultAutowire = Autowire.BY_NAME)
public abstract class Config extends ConfigurationSupport {

@ExternalBean
public abstract PropertyPlaceholderConfigurer propertyPlaceholderConfigurer();

@Bean(destroyMethodName = "close")
public DataSource dataSource() {
BasicDataSource ds = new BasicDataSource();
ds.setUrl(propertyPlaceholderConfigurer().parseStringValue(
"${jdbc.url}"));
ds.setUsername(propertyPlaceholderConfigurer().parseStringValue(
"${jdbc.username}"));
ds.setPassword(propertyPlaceholderConfigurer().parseStringValue(
"${jdbc.password}"));
ds.setDriverClassName(propertyPlaceholderConfigurer().parseStringValue(
"${jdbc.driverClassName}"));
ds.setPoolPreparedStatements(true);
return ds;
}
}


<bean id="propertyPlaceholderConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"
p:location="classpath:applicationContext.properties" />
<bean class="Config" />
<bean class="org.springframework.config.java.process.ConfigurationPostProcessor" />

please notes: method PropertyPlaceholderConfigurer.parseStringValue(String str) hasn't presented yet,I subbmit a issue,please see http://opensource.atlassian.com/projects/spring/browse/SPR-3559

Sandu Turcan added a comment - 19/Oct/07 11:32 PM
How about using fields?

@Config
class MyConfig {
String host;
int port = 80;

@Bean
Socket serverSocket() {
   return new ServerSocket(host,port)
}
}

When instantiating the context you'd need to provide properties that would include host and port (port would be optional)
Perhaps @Required can play a role here as well but I'd avoid it if possible.

Rod Johnson added a comment - 03/Nov/07 08:03 PM
I've added some basic support for this as we work toward M3, using the abstract method style and sourcing values from resource bundles. Using fields is an elegant idea, but I like the fact that dynamic resolution (at runtime) can work naturally with methods.

Usage looks like this:

@Config
@ResourceBundles("classpath://com/foo/bar/basename")
abstract class MyConfig {

@Bean
Socket serverSocket() {
   return new ServerSocket(getHost());
}

@ExternalValue
protected abstract int getHost();
}

I will play around with various refinements and possibly using a concrete implementation as a default value.

Note: M3 is on the way and should be out this month.

Sandu Turcan added a comment - 05/Nov/07 09:44 AM
I see what you mean, methods are definitely more flexible, on the other hand fields make the code more concise.
Perhaps there is room for both?


Ben Rowlands added a comment - 08/Jan/08 05:33 AM
One nice feature of the proposal in SJC-2 is that properties are implicitly scoped to the bean they apply to. A config with 2 servers requiring both a host and port:

 @Configuration
 public class MyConfig
 {
   @Bean
   public Server server1( int host, int port ) {...}
   
   @Bean
   public Server server2( int host, int port ) {...}
 }

Has its properties cleanly scoped:

 server1.host=foo
 server1.port=123
 server2.host=bar
 server2.port=456

Using @ExternalValues defined at the class level forces us to explicitly scope the property name:

 @ExternalValue
 public abstract int getServer1Host();

 @ExternalValue
 public abstract int getServer2Host();
 
And set properties with names like 'server1Host'. The '.' separated properties feel more natural than cammalCase properties? @ExternalValue could take a name attribute but that adds noise.

SJC-2 style properties would work nicely with the autowiring proposal in SJC-50, providing a common mechanism for bean factory methods to declare they need some extra context to create a bean (either deployment properties or an autowired bean).

OTOH if a property is shared across beans then this becomes a bit clunky, we must either duplicate the value in 2 properties (yuck) or decorate both properties to have the same name using the @Name annotation. It feels more common to have 1 unique user of a property rather than using it in many places throughout the config so this might be a reasonable tradeoff. Perhaps properties lacking a bean qualifier are applied to all matching params? For example the property:

 locale=UK
 
Would be bound to *any* bean factory parameter with the name 'locale' unless an explicit value is provided for that bean?

One more tradeoff is how often a property needs to be defaulted, defaults look quite elegant using a non-abstract @ExternalValue method and feel cumbersome having to decorate each param with a @Default(...) annotation, not sure what could be done here to improve this (without native Java support for default paramaters!).

Adam Klein added a comment - 08/Feb/08 12:32 PM
Along the same lines, I hope there's an easy way to hook into Spring's registered Property Editors and reflection to inject strings that are converted to more complex objects ... for example:

@Configuration
@ResourceBundles("classpath://com/foo/bar/file_with_EnumValue_in_it")
public class Config extends ConfigurationSupport {

  @ExternalValue
   public abstract MyCustomEnum getEnumValue();

  @Bean
  public MyNewBean newBean() {
     MyNewBean myNewBean = new MyNewBean();
     myNewBean.setEnumValue( getEnumValue() );
     ....


Michal Grosos added a comment - 14/Feb/08 09:04 AM
Here's how I am doing it. I guess, it's better to leave this kind of configuration in the XML file, but I was just too eager to configure my app absolutelly without an XML file :)

private static String SETTINGS_PROPERTIES = "sk/seges/attis/config/dataSourceConfiguration.properties";
private static final Properties settingsProperties;
static {
InputStream is = null;
try {
is = ClassLoader.getSystemClassLoader().getResourceAsStream(SETTINGS_PROPERTIES);
settingsProperties = new Properties();
settingsProperties.load(is);
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
} finally {
if(is != null) {
try {
is.close();
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
}
}

@SuppressWarnings("unused")
@Bean
private DriverManagerDataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl(settingsProperties.getProperty("db.url"));
dataSource.setUsername(settingsProperties.getProperty("db.username"));
dataSource.setPassword(settingsProperties.getProperty("db.userpassword"));
dataSource.setDriverClassName(settingsProperties.getProperty("db.driver"));
return dataSource;
}

In other words, when you give up the idea of configuring the class's properties using annotations, you can do pretty much you want, like implement your own PropertyPlaceholderConfigurer, since you are in java. IMO, the PropertyPlaceholderConfigurer makes sense only in XML configurations.

Chris Beams added a comment - 10/Apr/08 01:40 PM
SJC-74 addresses property-placeholder-like functionality for JavaConfig

Chris Beams added a comment - 13/Apr/08 02:09 PM
As mentioned previously, SJC-74 covers this functionality. See comments there for details.

Chris Beams added a comment - 05/Sep/08 09:00 PM
For those interested in this issue, please take a look at the @PropertiesValueSource / @ExternalValue support coming out shortly in 1.0.0.m4. It's stable in the nightlies as well.

Chris Beams added a comment - 05/Sep/08 09:09 PM
Linking to SJC-166, the issue that introduced @PropertiesValueSource