Questa pagina non è ancora tradotta in italiano: quello che segue è il testo inglese.
Importing properties
Questi contenuti non sono ancora disponibili nella tua lingua.
You can use another mechanism to load your properties into a mapping interface.
And this mechanism is to specify a Properties object programmatically when
calling ConfigFactory.create():
public interface ImportConfig extends Config {
@DefaultValue("apple") String foo();
@DefaultValue("pear") String bar();
@DefaultValue("orange") String baz();
}
// then...
Properties props = new Properties();props.setProperty("foo", "pineapple");props.setProperty("bar", "lime");
ImportConfig cfg = ConfigFactory .create(ImportConfig.class, props); // props imported!
assertEquals("pineapple", cfg.foo());assertEquals("lime", cfg.bar());assertEquals("orange", cfg.baz());Imported keys and values must be non-null Strings.
A Properties or Map object accepts null key or
null values, but that is obviously an error,
so starting from version 1.0.10, an IllegalArgumentException is thrown.
Starting from version 2.0.0, the same applies to keys and values that are not
String. The exception message also contains further information about the
offending key, and its type when applicable.
The reason is that imports are merged into a java.util.Properties, and even though its
contract only admits String keys and values, it extends Hashtable<Object, Object>:
anything else is accepted by putAll and then becomes invisible to getProperty.
Before 2.0.0 such an entry was taken without complaint and then quietly misbehaved when read:
public interface MyConfig extends Config { @Key("some.key") @DefaultValue("1") Integer someValue();}
Map<String, Object> imports = new HashMap<>();imports.put("some.key", 42); // an Integer, not a String
MyConfig cfg = ConfigFactory.create(MyConfig.class, imports);
// before 2.0.0: null - the unusable entry also shadowed @DefaultValue// since 2.0.0: IllegalArgumentException at create() timecfg.someValue();A non-String key failed in a different way: the entry was simply ignored, and the
property silently fell back to its default, as if the import had never been passed.
Map<Object, String> imports = new HashMap<>();imports.put(42, "value"); // an Integer key
// before 2.0.0: the entry is dropped without any notice// since 2.0.0: IllegalArgumentException at create() timeConfigFactory.create(MyConfig.class, imports);Note that a CharSequence is not enough, since Properties compares against String
specifically: new StringBuilder("some.key") and new StringBuffer("42") are rejected too.
Convert them with toString() before importing.
Passing a String key with a String value keeps working exactly as before, so code
that was already correct is unaffected:
Map<String, String> imports = new HashMap<>();imports.put("some.key", "42");
assertEquals(Integer.valueOf(42), ConfigFactory.create(MyConfig.class, imports).someValue());The check runs for every entry point that builds a Config from imports, so
ConfigFactory.create(), a Factory instance obtained from ConfigFactory.newInstance()
and ConfigCache.getOrCreate() all behave the same way.
You can specify multiple properties to import on the same line:
ImportConfig cfg = ConfigFactory .create(ImportConfig.class, props1, props2, ...);If there are prop1 and prop2 defining two different values for the same property key, the one specified first will prevail:
Properties p1 = new Properties();p1.setProperty("foo", "pineapple");p1.setProperty("bar", "lime");
Properties p2 = new Properties();p2.setProperty("bar", "grapefruit");p2.setProperty("baz", "blackberry");
ImportConfig cfg = ConfigFactory .create(ImportConfig.class, p1, p2); // props imported!
assertEquals("pineapple", cfg.foo());
// p1 prevails, so this is lime and not grapefruitassertEquals("lime", cfg.bar());
assertEquals("blackberry", cfg.baz());This is pretty handy if you want to load properties provided by other mechanisms which not accessible through any of the supported URI schemes listed under Loading Strategies
For instance, a Java EE (a.k.a. Jakarta EE) servlet running on a servlet container might load properties during initialization from a resource accessible through its respective ServletContext:
interface ServletContextProperties extends Config { /** JDBC name of a data source used by the servlet */ @Key("ds.name") String dsName();
void list(PrintStream out);}
...
public class MyServlet extends HttpServlet {
protected void init() { ServletContextProperties cfg = ConfigFactory .create(ServletContextProperties.class, getServletConfig().getServletContext().getResourceAsStream("/WEB-INF/myServlet.properties")); }</div>}Thus, if you want to refer to properties provided by any of the mechanisms directly supported
by the @Source annotation, you should rather use them, as explained in Loading strategies.
In particular, to refer to system properties or environment variables,
you can use (since version 1.0.10) system:properties or system:env (respectively).
Other typical usage of importing properties might involve loading them from other sources directly
provided by the execution environment, e.g. servlet context attributes, context or servlet initialization parameters, Java preferences, or any other environment-dependent property sources. However, none of these sources direcly provide an API to access their contents as a Map object;
hence the programmer would need in that case to implement first their own method to convert from lists of names
plus individual values to Map object (therefore compatible with Owner API).
JNDI no longer needs any of that. Since
2.0.0 the owner-extras artifact reads java:comp/env/ as a source, so it goes in @Sources with
everything else and takes part in MERGE — see JNDI.
Interactions with loading strategies
Section titled “Interactions with loading strategies”Notice that the “importing properties” feature is additional to the properties loading mechanism explained in chapter Loading strategies.
Properties imported programmatically have higher priority regarding the
properties loaded from the @Sources attribute.
Imagine the scenario where the you define your configuration with @Sources
annotation, but you want to allow the user to specify a configuration file at
the command line.
@Sources(...)interface MyConfig extends Config { ...}
public static void main(String[] args) { MyConfig cfg; if (args.lenght() > 0) { Properties props = new Properties(); props.load(new FileInputStream(new File(args[0]))); cfg = ConfigFactory.create(MyConfig.class, userProps); } else { cfg = ConfigFactory.create(MyConfig.class); }}In the above example, the properties file specified by the user will override
the properties loaded by @Sources if there is overlapping between the
properties names.
This approach is used by many command line tools, that allow the user to specify
a configuration on the command line that overrides the default one.
This is true only with version 1.0.3.1 and superior!
Be aware that in versions prior to 1.0.3.1 imported properties have lower priority than others loaded properties. This behavior has been changed in version 1.0.3.1 and it will be kept this way for future releases.