Three new article modules: configuration binding, profiles and config data, Spring AOP
configuration-properties/ @ConfigurationProperties vs @Value on Spring Boot 4.1.1. The relaxed-binding matrix is generated by binding each spelling rather than transcribed, and re-checked against real processes -- the in-process probe was wrong twice before it was right. Records the three findings that came out of it: @Value does get relaxed resolution inside Spring Boot (Boot attaches ConfigurationPropertySources), the configuration processor silently stops generating metadata on JDK 23+ when declared as a plain dependency, and @Valid is not what makes nested constraints run. profiles-and-config/ Precedence, profiles, spring.config.import and config trees. /precedence reports every source holding a property in rank order with file and line, which turns "my profile file had no effect" into a two-line answer. Also pins the counterintuitive one: an imported file outranks the file that imported it. spring-aop/ Designators, proxy types, and aspects that do not fire. One advice per supported designator so the reference table is generated from real matches; all fourteen unsupported designators fed to the parser. Two corrections to the reference documentation: unsupported designators throw UnsupportedPointcutPrimitiveException (extends RuntimeException, not IllegalArgumentException), and spring-boot-starter-aop was renamed to spring-boot-starter-aspectj in Boot 4. 19 contract tests across the three modules, 15 captured transcripts, all regenerated by scripts/run-all.sh. Verified on Spring Boot 4.1.1, Spring Framework 7.0.9, JDK 25.0.4.1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gip4srpzMwjgoba6uEfbr5
This commit is contained in:
56
profiles-and-config/docs/01-the-precedence-list.md
Normal file
56
profiles-and-config/docs/01-the-precedence-list.md
Normal file
@@ -0,0 +1,56 @@
|
||||
[Index](../README.md) · [Profiles →](02-profiles.md)
|
||||
|
||||
# 1. The precedence list
|
||||
|
||||
Spring Boot's documented order, lowest precedence first. Later entries win.
|
||||
|
||||
| # | Source |
|
||||
|---|---|
|
||||
| 1 | Default properties (`SpringApplication.setDefaultProperties`) |
|
||||
| 2 | `@PropertySource` on `@Configuration` classes |
|
||||
| 3 | **Config data** — `application.properties`, `application.yaml`, profile-specific files, `spring.config.import` |
|
||||
| 4 | `RandomValuePropertySource` (`random.*`) |
|
||||
| 5 | **OS environment variables** |
|
||||
| 6 | Java system properties (`-D`) |
|
||||
| 7 | JNDI attributes from `java:comp/env` |
|
||||
| 8 | `ServletContext` init parameters |
|
||||
| 9 | `ServletConfig` init parameters |
|
||||
| 10 | `SPRING_APPLICATION_JSON` |
|
||||
| 11 | Command-line arguments |
|
||||
| 12 | `properties` on `@SpringBootTest` |
|
||||
| 13 | `@DynamicPropertySource` |
|
||||
| 14 | `@TestPropertySource` |
|
||||
| 15 | Devtools global settings |
|
||||
|
||||
## The two rows that matter
|
||||
|
||||
**Item 3 covers every file you write.** `application.yaml`, `application-prod.yaml`, an
|
||||
imported config tree, a mounted ConfigMap — all of it is config data, all of it at rank 3.
|
||||
|
||||
**Item 5 is above it.** Every environment variable outranks every file.
|
||||
|
||||
Profile-specific files beat non-profile files, and later imports beat earlier ones, but those
|
||||
are orderings *within* item 3. Nothing inside item 3 can reach item 5.
|
||||
|
||||
That single fact explains the bug this project exists for, and
|
||||
[chapter 4](04-why-your-profile-file-lost.md) walks through it with a transcript.
|
||||
|
||||
## Seeing it for real
|
||||
|
||||
`/sources` prints the live stack, which is more useful than the table because it shows exactly
|
||||
which files were loaded:
|
||||
|
||||
```
|
||||
3. SimpleCommandLinePropertySource commandLineArgs
|
||||
6. PropertiesPropertySource systemProperties
|
||||
7. OriginAwareSystemEnvironmentPropertySource systemEnvironment
|
||||
9. OriginTrackedMapPropertySource application-prod-metrics.yaml
|
||||
10. OriginTrackedMapPropertySource application-prod-db.yaml
|
||||
11. OriginTrackedMapPropertySource application-prod.yaml
|
||||
12. OriginTrackedMapPropertySource application.yaml
|
||||
```
|
||||
|
||||
Note rank 2 in the real stack, which the table does not mention:
|
||||
`ConfigurationPropertySourcesPropertySource`, named `configurationProperties`. That is the
|
||||
source Spring Boot attaches to give `${...}` placeholders the binder's relaxed name matching —
|
||||
see the [binding project's chapter 2](../../configuration-properties/docs/02-relaxed-binding.md).
|
||||
70
profiles-and-config/docs/02-profiles.md
Normal file
70
profiles-and-config/docs/02-profiles.md
Normal file
@@ -0,0 +1,70 @@
|
||||
[← Precedence list](01-the-precedence-list.md) · [Index](../README.md) · [Seeing precedence →](03-seeing-precedence.md)
|
||||
|
||||
# 2. Profiles: files, documents and groups
|
||||
|
||||
## Profile-specific files
|
||||
|
||||
`application-<profile>.yaml`, loaded from the same locations as `application.yaml`, and always
|
||||
overriding it. With several profiles active, last one wins:
|
||||
`--spring.profiles.active=prod,live` means `application-live.yaml` beats
|
||||
`application-prod.yaml`.
|
||||
|
||||
## Multi-document files
|
||||
|
||||
The same effect without multiplying files. Documents are separated by `---` and activated by
|
||||
condition:
|
||||
|
||||
```yaml
|
||||
demo:
|
||||
greeting: from-multidoc-default-document
|
||||
---
|
||||
spring:
|
||||
config:
|
||||
activate:
|
||||
on-profile: staging
|
||||
demo:
|
||||
greeting: from-multidoc-staging-document
|
||||
```
|
||||
|
||||
Later documents win over earlier ones, so an unconditional first document acts as the default
|
||||
and each conditional document overrides it. Measured in
|
||||
[`04-import-and-multidoc.txt`](output/04-import-and-multidoc.txt).
|
||||
|
||||
`spring.config.activate.on-cloud-platform` and `spring.config.activate.on-profile` can be
|
||||
combined; both must match.
|
||||
|
||||
## Profile groups
|
||||
|
||||
One profile that activates several:
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
profiles:
|
||||
group:
|
||||
prod: prod-db,prod-metrics
|
||||
```
|
||||
|
||||
`--spring.profiles.active=prod` reports all three as active, and all three
|
||||
`application-<name>.yaml` files are loaded. Groups are resolved before config data is
|
||||
processed, which is why declaring a group in `application.yaml` can still affect which files
|
||||
get loaded.
|
||||
|
||||
## The activation Spring Boot refuses
|
||||
|
||||
`spring.profiles.active` cannot be set from a document that is itself profile-specific:
|
||||
|
||||
```
|
||||
InvalidConfigDataPropertyException: Property 'spring.profiles.active' imported from location
|
||||
'class path resource [application-badactivation.yaml]' is invalid in a profile specific
|
||||
resource [origin: ... - 12:13]
|
||||
```
|
||||
|
||||
A profile that activates itself would change which files are loaded after the set of files had
|
||||
already been decided. Boot refuses rather than half-applying it. `spring.profiles.include` has
|
||||
the same restriction; `spring.config.activate.on-profile` is how you express the condition.
|
||||
|
||||
## `@Profile` is a different mechanism
|
||||
|
||||
`@Profile("prod")` on a bean is evaluated when the context is built, long after config data is
|
||||
resolved. It decides which *beans* exist, not which *properties* are set. The two use the same
|
||||
profile names and nothing else.
|
||||
59
profiles-and-config/docs/03-seeing-precedence.md
Normal file
59
profiles-and-config/docs/03-seeing-precedence.md
Normal file
@@ -0,0 +1,59 @@
|
||||
[← Profiles](02-profiles.md) · [Index](../README.md) · [Why your profile file lost →](04-why-your-profile-file-lost.md)
|
||||
|
||||
# 3. Seeing precedence instead of reasoning about it
|
||||
|
||||
Endpoint: [`PrecedenceEndpoint`](../src/main/java/com/ankurm/profiles/web/PrecedenceEndpoint.java).
|
||||
Transcript: [`01-precedence.txt`](output/01-precedence.txt).
|
||||
|
||||
Set `demo.greeting` from five places at once and ask which won:
|
||||
|
||||
```
|
||||
$ DEMO_GREETING=from-environment-variable \
|
||||
java -Ddemo.greeting=from-system-property \
|
||||
-jar profiles-and-config-1.0.0.jar --spring.profiles.active=prod \
|
||||
--demo.greeting=from-command-line-argument
|
||||
```
|
||||
|
||||
```
|
||||
"effectiveValue": "from-command-line-argument",
|
||||
"activeProfiles": ["prod", "prod-db", "prod-metrics"],
|
||||
"holders": [
|
||||
{ "rank": 1, "value": "from-command-line-argument", "source": "commandLineArgs" },
|
||||
{ "rank": 2, "value": "from-system-property", "source": "systemProperties" },
|
||||
{ "rank": 3, "value": "from-environment-variable", "source": "systemEnvironment" },
|
||||
{ "rank": 4, "value": "from-application-prod-yaml", "origin": "application-prod.yaml - 4:13" },
|
||||
{ "rank": 5, "value": "from-application-yaml", "origin": "application.yaml - 21:13" }
|
||||
],
|
||||
"shadowedCount": 4
|
||||
```
|
||||
|
||||
Five sources hold the property. Four of them lose. Each one that came from a file names its
|
||||
line.
|
||||
|
||||
## The whole implementation
|
||||
|
||||
```java
|
||||
for (ConfigurationPropertySource source : ConfigurationPropertySources.get(environment)) {
|
||||
ConfigurationProperty property =
|
||||
source.getConfigurationProperty(ConfigurationPropertyName.of(name));
|
||||
if (property != null) {
|
||||
// rank = position, property.getValue(), property.getOrigin()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`ConfigurationPropertySources.get(...)` returns the sources in precedence order. Iterate,
|
||||
collect every hit, and the first is the winner. That is the entire diagnostic.
|
||||
|
||||
## Why this beats reading the list
|
||||
|
||||
The documented order is correct but abstract. It does not tell you that a `DEMO_GREETING` left
|
||||
over from a shell three weeks ago is sitting at rank 3, and that is the actual question.
|
||||
|
||||
## Alternatives if you would rather not add an endpoint
|
||||
|
||||
- Actuator's `/actuator/env` gives the same information with sanitisation, and
|
||||
`/actuator/env/{name}` narrows to one property. Prefer it in anything real.
|
||||
- `logging.level.org.springframework.boot.context.config=TRACE` logs which config data
|
||||
resources were loaded and in what order.
|
||||
- `--debug` does *not* show this. It prints the auto-configuration report.
|
||||
73
profiles-and-config/docs/04-why-your-profile-file-lost.md
Normal file
73
profiles-and-config/docs/04-why-your-profile-file-lost.md
Normal file
@@ -0,0 +1,73 @@
|
||||
[← Seeing precedence](03-seeing-precedence.md) · [Index](../README.md) · [Config import →](05-config-import.md)
|
||||
|
||||
# 4. Why your profile-specific file lost
|
||||
|
||||
Transcript: [`02-profile-file-loses.txt`](output/02-profile-file-loses.txt).
|
||||
|
||||
The bug: you set a value in `application-prod.yaml`, deploy with `prod` active, and the old
|
||||
value is still in effect.
|
||||
|
||||
## Two runs, one difference
|
||||
|
||||
```
|
||||
--- 1. prod profile active, no environment variable ---
|
||||
effective value : jdbc:postgresql://prod-db:5432/orders
|
||||
1. jdbc:postgresql://prod-db:5432/orders <- application-prod.yaml
|
||||
2. jdbc:h2:mem:default <- application.yaml
|
||||
```
|
||||
|
||||
Working as intended. Now with one leftover variable in the environment:
|
||||
|
||||
```
|
||||
--- 2. identical, plus one leftover environment variable ---
|
||||
effective value : jdbc:postgresql://leftover:5432/orders
|
||||
1. jdbc:postgresql://leftover:5432/orders <- systemEnvironment
|
||||
2. jdbc:postgresql://prod-db:5432/orders <- application-prod.yaml
|
||||
3. jdbc:h2:mem:default <- application.yaml
|
||||
```
|
||||
|
||||
The profile file was still loaded. It still holds the right value. It is at rank 2.
|
||||
|
||||
## Why it feels wrong
|
||||
|
||||
Profile-specific files *do* override — the mental model is not wrong, it is incomplete. They
|
||||
override other config data. Config data as a whole sits at item 3 in the precedence list and
|
||||
environment variables at item 5, so the strongest file loses to the weakest variable.
|
||||
|
||||
## Where the leftover variables come from
|
||||
|
||||
Every one of these is real:
|
||||
|
||||
- A Kubernetes `Deployment` with an `env:` block that predates the ConfigMap and was never
|
||||
removed. `envFrom` a `ConfigMap` produces environment variables, not config data.
|
||||
- A `docker-compose.yml` `environment:` entry copied from a colleague.
|
||||
- Spring Cloud Kubernetes or a service mesh injecting `SPRING_DATASOURCE_URL`.
|
||||
- A CI runner exporting variables for a different service.
|
||||
- `SPRING_APPLICATION_JSON`, which is item 10 and beats almost everything.
|
||||
|
||||
## Diagnosing it in one step
|
||||
|
||||
If a property is not what the file says, look for a variable:
|
||||
|
||||
```bash
|
||||
kubectl exec deploy/my-app -- env | grep -i datasource
|
||||
```
|
||||
|
||||
or ask the running application, which reports every holder including the one you did not know
|
||||
about.
|
||||
|
||||
## Living with it
|
||||
|
||||
**Prefer environment variables in containers, files for defaults.** The precedence order is
|
||||
designed for exactly this: the image carries defaults, the deployment overrides them. Fighting
|
||||
it means fighting the design.
|
||||
|
||||
**Do not set the same key in both places.** If a value is per-environment, keep it out of the
|
||||
profile files entirely so there is only ever one source.
|
||||
|
||||
**Name environment variables specifically.** `DEMO_DATASOURCE_URL` collides with nothing;
|
||||
`SPRING_DATASOURCE_URL` collides with every Spring application on the host.
|
||||
|
||||
**Mount configuration as a config tree instead.** Still config data, still below environment
|
||||
variables, but at least it is one mechanism rather than two —
|
||||
[chapter 6](06-config-trees-and-configmaps.md).
|
||||
58
profiles-and-config/docs/05-config-import.md
Normal file
58
profiles-and-config/docs/05-config-import.md
Normal file
@@ -0,0 +1,58 @@
|
||||
[← Why your profile file lost](04-why-your-profile-file-lost.md) · [Index](../README.md) · [Config trees →](06-config-trees-and-configmaps.md)
|
||||
|
||||
# 5. `spring.config.import`
|
||||
|
||||
Transcript: [`04-import-and-multidoc.txt`](output/04-import-and-multidoc.txt).
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
config:
|
||||
import: "optional:classpath:/imported.yaml"
|
||||
```
|
||||
|
||||
## The imported file wins
|
||||
|
||||
This is the part that catches people, and it catches them in the direction opposite to the one
|
||||
they brace for:
|
||||
|
||||
```
|
||||
effective value : from-imported-yaml
|
||||
1. from-imported-yaml <- imported.yaml
|
||||
2. from-application-import-yaml <- application-import.yaml (declared the import)
|
||||
3. from-application-yaml <- application.yaml
|
||||
```
|
||||
|
||||
The importing file declared the import and then lost to it. An imported document is processed
|
||||
*after* the document that declared it, and later documents win.
|
||||
|
||||
`#include` semantics would give the opposite. So would treating the import as a set of
|
||||
defaults, which is what people usually intend when they import a shared baseline. If you import
|
||||
a company-wide `common.yaml` expecting your own file to override it, every key `common.yaml`
|
||||
sets will quietly beat yours.
|
||||
|
||||
To get defaults-style behaviour, put your overrides somewhere that outranks config data — an
|
||||
environment variable or a command-line argument — or import from a *later* document in your own
|
||||
file so the ordering is explicit.
|
||||
|
||||
## Prefixes
|
||||
|
||||
| Prefix | Meaning |
|
||||
|---|---|
|
||||
| `optional:` | do not fail if it is missing |
|
||||
| `file:` | a filesystem path |
|
||||
| `classpath:` | a classpath resource |
|
||||
| `configtree:` | a directory of value-per-file entries |
|
||||
|
||||
They compose: `optional:configtree:/etc/config/`.
|
||||
|
||||
Without `optional:`, a missing location is `ConfigDataLocationNotFoundException` at startup.
|
||||
That is usually what you want for a secret mount and never what you want for a developer
|
||||
machine.
|
||||
|
||||
## Where imports are legal
|
||||
|
||||
`spring.config.import` is only honoured in config data — `application.yaml` and friends. Setting
|
||||
it as an environment variable or a command-line argument works too, because those are processed
|
||||
before config data is loaded. Setting it anywhere else does nothing.
|
||||
|
||||
Imports are processed depth-first, and a cycle is detected and reported rather than looping.
|
||||
85
profiles-and-config/docs/06-config-trees-and-configmaps.md
Normal file
85
profiles-and-config/docs/06-config-trees-and-configmaps.md
Normal file
@@ -0,0 +1,85 @@
|
||||
[← Config import](05-config-import.md) · [Index](../README.md)
|
||||
|
||||
# 6. Config trees and Kubernetes ConfigMaps
|
||||
|
||||
Transcript: [`03-config-tree.txt`](output/03-config-tree.txt).
|
||||
|
||||
## What Kubernetes actually mounts
|
||||
|
||||
A ConfigMap mounted as a volume is not a properties file. Kubernetes writes **one file per
|
||||
key**, named after the key, containing only the value with no trailing newline:
|
||||
|
||||
```
|
||||
<configmap-mount>/demo.datasource-url
|
||||
<configmap-mount>/demo.greeting
|
||||
<configmap-mount>/demo.pool-size
|
||||
<configmap-mount>/demo/nested/value
|
||||
<secret-mount>/demo.api-key
|
||||
```
|
||||
|
||||
```
|
||||
$ cat <configmap-mount>/demo.greeting
|
||||
from-configmap-volume
|
||||
```
|
||||
|
||||
There is no syntax to parse. The filename is the key.
|
||||
|
||||
## Reading it
|
||||
|
||||
```
|
||||
--spring.config.import=configtree:/etc/config/,configtree:/etc/secrets/
|
||||
```
|
||||
|
||||
A trailing `/` is required — the location is a directory. Values arrive as properties:
|
||||
|
||||
```
|
||||
demo.pool-size = 25
|
||||
demo.nested.value = from-nested-directory
|
||||
demo.api-key = sk_live_not_a_real_key
|
||||
```
|
||||
|
||||
A directory under the mount becomes a nested property, so `demo/nested/value` is
|
||||
`demo.nested.value`. That is how a ConfigMap whose keys contain slashes arrives.
|
||||
|
||||
Secrets mount identically. The only difference is file permissions, which is why the same
|
||||
mechanism reads both and why nothing in your application needs to know which it got.
|
||||
|
||||
## Why this beats mounting a properties file
|
||||
|
||||
- **Per-key updates.** Changing one key rewrites one file. Kubernetes propagates it to the
|
||||
volume without a restart, and `spring.config.import` supports `configtree` reloading through
|
||||
Spring Cloud Kubernetes if you want to act on it.
|
||||
- **No parse step**, so no chance of one malformed line taking out the whole file.
|
||||
- **Secrets and config read the same way.**
|
||||
- **Values can contain anything.** No escaping, no quoting, no YAML surprises — a value of
|
||||
`yes` stays the string `yes`.
|
||||
|
||||
## Wildcards
|
||||
|
||||
```
|
||||
--spring.config.import=optional:configtree:/etc/config/*/
|
||||
```
|
||||
|
||||
Reads every immediate subdirectory, which is the shape you get when several ConfigMaps are
|
||||
mounted under one parent. Useful for "one ConfigMap per component" layouts.
|
||||
|
||||
## It is still config data
|
||||
|
||||
An imported config tree outranks `application.yaml` — and still loses to an environment
|
||||
variable:
|
||||
|
||||
```
|
||||
effective value : from-environment-variable
|
||||
1. from-environment-variable <- systemEnvironment
|
||||
2. from-configmap-volume <- ConfigTreePropertySource
|
||||
3. from-application-yaml <- application.yaml
|
||||
```
|
||||
|
||||
If you mount a ConfigMap *and* set `envFrom` on the same Deployment — which is a common way to
|
||||
migrate from one to the other — the environment variables win and the ConfigMap looks broken.
|
||||
|
||||
## There is no profile-specific config tree
|
||||
|
||||
No `<mount>-prod` convention exists. Per-environment configuration is a different ConfigMap
|
||||
chosen by the Deployment, not by `spring.profiles.active`. This is a feature: the environment
|
||||
is decided by what you deploy, not by a string inside the image.
|
||||
83
profiles-and-config/docs/output/01-precedence.txt
Normal file
83
profiles-and-config/docs/output/01-precedence.txt
Normal file
@@ -0,0 +1,83 @@
|
||||
== every source sets demo.greeting at once ==
|
||||
|
||||
$ DEMO_GREETING=from-environment-variable \
|
||||
java -Ddemo.greeting=from-system-property \
|
||||
-jar target/profiles-and-config-1.0.0.jar --spring.profiles.active=prod \
|
||||
--demo.greeting=from-command-line-argument
|
||||
|
||||
{
|
||||
"property": "demo.greeting",
|
||||
"effectiveValue": "from-command-line-argument",
|
||||
"activeProfiles": [
|
||||
"prod",
|
||||
"prod-db",
|
||||
"prod-metrics"
|
||||
],
|
||||
"holders": [
|
||||
{
|
||||
"rank": 1,
|
||||
"source": "SimpleCommandLinePropertySource {name='commandLineArgs'}",
|
||||
"value": "from-command-line-argument",
|
||||
"origin": "\"demo.greeting\" from property source \"commandLineArgs\""
|
||||
},
|
||||
{
|
||||
"rank": 2,
|
||||
"source": "PropertiesPropertySource {name='systemProperties'}",
|
||||
"value": "from-system-property",
|
||||
"origin": "\"demo.greeting\" from property source \"systemProperties\""
|
||||
},
|
||||
{
|
||||
"rank": 3,
|
||||
"source": "OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}",
|
||||
"value": "from-environment-variable",
|
||||
"origin": "System Environment Property \"DEMO_GREETING\""
|
||||
},
|
||||
{
|
||||
"rank": 4,
|
||||
"source": "OriginTrackedMapPropertySource {name='Config resource 'class path resource [application-prod.yaml]' via location 'optional:classpath:/''}",
|
||||
"value": "from-application-prod-yaml",
|
||||
"origin": "class path resource [application-prod.yaml] from profiles-and-config-1.0.0.jar - 4:13"
|
||||
},
|
||||
{
|
||||
"rank": 5,
|
||||
"source": "OriginTrackedMapPropertySource {name='Config resource 'class path resource [application.yaml]' via location 'optional:classpath:/''}",
|
||||
"value": "from-application-yaml",
|
||||
"origin": "class path resource [application.yaml] from profiles-and-config-1.0.0.jar - 21:13"
|
||||
}
|
||||
],
|
||||
"shadowedCount": 4
|
||||
}
|
||||
|
||||
== and with the environment variable removed, nothing else changed ==
|
||||
./scripts/demo-precedence.sh: line 41: 5735 Killed DEMO_GREETING=from-environment-variable setsid nohup java -Ddemo.greeting=from-system-property -jar "$JAR" --spring.profiles.active=prod --demo.greeting=from-command-line-argument > /tmp/profiles-precedence.log 2>&1 < /dev/null
|
||||
{
|
||||
"property": "demo.greeting",
|
||||
"effectiveValue": "from-system-property",
|
||||
"activeProfiles": [
|
||||
"prod",
|
||||
"prod-db",
|
||||
"prod-metrics"
|
||||
],
|
||||
"holders": [
|
||||
{
|
||||
"rank": 1,
|
||||
"source": "PropertiesPropertySource {name='systemProperties'}",
|
||||
"value": "from-system-property",
|
||||
"origin": "\"demo.greeting\" from property source \"systemProperties\""
|
||||
},
|
||||
{
|
||||
"rank": 2,
|
||||
"source": "OriginTrackedMapPropertySource {name='Config resource 'class path resource [application-prod.yaml]' via location 'optional:classpath:/''}",
|
||||
"value": "from-application-prod-yaml",
|
||||
"origin": "class path resource [application-prod.yaml] from profiles-and-config-1.0.0.jar - 4:13"
|
||||
},
|
||||
{
|
||||
"rank": 3,
|
||||
"source": "OriginTrackedMapPropertySource {name='Config resource 'class path resource [application.yaml]' via location 'optional:classpath:/''}",
|
||||
"value": "from-application-yaml",
|
||||
"origin": "class path resource [application.yaml] from profiles-and-config-1.0.0.jar - 21:13"
|
||||
}
|
||||
],
|
||||
"shadowedCount": 2
|
||||
}
|
||||
./scripts/demo-precedence.sh: line 41: 5796 Killed setsid nohup java -Ddemo.greeting=from-system-property -jar "$JAR" --spring.profiles.active=prod > /tmp/profiles-precedence2.log 2>&1 < /dev/null
|
||||
41
profiles-and-config/docs/output/02-profile-file-loses.txt
Normal file
41
profiles-and-config/docs/output/02-profile-file-loses.txt
Normal file
@@ -0,0 +1,41 @@
|
||||
== does application-prod.yaml win? ==
|
||||
|
||||
demo.datasource-url is set in application.yaml and again in application-prod.yaml.
|
||||
|
||||
--- 1. prod profile active, no environment variable ---
|
||||
$ java -jar target/profiles-and-config-1.0.0.jar --spring.profiles.active=prod
|
||||
active profiles : prod, prod-db, prod-metrics
|
||||
effective value : jdbc:postgresql://prod-db:5432/orders
|
||||
1. jdbc:postgresql://prod-db:5432/orders <- 'file application-prod.yaml' via location 'optional:classpath:/''}
|
||||
2. jdbc:h2:mem:default <- 'file application.yaml' via location 'optional:classpath:/''}
|
||||
holders that lost: 1
|
||||
|
||||
--- 2. identical, plus one leftover environment variable ---
|
||||
$ DEMO_DATASOURCE_URL=jdbc:postgresql://leftover:5432/orders \
|
||||
java -jar target/profiles-and-config-1.0.0.jar --spring.profiles.active=prod
|
||||
active profiles : prod, prod-db, prod-metrics
|
||||
effective value : jdbc:postgresql://leftover:5432/orders
|
||||
1. jdbc:postgresql://leftover:5432/orders <- OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}
|
||||
2. jdbc:postgresql://prod-db:5432/orders <- 'file application-prod.yaml' via location 'optional:classpath:/''}
|
||||
3. jdbc:h2:mem:default <- 'file application.yaml' via location 'optional:classpath:/''}
|
||||
holders that lost: 2
|
||||
|
||||
The profile-specific file is still loaded and still holds its value -- it is listed,
|
||||
and it lost. Config data is item 3 in the documented precedence list; OS environment
|
||||
variables are item 5, and later items win.
|
||||
|
||||
== the full property-source stack, in order ==
|
||||
$ curl -s localhost:8080/sources
|
||||
1. MapPropertySource server.ports
|
||||
2. ConfigurationPropertySourcesPropertySource configurationProperties
|
||||
3. SimpleCommandLinePropertySource commandLineArgs
|
||||
4. StubPropertySource servletConfigInitParams
|
||||
5. ServletContextPropertySource servletContextInitParams
|
||||
6. PropertiesPropertySource systemProperties
|
||||
7. OriginAwareSystemEnvironmentPropertySource systemEnvironment
|
||||
8. RandomValuePropertySource random
|
||||
9. OriginTrackedMapPropertySource Config resource 'class path resource [application-prod-metrics.yaml]' via location 'optional:classpath:/'
|
||||
10. OriginTrackedMapPropertySource Config resource 'class path resource [application-prod-db.yaml]' via location 'optional:classpath:/'
|
||||
11. OriginTrackedMapPropertySource Config resource 'class path resource [application-prod.yaml]' via location 'optional:classpath:/'
|
||||
12. OriginTrackedMapPropertySource Config resource 'class path resource [application.yaml]' via location 'optional:classpath:/'
|
||||
13. ApplicationInfoPropertySource applicationInfo
|
||||
45
profiles-and-config/docs/output/03-config-tree.txt
Normal file
45
profiles-and-config/docs/output/03-config-tree.txt
Normal file
@@ -0,0 +1,45 @@
|
||||
== what Kubernetes actually mounts ==
|
||||
$ find /tmp/demo-configmap /tmp/demo-secret -type f | sort
|
||||
<configmap-mount>/demo.datasource-url
|
||||
<configmap-mount>/demo.greeting
|
||||
<configmap-mount>/demo.pool-size
|
||||
<configmap-mount>/demo/nested/value
|
||||
<secret-mount>/demo.api-key
|
||||
|
||||
$ cat <configmap-mount>/demo.greeting; echo
|
||||
from-configmap-volume
|
||||
|
||||
Each file holds a bare value with no trailing newline and no key. There is no
|
||||
properties syntax to parse -- the filename is the key.
|
||||
|
||||
== importing it ==
|
||||
$ java -jar target/profiles-and-config-1.0.0.jar \
|
||||
--spring.config.import=configtree:/tmp/demo-configmap/,configtree:/tmp/demo-secret/
|
||||
|
||||
active profiles : (none)
|
||||
effective value : from-configmap-volume
|
||||
1. from-configmap-volume <- ConfigTreePropertySource {name='Config tree '/tmp/demo-configmap''}
|
||||
2. from-application-yaml <- 'file application.yaml' via location 'optional:classpath:/''}
|
||||
holders that lost: 1
|
||||
|
||||
demo.pool-size = 25
|
||||
demo.nested.value = from-nested-directory
|
||||
demo.api-key = sk_live_not_a_real_key
|
||||
|
||||
A directory under the mount becomes a nested property: demo/nested/value is
|
||||
demo.nested.value. That is how a ConfigMap with slashes in its keys arrives.
|
||||
|
||||
== the part that surprises people ==
|
||||
An imported config tree outranks application.yaml, but it is still config data,
|
||||
so it still loses to an environment variable:
|
||||
|
||||
active profiles : (none)
|
||||
effective value : from-environment-variable
|
||||
1. from-environment-variable <- OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}
|
||||
2. from-configmap-volume <- ConfigTreePropertySource {name='Config tree '/tmp/demo-configmap''}
|
||||
3. from-application-yaml <- 'file application.yaml' via location 'optional:classpath:/''}
|
||||
holders that lost: 2
|
||||
|
||||
There is also no such thing as a profile-specific config tree. There is no
|
||||
<mount>-prod directory convention; a per-environment ConfigMap is a different mount
|
||||
chosen by the deployment, not by spring.profiles.active.
|
||||
60
profiles-and-config/docs/output/04-import-and-multidoc.txt
Normal file
60
profiles-and-config/docs/output/04-import-and-multidoc.txt
Normal file
@@ -0,0 +1,60 @@
|
||||
== spring.config.import: which document wins? ==
|
||||
|
||||
application-import.yaml imports imported.yaml. Both set demo.greeting.
|
||||
$ java -jar target/profiles-and-config-1.0.0.jar --spring.profiles.active=import
|
||||
|
||||
active profiles : import
|
||||
effective value : from-imported-yaml
|
||||
1. from-imported-yaml <- 'file imported.yaml' via location 'optional:classpath:/imported.yaml''}
|
||||
2. from-application-import-yaml <- 'file application-import.yaml' via location 'optional:classpath:/''}
|
||||
3. from-application-yaml <- 'file application.yaml' via location 'optional:classpath:/''}
|
||||
holders that lost: 2
|
||||
|
||||
demo.imported-only = yes-this-file-was-read
|
||||
|
||||
The imported file WON. spring.config.import does not behave like #include, and it
|
||||
does not behave like a default either: the imported document is processed AFTER the
|
||||
document that declared the import, so it outranks the file that pulled it in.
|
||||
If you import a shared baseline expecting your own file to override it, every key
|
||||
the baseline sets will quietly beat yours.
|
||||
|
||||
== one file, several documents, activated by condition ==
|
||||
--- spring.profiles.active=<none> (with the multidoc profile) ---
|
||||
active profiles : multidoc
|
||||
effective value : from-multidoc-default-document
|
||||
1. from-multidoc-default-document <- 'file application-multidoc.yaml' via location 'optional:classpath:/' (document ...
|
||||
2. from-application-yaml <- 'file application.yaml' via location 'optional:classpath:/''}
|
||||
holders that lost: 1
|
||||
|
||||
--- spring.profiles.active=staging (with the multidoc profile) ---
|
||||
active profiles : multidoc, staging
|
||||
effective value : from-multidoc-staging-document
|
||||
1. from-multidoc-staging-document <- 'file application-multidoc.yaml' via location 'optional:classpath:/' (document ...
|
||||
2. from-multidoc-default-document <- 'file application-multidoc.yaml' via location 'optional:classpath:/' (document ...
|
||||
3. from-application-yaml <- 'file application.yaml' via location 'optional:classpath:/''}
|
||||
holders that lost: 2
|
||||
|
||||
--- spring.profiles.active=prod (with the multidoc profile) ---
|
||||
active profiles : multidoc, prod, prod-db, prod-metrics
|
||||
effective value : from-application-prod-yaml
|
||||
1. from-application-prod-yaml <- 'file application-prod.yaml' via location 'optional:classpath:/''}
|
||||
2. from-multidoc-prod-document <- 'file application-multidoc.yaml' via location 'optional:classpath:/' (document ...
|
||||
3. from-multidoc-default-document <- 'file application-multidoc.yaml' via location 'optional:classpath:/' (document ...
|
||||
4. from-application-yaml <- 'file application.yaml' via location 'optional:classpath:/''}
|
||||
holders that lost: 3
|
||||
|
||||
Later documents in the same file win over earlier ones, so the unconditional first
|
||||
document acts as the default and each conditional document overrides it.
|
||||
|
||||
== the activation Spring Boot refuses ==
|
||||
application-badactivation.yaml tries to set spring.profiles.active from a document
|
||||
that is itself conditional on a profile.
|
||||
$ java -jar target/profiles-and-config-1.0.0.jar --spring.profiles.active=badactivation,staging
|
||||
|
||||
org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property
|
||||
'spring.profiles.active' imported from location 'class path resource
|
||||
[application-badactivation.yaml]' is invalid in a profile specific resource [origin: class path
|
||||
resource [application-badactivation.yaml] from profiles-and-config-1.0.0.jar - 12:13]
|
||||
at
|
||||
org.springframework.boot.context.config.InvalidConfigDataPropertyException.lambda$throwIfPropert
|
||||
yFound$1(InvalidConfigDataPropertyException.java:123)
|
||||
Reference in New Issue
Block a user