Questa pagina non è ancora tradotta in italiano: quello che segue è il testo inglese.
Key prefix
Questi contenuti non sono ancora disponibili nella tua lingua.
Configuration keys are commonly grouped under a common prefix: every property
belonging to the server is called server.something, every property belonging
to the database is called db.something, and so on.
Mapping such a file with the @Key annotation alone means repeating that
prefix on every single method:
server.hostname=foobar.comserver.port=80server.max.threads=100public interface ServerConfig extends Config { @Key("server.hostname") String hostname();
@Key("server.port") int port();
@Key("server.max.threads") @DefaultValue("42") int maxThreads();}Since version 2.0.0, the @Prefix annotation lets you state the common part
once, on the interface:
@Prefix("server.")public interface ServerConfig extends Config { String hostname();
int port();
@Key("max.threads") @DefaultValue("42") int maxThreads();}The two interfaces above read exactly the same properties. The prefix is
prepended to the key of every property declared in the interface: to the key
derived from the method name, as for hostname() and port(), and to the one
given by @Key, as for maxThreads() — which is looked up as
server.max.threads.
The prefix is concatenated literally.
Nothing is inserted between the prefix and the key: @Prefix("server.") gives
server.hostname, while @Prefix("server") — without the trailing dot —
gives serverhostname. Ending the prefix with the separator you want to use is the
recommended way to write it, and the one all the examples in this chapter follow.
The concatenation stays literal on purpose, and OWNER neither adds a separator nor complains
about a missing one: a prefix is just the leading part of a key, so a naming scheme that does
not use a separator — @Prefix("db_"), or a prefix ending in the middle of a word —
is a legitimate use of the annotation rather than a mistake to be corrected.
The prefix is only about lookup: it changes the key a method resolves to, and
nothing else. A @DefaultValue declared on a prefixed method is registered
under the prefixed key, and the methods of
Accessible and Mutable keep
taking plain property names, so they too need the full key, prefix included:
@Prefix("server.")public interface ServerConfig extends Config, Accessible { @Key("max.threads") @DefaultValue("42") int maxThreads();}
ServerConfig cfg = ConfigFactory.create(ServerConfig.class);
cfg.maxThreads(); // 42cfg.getProperty("server.max.threads"); // "42"cfg.getProperty("max.threads"); // nullFor the same reason, a missing mandatory property is reported by its prefixed key, which is the one that could not be resolved.
Prefixes and interface inheritance
Section titled “Prefixes and interface inheritance”Every method takes the prefix of the interface where it is declared. A prefix therefore never leaks onto the methods a sub-interface inherits, and a method keeps the prefix of the interface that declares it however deep the hierarchy goes:
@Prefix("datasource.")public interface DataSourceConfig extends Config { String url(); // datasource.url}
@Prefix("pool.")public interface PoolConfig extends DataSourceConfig { int maxSize(); // pool.maxSize // url() is still datasource.url}
@Prefix("metrics.")public interface MonitoredPoolConfig extends PoolConfig { boolean enabled(); // metrics.enabled // maxSize() is still pool.maxSize // url() is still datasource.url}The rule holds in both directions, so mixing prefixed and unprefixed interfaces does what the rule says and nothing more.
An interface that declares no prefix of its own does not remove the prefix of what it inherits:
public interface PlainConfig extends DataSourceConfig { String name(); // name}name() resolves to name and url() still resolves to datasource.url.
Symmetrically, a prefixed interface does not push its prefix onto what it inherits from an unprefixed one — the methods of the super-interface keep their bare keys:
public interface PlainConfig extends Config { String name(); // name}
@Prefix("pool.")public interface PoolConfig extends PlainConfig { int maxSize(); // pool.maxSize // name() is still name}The same applies to the @DefaultValue of an inherited method: it is
registered under the key of the interface that declares it, so a default
declared in PlainConfig stays under name, not under pool.name.
This makes @Prefix a natural fit for composing a configuration out of
reusable pieces: each interface describes one section of the properties file
and carries the name of that section with it, so an interface that extends
several of them reads every property under the section it belongs to.
Overriding the prefix of an inherited method.
Since the prefix follows the declaration, re-declaring a method in the sub-interface moves it
under the prefix of the sub-interface. Writing String url(); again inside
PoolConfig makes it resolve to pool.url instead of
datasource.url.
When the keys are not the ones you expected — which a prefix makes happen to all of them at once, without an error anywhere — the library will tell you what it resolved: see which key is my method reading.
Composing a configuration out of reusable pieces has a second form, in which
the sections are objects of their own rather than interfaces to inherit from:
see Nested configuration. A @Prefix
declared on a nested interface composes with the path it hangs from,
where the one configured on a factory is overridden by it.
Variables in the prefix
Section titled “Variables in the prefix”The prefix is part of the key, so it goes through variables expansion like the rest of it. Given the multi-environment properties file used in that chapter:
servers.dev.name=Developmentservers.dev.hostname=devhostservers.dev.port=6000
servers.uat.name=User Acceptance Testservers.uat.hostname=uathostservers.uat.port=60020
servers.prod.name=Productionservers.prod.hostname=prod-hostservers.prod.port=600the interface that selects one environment at runtime can be written without
repeating servers.${env}. on every method:
@Prefix("servers.${env}.")public interface ServerConfig extends Config {
@DisableFeature(PREFIX) @DefaultValue("dev") String env();
String name();
String hostname();
Integer port();}Map<String, String> myVars = new HashMap<String, String>();myVars.put("env", "uat");
ServerConfig cfg = ConfigFactory.create(ServerConfig.class, myVars);
cfg.name(); // User Acceptance Testcfg.hostname(); // uathostcfg.port(); // 60020Notice the @DisableFeature(PREFIX) on env(): the variable that selects
the section is not itself part of the section, so it has to opt out of the
prefix — see the next paragraph.
The same mechanism makes a prefix optional, which is what you want when one deployment namespaces everything and another does not:
@Prefix("${env.prefix:}")public interface ServerConfig extends Config { String host(); int port();}With env.prefix undefined the keys are host and port; setting it to
FOO_ — as a system property, an environment variable, or an import — moves
every method of the interface onto FOO_host and FOO_port at once. The
empty default is what makes the prefix disappear when nothing is set.
If the only reason for that method is to give ${env} a fallback, a
default value in the prefix
itself says the same thing in one line, and the interface goes back to
describing nothing but the section:
@Prefix("servers.${env:dev}.")public interface ServerConfig extends Config {
String name();
String hostname();
Integer port();}The dev section is read when env is defined nowhere, and passing
env=uat at creation time selects the other one exactly as above.
Disabling the prefix
Section titled “Disabling the prefix”PREFIX is a disableable feature:
@DisableFeature(PREFIX) makes a method resolve to its bare key, ignoring the
prefix declared on the interface.
@Prefix("server.")public interface ServerConfig extends Config {
String hostname(); // server.hostname
@DisableFeature(PREFIX) @DefaultValue("UTF-8") String encoding(); // encoding}As with the other disableable features, the annotation can also be placed on the interface. It is read from the same interface the prefix is read from — the one declaring the method — so it switches the prefix off for the methods declared in that interface, and it does not reach the methods it inherits:
@Prefix("datasource.")public interface DataSourceConfig extends Config { String url(); // still datasource.url}
@DisableFeature(PREFIX)@Prefix("pool.")public interface PoolConfig extends DataSourceConfig { int maxSize(); // maxSize, the pool. prefix is off}A prefix for the whole factory
Section titled “A prefix for the whole factory”@Prefix states the prefix in the source code, one interface at a time. Since
version 2.0.0 a prefix can also be configured on the
factory, for the interfaces that do not
declare one of their own. It is set through the factory properties, which are
the place where the factory itself is configured, so there is no new method to
learn:
Factory factory = ConfigFactory.newInstance();factory.setProperty("owner.key.prefix.from.package", "true");
ServerConfig cfg = factory.create(ServerConfig.class);Two forms are available, and they compose — the literal one comes first:
| property | effect |
|---|---|
owner.key.prefix |
a literal, prepended to every key |
owner.key.prefix.from.package |
the package of the interface declaring the method, followed by a dot |
With owner.key.prefix.from.package set to true, an interface written like
this:
package com.example;
public interface ServerConfig extends Config { @DefaultValue("8080") int port();}reads:
com.example.port=80which is the point of the derived form: the prefix is not a string somebody
typed, so moving the interface to another package moves its keys with it,
instead of leaving a literal behind. Setting both properties nests one inside
the other: owner.key.prefix=myapp. together with the derived form gives
myapp.com.example.port.
This is less of a new idea than it looks. OWNER already derives the name of the default properties file from the package and name of the interface — it was only the keys that were left out of that convention.
The literal form belongs to an application, not to a library.
owner.key.prefix moves the keys of every interface created by that factory, including
the ones you did not write. The derived form does not have that problem: it puts each interface
under its own package, so a configuration interface shipped by a library stays consistent with
itself. If you are writing a library, prefer @Prefix or the derived form.
Which prefix wins
Section titled “Which prefix wins”An interface declaring @Prefix keeps it: the annotation is the explicit
statement of the two, and it is not appended to the one of the factory.
@DisableFeature(PREFIX) switches off both, so a method or an interface that
opts out resolves to its bare key whatever the factory says.
One factory, one mapping
Section titled “One factory, one mapping”The prefix belongs to the factory, not to the JVM, so two factories do not interfere:
Factory prefixed = ConfigFactory.newInstance();prefixed.setProperty("owner.key.prefix.from.package", "true");
Factory plain = ConfigFactory.newInstance();
prefixed.create(ServerConfig.class).port(); // reads com.example.portplain.create(ServerConfig.class).port(); // reads portThe static ConfigFactory is a factory like any other: setting the property
through ConfigFactory.setProperty() applies to what ConfigFactory.create()
builds from that moment on, and leaves the factories you created yourself
alone.
The prefix is read when the Config object is created, and the object keeps
it for the rest of its life. Reconfiguring the factory afterwards cannot rename
the keys of an object that already exists, a
reload resolves the same keys it resolved the
first time, and the mapping travels with the object when it is serialized. For
the same reason, an instance taken from
ConfigCache keeps the mapping it was born
with: the cache returns the object created the first time, prefix included.
What else it reaches
Section titled “What else it reaches”Being a prefix like the one of the annotation, it applies wherever a key is built:
- a parametrized key keeps it in front of the key it completes at call time;
- a
Mapreturn type reads its group of properties below the prefixed key; - a missing mandatory property is reported by its prefixed key, and so is a value that fails to convert;
- an interface in the default package has no package name to build a prefix out of, so the derived form leaves its keys alone rather than prefixing them with a bare dot.
The properties keep their names
Section titled “The properties keep their names”The prefix says where a method looks; it does not rename the properties. The
file, the imports and the
Accessible methods all use the full
key:
factory.create(ServerConfig.class, map("com.example.port", "80")); // foundfactory.create(ServerConfig.class, map("port", "80")); // not foundWhy isn't the prefix applied to the imports too?
Because the prefix belongs to the interface, while the properties are shared. The same imported
map, the same file and the same System properties are read by every interface, and
each of them can have a different prefix — so a rule that depends on who is reading can
only be applied where the reading happens, never to the store itself.
Applying it to the store would also break what a key is: getProperty("com.example.port")
would stop matching the property it just wrote with store(), and
${com.example.port} in a variable would point at something else again. This is not a
rule the factory prefix introduces: @Prefix("server.") has always needed
server.port in the file, and this is the same rule with the prefix stated elsewhere.