This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch docs
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/docs by this push:
new 8048837b47 docs: document per-RestContext @Rest(config=...) @Value
resolution and release-notes entry (TODO-95)
8048837b47 is described below
commit 8048837b4744ad275db19f2b97abcdd2eb5d89e7
Author: James Bognar <[email protected]>
AuthorDate: Wed May 27 09:04:07 2026 -0400
docs: document per-RestContext @Rest(config=...) @Value resolution and
release-notes entry (TODO-95)
Co-authored-by: Cursor <[email protected]>
---
pages/release-notes/9.5.0.md | 36 ++++++--
pages/topics/02.21.06.ValueAnnotationBasics.md | 118 +++++++++++++++++++++++--
2 files changed, 140 insertions(+), 14 deletions(-)
diff --git a/pages/release-notes/9.5.0.md b/pages/release-notes/9.5.0.md
index b70d27d7b7..42311de31e 100644
--- a/pages/release-notes/9.5.0.md
+++ b/pages/release-notes/9.5.0.md
@@ -2253,15 +2253,33 @@ see [@Value Framework-Internal
Adoption](../topics/ValueFrameworkInternal.md) fo
mapping. The Javadoc cross-references that used to read `{@link
CallLogger#SP_xxx "..."}`
have all been rewritten to plain `<js>"..."</js>` literal text.
-#### `RestContext` wires its `Config` into `Settings` (TODO-79)
-
-- `RestContext`'s constructor now installs a `ConfigPropertySource` for the
resource-scoped `Config`
- onto the process-wide `Settings.get()` source stack, and `destroy()` removes
it. This lets
- `@Value("${section/key}")` injections on resource-scoped beans see the same
`Config` that
- `$C{section/key}` resolves against — without any further wiring.
-- Because `Settings` walks sources in reverse insertion order, the
per-resource `Config` wins over
- the classpath-default `juneau.cfg` bridge installed by
`ConfigPropertySourceProvider` and over
- the JVM-default sources (system properties, env vars).
+#### Per-`RestContext` `@Value` resolution against `@Rest(config=...)` Configs
(TODO-95)
+
+- `RestContext`'s constructor now installs a `PropertySource` named
`"rest.config"` on the
+ resource's `BeanStore` — **not** on the process-wide `Settings.get()`
singleton. The source
+ wraps the resource's `@Rest(config=...)` `Config`(s) and walks them in
child-to-parent order so
+ the child's keys win on collision and the parent's keys fill the gaps.
+- `@Value("${section/key}")` injection sites on the resource bean (and on
request-scoped beans
+ whose `BeanStore` parent-walks to the resource's) consult the scoped
`PropertySource` between
+ the `Settings` local/global override stores and the global `Settings`
sources chain. Resolution
+ order for these sites is now:
+ 1. `Settings.setLocal(...)` / `Settings.setGlobal(...)` overrides (still win
— test
+ override semantics are preserved).
+ 2. The resource's `@Rest(config=...)` `Config`(s).
+ 3. The global `Settings` sources list (system properties, env vars, classpath
+ `ConfigPropertySourceProvider`, microservice `Config`, Spring
`Environment`, …).
+ 4. The `@Value("${key:default}")` literal default branch.
+- The `BeanStore`-scoped registration means `MockRestClient`'s static
`RestContext` cache (and
+ any other long-lived cache) cannot leak `ConfigPropertySource`s into the
global `Settings`
+ singleton — the 6.7× full-suite regression seen during
FINISHED-79's prototype is
+ structurally impossible. A new `Settings_NoLeakedSources_Test` (200×
`MockRestClient.build`
+ cycle) asserts the source count stays flat across the suite.
+- `@Value Supplier<String>` field types compose with the scoped chain: the
`BeanStore` reference
+ is captured (by-reference, not snapshotted) by the supplier so every
`.get()` re-evaluates
+ against the current state of the resource's `PropertySource`s. Hot-reload of
the underlying
+ `Config` (or `Settings.set(...)` of an override key) takes effect on the
next `.get()` without
+ rebuilding the resource bean.
+- Cross-link: [@Value Annotation Basics — Per-resource
`@Rest(config=...)`
integration](../topics/ValueAnnotationBasics.md#per-resource-restconfig-integration).
#### Rest Debug Rethink (TODO-20)
diff --git a/pages/topics/02.21.06.ValueAnnotationBasics.md
b/pages/topics/02.21.06.ValueAnnotationBasics.md
index 1420e088b8..f47c870cdc 100644
--- a/pages/topics/02.21.06.ValueAnnotationBasics.md
+++ b/pages/topics/02.21.06.ValueAnnotationBasics.md
@@ -38,7 +38,8 @@ sources) works identically with `${...}`. See
## Resolution order (precedence)
-`Settings.get(name)` walks every registered `PropertySource` in **reverse
insertion order** — the
+For most `@Value` sites, resolution goes through the process-wide
`Settings.get(name)` chain:
+`Settings` walks every registered `PropertySource` in **reverse insertion
order** — the
most-recently-added source wins. The default chain (built up at startup) looks
like:
1. **`PropertySourceProvider` SPI sources** added at process startup (e.g.
classpath `juneau.cfg`
@@ -46,14 +47,17 @@ most-recently-added source wins. The default chain (built
up at startup) looks l
the priority list.
2. **Per-microservice `Config`** —
`org.apache.juneau.microservice.Microservice` installs a
`ConfigPropertySource` for its own `Config` in its constructor.
-3. **Per-`RestContext` `Config`** — `RestContext` installs one for the
`@Rest`-resource-scoped
- `Config` in its constructor.
-4. **Spring `Environment` bridge** — when `juneau-rest-server-springboot`'s
`SpringBeanStore` is
+3. **Spring `Environment` bridge** — when `juneau-rest-server-springboot`'s
`SpringBeanStore` is
constructed, it installs a `SpringEnvironmentPropertySource` so
`${spring.app.key}` resolves
against `application.yaml`, env vars, command-line args, and any custom
Spring `PropertySource`.
-5. **Built-in `Settings` sources** — system properties, environment variables,
`.env` file,
+4. **Built-in `Settings` sources** — system properties, environment variables,
`.env` file,
argument list, JAR manifest entries.
+When the `@Value` site is on a bean that the REST runtime instantiates
(resource bean fields,
+constructor parameters, request-scoped bean fields), the resolution order
grows one tier —
+the resource's [`@Rest(config=...)`](#per-resource-restconfig-integration)
`Config` participates
+between the `Settings` override layer and the chain above.
+
Because all sources are checked, the same `@Value` expression resolves
identically under a
microservice, a Spring Boot app, or a bare REST resource — the *origin* of the
value changes, the
expression doesn't.
@@ -211,6 +215,110 @@ public class MyResource extends BasicSpringRestServlet {
The bridge is removed when `SpringBeanStore.clear()` is called — important for
tests that build
many transient Spring contexts in the same JVM.
+## Per-resource `@Rest(config=...)` integration
{#per-resource-restconfig-integration}
+
+When a REST resource declares `@Rest(config="my.cfg")`, the resolved `Config`
is wired into the
+resource's `BeanStore` as a <a
href="/site/apidocs/org/apache/juneau/commons/settings/PropertySource.html"
target="_blank">PropertySource</a>
+bean named `"rest.config"`. Any `@Value("${some-key}")` site on the resource
bean (or on a
+request-scoped bean whose `BeanStore` parent-walks to the resource's) consults
this scoped
+`PropertySource` between the `Settings` local/global overrides and the global
`Settings` sources
+chain.
+
+### Resolution order for resource-scoped `@Value` sites
+
+1. **`Settings.setLocal(...)` / `Settings.setGlobal(...)` overrides** —
still win. The test
+ override contract is preserved (overrides beat resource Configs).
+2. **The resource's `@Rest(config=...)` `Config`(s)** — via the
per-`BeanStore`
+ `PropertySource` bean.
+3. **The process-wide `Settings.get(name)` chain** — reverse-walks the
registered sources
+ from the [resolution-order section](#resolution-order-precedence) above.
+4. **The `@Value` default branch** — the `${key:default}` literal.
+
+### Concrete example
+
+`api.cfg` on the classpath / cwd:
+
+```ini
+api.key = secret-A
+api.url = https://a.example.org/
+
+[section]
+nested = nested-value
+```
+
+```java
+@Rest(config="api.cfg")
+public class MyResource extends BasicRestServlet {
+
+ @Value("${api.key}") // → "secret-A"
+ String apiKey;
+
+ @Value("${section/nested}") // → "nested-value"
+ String nested;
+
+ @Value("${unknown:fallback}") // → "fallback" (key absent, default
used)
+ String fallback;
+
+ @Value("${java.home}") // → System property (falls through to
Settings)
+ String javaHome;
+}
+```
+
+### Isolation guarantee
+
+Each `RestContext` carries its own `BeanStore` with its own `"rest.config"`
bean. Resource A's
+`@Value("${foo}")` resolves against `A.cfg`, Resource B's `@Value("${foo}")`
resolves against
+`B.cfg` — the two never see each other's `Config`. The same isolation
extends to
+`MockRestClient`'s static `RestContext` cache and to long-lived Spring Boot
contexts because
+the registration lives on the per-resource `BeanStore`, not on the
process-wide singleton.
+
+### Inheritance (child wins, parent fills gaps)
+
+When `@Rest(config="parent.cfg")` on `ParentResource` and
`@Rest(config="child.cfg")` on
+`ChildResource extends ParentResource` declare different config names, the
child's keys win on
+collision and the parent's keys fill the gaps:
+
+```ini
+# parent.cfg
+parent.only = parent-value
+shared = parent-shared
+```
+
+```ini
+# child.cfg
+child.only = child-value
+shared = child-shared
+```
+
+```java
+@Rest(config="parent.cfg")
+public class ParentResource extends BasicRestServlet {
+ @Value("${parent.only}") String parentOnly;
+ @Value("${shared}") String shared;
+}
+
+@Rest(config="child.cfg")
+public class ChildResource extends ParentResource {}
+```
+
+A `ChildResource` instance sees `child-value` for `child.only`, `parent-value`
for `parent.only`
+(child's `Config` doesn't define it — parent fills the gap), and
`child-shared` for
+`shared` (child wins on collision).
+
+### `setLocal` / `setGlobal` still win
+
+The resource-scoped `Config` is consulted *after* the `Settings` override
stores, so existing
+test fixtures that call `Settings.get().setGlobal("api.key", "from-test")`
continue to take
+precedence over `api.cfg` — matching the established override contract.
+
+### `Supplier<String>` re-evaluates against the scoped `BeanStore`
+
+`@Value Supplier<String>` field types compose with the resource-scoped chain:
the `BeanStore`
+reference is captured (by-reference, not snapshotted) by the supplier, so
every `.get()` call
+re-evaluates against the current state of the resource's `PropertySource`s.
This means hot-
+reload of the resource Config (or `Settings.set(...)` of an override key)
takes effect on the
+next `.get()` call without rebuilding the resource bean.
+
## Classpath-default `Config` (`juneau-config`)
The `juneau-config` module ships a `META-INF/services` entry for