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 469fa09c41 docs: document juneau-commons inject package and migration 
updates
469fa09c41 is described below

commit 469fa09c4111de8bb663197c8c1fe23629cff2c7
Author: James Bognar <[email protected]>
AuthorDate: Thu May 14 18:02:45 2026 -0400

    docs: document juneau-commons inject package and migration updates
    
    Co-authored-by: Cursor <[email protected]>
---
 pages/release-notes/9.5.0.md                 |  67 ++++++++++
 pages/topics/06.01.JuneauCommonsBasics.md    |   1 +
 pages/topics/06.02.07.JuneauCommonsInject.md | 185 +++++++++++++++++++++++++++
 pages/topics/23.01.V9.5-migration-guide.md   |  14 ++
 4 files changed, 267 insertions(+)

diff --git a/pages/release-notes/9.5.0.md b/pages/release-notes/9.5.0.md
index 5c4754f4e2..d33162ed5d 100644
--- a/pages/release-notes/9.5.0.md
+++ b/pages/release-notes/9.5.0.md
@@ -1277,6 +1277,73 @@ The following builder-class constructors and fields have 
been narrowed from `Wri
 
 Callers that previously passed a `WritableBeanStore` still compile without 
changes, since `WritableBeanStore` extends `BeanStore`.
 
+#### JSR-330 Alignment + Spring-Lite Additions in `commons.inject` (TODO-24)
+
+The `org.apache.juneau.commons.inject` package now presents a coherent 
injection story:
+
+> *Juneau supports JSR-330 (`jakarta.inject` / `javax.inject`) annotations as 
closely as our lightweight model allows, without taking a hard dependency on 
`jakarta.inject-api`. Where Spring conventions add value to the lightweight 
model, we adopt them under our own annotations.*
+
+All JSR/Spring annotation recognition is **by fully qualified name** (FQN), so 
applications can use `jakarta.inject.Inject`, `javax.inject.Inject`, 
`org.springframework.beans.factory.annotation.Autowired`, or 
`org.apache.juneau.commons.inject.Inject` interchangeably — Juneau picks them 
up transparently without a compile-time dependency.
+
+##### New annotations and types
+
+| Type | Purpose |
+|------|---------|
+| `@Inject` (Juneau-owned) | Marks constructor parameters, methods, or fields 
as injection points. JSR-330 / Spring equivalents (`jakarta.inject.Inject`, 
`javax.inject.Inject`, `@Autowired`) are recognized by FQN. |
+| `@Named` | Bean qualifier (moved from `org.apache.juneau.annotation.Named` — 
see migration row below). |
+| `@Qualifier` | Meta-annotation marker used to declare custom qualifier 
annotations. |
+| `@Singleton` | Scope marker. Semantics: a singleton is registered once in a 
`BeanStore` and reused; no JVM-wide singleton, no proxying. |
+| `@Primary` | Disambiguates when multiple candidates of the same type exist. 
Unqualified `BeanStore.getBean(type)` prefers a single `@Primary` candidate; 
multiple primaries throw `BeanCreationException`. |
+| `@Order(int)` and `@Bean#priority()` | Ordering for 
`BeanStore.getBeansOfType(Class)`. Lower values are higher precedence. When 
both are present, `@Order` wins. |
+| `@Configuration` | Marker for types that host `@Bean` declarations. Supports 
`imports = { ... }` for chaining configuration classes (deduped transitively). 
`@Bean` members declared on superclasses are inherited. |
+| `@PostConstruct` / `@PreDestroy` | Lifecycle hooks. JSR-250 
`jakarta.annotation` / `javax.annotation` equivalents are recognized by FQN. |
+| `@Conditional(Class<? extends Condition>)` | Predicate-driven registration 
guard. |
+| `@ConditionalOnClass(String)` | Skip the annotated configuration / `@Bean` 
when a class is absent from the classpath. |
+| `@ConditionalOnMissingBean(Class, name)` | Skip when a matching bean is 
already registered. |
+| `@ConditionalOnProperty(name, havingValue, matchIfMissing)` | Skip / 
register based on a `Settings` property. |
+| `Provider<T>` | Single-method `T get()` interface. Parameters and fields 
typed as `Provider<T>` (Juneau / `jakarta.inject` / `javax.inject`) are 
resolved via a `java.lang.reflect.Proxy` over a `Supplier<T>` from the bean 
store. |
+| `Condition` / `ConditionContext` | SPI for custom `@Conditional` predicates 
with access to the bean store, settings facade, classloader, and annotated 
element. |
+| `JsrSupport` | Single utility class centralizing recognized FQN constants 
(`JAKARTA_INJECT`, `SPRING_AUTOWIRED`, etc.) and matcher helpers. |
+
+##### `@Configuration` registration
+
+`WritableBeanStore.registerConfiguration(Class<?>)` and 
`registerConfigurations(Class<?>...)` register a `@Configuration` class with 
the store:
+
+- Static and non-static `@Bean` fields and methods are discovered.
+- `imports` are processed recursively and deduplicated; the same class 
registered twice (directly or transitively) is processed only once per bean 
store.
+- `@Bean` members declared on superclasses of the configuration type are 
inherited; the superclass is processed before the subclass so subclass `@Bean` 
methods can depend on superclass beans.
+- Duplicate `(type, name)` registrations within the same store throw 
`BeanCreationException`.
+- `@Conditional` annotations on the configuration class skip the entire class 
(and any imports it would have pulled in if the configuration class itself is 
conditional); on individual `@Bean` members they skip only that member.
+
+##### Bean lifecycle: `AutoCloseable`
+
+`WritableBeanStore` now extends `AutoCloseable`. Calling `close()`:
+
+- Invokes `@PreDestroy` methods on all beans that were resolved through 
`getBean(...)` / `getBeansOfType(...)`, in LIFO order (most-recently resolved 
first).
+- Aggregates per-bean destroy failures as suppressed exceptions on a single 
thrown `BeanCreationException`.
+- Marks the store as closed; subsequent mutating operations throw 
`IllegalStateException`.
+- Is idempotent; a second `close()` is a no-op.
+
+`RestContext.destroy()` now invokes `beanStore.close()` after the existing 
`@RestDestroy` hooks fire, so REST resources can rely on `@PreDestroy` for 
cleanup of bean-store-managed dependencies.
+
+##### `@Inject` on `@Rest` resource fields and methods
+
+`@Rest` resources now have `@Inject`-annotated fields and methods populated 
from the resource's bean store during context initialization, immediately 
before `@PostConstruct` callbacks fire. This applies to all four recognized 
FQNs (Juneau, `jakarta.inject.Inject`, `javax.inject.Inject`, Spring 
`@Autowired`). `Optional<T>`, collection types, and `Provider<T>` are all 
supported as field / parameter types.
+
+##### Constructor visibility for `BeanInstantiator`
+
+`BeanInstantiator` now considers **package-private** constructors as a final 
fallback after public and protected, both for the target bean type and for 
builder discovery. `private` constructors remain excluded (they signal "do not 
instantiate"). This means `@Configuration` classes and other 
framework-instantiated types no longer need to expose a `public` (or even 
`protected`) constructor purely to satisfy the bean store, as long as the class 
is reachable in the declaring package.
+
+##### Migration guide
+
+| Topic | Action |
+|-------|--------|
+| `org.apache.juneau.annotation.Named` | **Moved** to 
`org.apache.juneau.commons.inject.Named`. Update imports — no alias is kept at 
the old location. |
+| Simple-name annotation matching | Recognition is now strictly **FQN-based**. 
Local `@interface Inject` / `@interface Autowired` / `@interface PostConstruct` 
mocks in tests no longer trigger injection; use the real Juneau-owned types or 
the canonical JSR-330 / Spring FQNs. |
+| `@PostConstruct` discovery | Now matches by FQN 
(`org.apache.juneau.commons.inject.PostConstruct`, 
`jakarta.annotation.PostConstruct`, `javax.annotation.PostConstruct`). Custom 
annotations named `PostConstruct` are no longer picked up. |
+| `WritableBeanStore` implementations | Must now implement `void close() 
throws BeanCreationException` (typically by delegating to a wrapped 
`BasicBeanStore`). |
+| `@Bean` precedence chain | Unchanged from earlier in 9.5.0: Spring / 
overriding-parent → `@Bean` / local entry → memoizer-backed framework default. |
+
 ### juneau-marshall-rdf
 
 #### Upgraded Apache Jena to 5.6.0
diff --git a/pages/topics/06.01.JuneauCommonsBasics.md 
b/pages/topics/06.01.JuneauCommonsBasics.md
index e6c422a4f3..21df3dec99 100644
--- a/pages/topics/06.01.JuneauCommonsBasics.md
+++ b/pages/topics/06.01.JuneauCommonsBasics.md
@@ -34,6 +34,7 @@ The `juneau-commons` module provides common utilities and 
APIs used across the J
 
 - **Collections** (`org.apache.juneau.commons.collections`): Enhanced 
collection utilities including fluent collections, multi-maps, filtered 
collections, and caching utilities
 - **I/O** (`org.apache.juneau.commons.io`): File and stream utilities, console 
support, MIME type detection, and path builders
+- **Inject** (`org.apache.juneau.commons.inject`): JSR-330–aligned bean store 
+ Spring-lite injection layer (`@Inject`, `@Named`, `@Qualifier`, `@Singleton`, 
`@Primary`, `@Order`, `@Bean`, `@Configuration`, `@Conditional`, 
`@PostConstruct`, `@PreDestroy`, and `Provider<T>`) without compile-time 
dependencies on `jakarta.inject-api` or `jakarta.annotation-api`
 - **Reflection** (`org.apache.juneau.commons.reflect`): Comprehensive 
reflection utilities for working with classes, methods, fields, and annotations
 - **Settings** (`org.apache.juneau.commons.settings`): Thread-local and global 
settings management with support for functional sources and stores
 - **Utils** (`org.apache.juneau.commons.utils`): General utility classes for 
strings, collections, classes, dates, files, and more
diff --git a/pages/topics/06.02.07.JuneauCommonsInject.md 
b/pages/topics/06.02.07.JuneauCommonsInject.md
new file mode 100644
index 0000000000..ec7ec31d79
--- /dev/null
+++ b/pages/topics/06.02.07.JuneauCommonsInject.md
@@ -0,0 +1,185 @@
+---
+title: "Inject Package"
+slug: JuneauCommonsInject
+---
+
+The `org.apache.juneau.commons.inject` package provides Juneau's lightweight 
injection model. The framework is **JSR-330–aligned** (`jakarta.inject` / 
`javax.inject`) and layers a small set of Spring-flavored conveniences 
(`@Bean`, `@Configuration`, `@Primary`, `@Order`, `@Conditional`) on top — all 
under Juneau-owned annotations, and **without a compile-time dependency on 
`jakarta.inject-api` or `jakarta.annotation-api`**.
+
+> Juneau supports JSR-330 (`jakarta.inject` / `javax.inject`) annotations as 
closely as our lightweight model allows. Where Spring conventions add value to 
the lightweight model, we adopt them under our own annotations.
+
+## What this package is — and is not
+
+| Goal | Non-goal |
+|------|----------|
+| Lightweight, predictable bean lookup with constructor / field / method 
injection. | Become a full JSR-330 / CDI **provider**. |
+| FQN-based recognition of `jakarta.inject` / `javax.inject` / Spring 
`@Autowired` so user code that already uses those annotations Just Works. | 
Become a Spring replacement. |
+| Spring-lite conveniences (`@Bean`, `@Configuration`, `@Primary`, `@Order`, 
`@Conditional`) under Juneau annotations. | Pull `jakarta.inject-api` onto the 
compile classpath of `juneau-commons` or downstream modules. |
+| JSR-250 lifecycle (`@PostConstruct` / `@PreDestroy`) honored on 
framework-instantiated beans. | Provide a JVM-wide singleton scope, AOP, 
proxying, or scopes beyond `@Singleton`. |
+
+## Recognized annotations and types
+
+All FQN-based — Juneau picks them up transparently whether the user wrote the 
Juneau-owned annotation, the JSR-330 equivalent, or the Spring equivalent.
+
+| Concern | Juneau type | JSR-330 / Spring equivalents recognized |
+|---------|-------------|----------------------------------------|
+| Injection point | `org.apache.juneau.commons.inject.Inject` | 
`jakarta.inject.Inject`, `javax.inject.Inject`, 
`org.springframework.beans.factory.annotation.Autowired` |
+| Named qualifier | `org.apache.juneau.commons.inject.Named` | 
`jakarta.inject.Named`, `javax.inject.Named` |
+| Qualifier marker | `org.apache.juneau.commons.inject.Qualifier` | 
`jakarta.inject.Qualifier`, `javax.inject.Qualifier` |
+| Singleton scope | `org.apache.juneau.commons.inject.Singleton` | 
`jakarta.inject.Singleton`, `javax.inject.Singleton` |
+| Provider | `org.apache.juneau.commons.inject.Provider<T>` (single `T get()`) 
| `jakarta.inject.Provider`, `javax.inject.Provider` (resolved via 
`java.lang.reflect.Proxy`) |
+| Post-construct hook | `org.apache.juneau.commons.inject.PostConstruct` | 
`jakarta.annotation.PostConstruct`, `javax.annotation.PostConstruct` |
+| Pre-destroy hook | `org.apache.juneau.commons.inject.PreDestroy` | 
`jakarta.annotation.PreDestroy`, `javax.annotation.PreDestroy` |
+
+Juneau-only extensions:
+
+| Annotation | Purpose |
+|------------|---------|
+| `@Bean(name, value, priority)` | Marks a method or field on a 
framework-instantiated host as a provider of a bean. |
+| `@Configuration(imports={...})` | Marks a type that hosts `@Bean` 
declarations. `imports` chains additional configuration classes. Members 
declared on superclasses are inherited. |
+| `@Primary` | Disambiguates when multiple candidates of the same type exist. 
Unqualified `getBean(type)` prefers a `@Primary` candidate; multiple primaries 
throw `BeanCreationException`. |
+| `@Order(int)` | Bean ordering for `getBeansOfType(Class)` — lower is higher 
precedence. When both `@Order` and `@Bean#priority()` are present, `@Order` 
wins. |
+| `@Conditional(Class<? extends Condition>)` | Generic predicate-driven 
registration guard. |
+| `@ConditionalOnClass("fqn")` | Skip registration when a class is not on the 
classpath. |
+| `@ConditionalOnMissingBean(Class, name)` | Skip when a matching bean is 
already in the store. |
+| `@ConditionalOnProperty(name, havingValue, matchIfMissing)` | Skip / 
register based on a `Settings` property. |
+
+`Condition` and `ConditionContext` are the SPI for custom predicates.
+
+## Core API
+
+### `BeanStore` — read-only lookup
+
+```java
+beanStore.getBean(MyService.class);                      // unnamed (or 
@Primary among candidates)
+beanStore.getBean(MyService.class, "name");              // qualified by name
+beanStore.getBeansOfType(MyService.class);               // all candidates, 
ordered by @Order / priority
+beanStore.hasBean(MyService.class);
+```
+
+### `WritableBeanStore` — registration
+
+```java
+WritableBeanStore store = new BasicBeanStore(null);
+store.addBean(MyService.class, new MyService());          // unnamed
+store.addBean(MyService.class, new MyService(), "name");  // named
+store.addSupplier(MyService.class, MyService::new);
+store.addDefaultSupplier(MyService.class, () -> defaultInstance);  // 
framework default, lowest priority
+store.registerConfiguration(MyConfig.class);              // @Configuration 
with @Bean members
+```
+
+`registerConfiguration` is **idempotent at the store level** — registering the 
same class twice (directly or transitively via `imports`) is processed only 
once per store.
+
+### Lifecycle: `AutoCloseable`
+
+`WritableBeanStore extends AutoCloseable`. Calling `close()`:
+
+1. Invokes `@PreDestroy` on all beans that were resolved through 
`getBean(...)` / `getBeansOfType(...)`, in **LIFO** order (most-recently 
resolved first).
+2. Aggregates per-bean destroyer failures as suppressed exceptions on a single 
`BeanCreationException`.
+3. Marks the store closed; subsequent mutating operations throw 
`IllegalStateException`.
+4. Is idempotent — a second `close()` is a no-op.
+
+`RestContext.destroy()` calls `beanStore.close()` after the existing 
`@RestDestroy` hooks fire.
+
+## Examples
+
+### Constructor injection (works with any recognized `@Inject`)
+
+```java
+public class MyService {
+
+    private final Repo repo;
+    private final Notifier notifier;
+
+    @Inject  // org.apache.juneau.commons.inject.Inject — or jakarta / javax / 
Spring @Autowired
+    public MyService(Repo repo, @Named("email") Notifier notifier) {
+        this.repo = repo;
+        this.notifier = notifier;
+    }
+}
+```
+
+### `@Configuration` with imports, superclass inheritance, and `@Conditional`
+
+```java
+@Configuration(imports = { CommonConfig.class })
+public class AppConfig extends BaseConfig {
+
+    @Bean public Repo repo() { return new JdbcRepo(); }
+
+    @Bean @Primary public Notifier emailNotifier() { return new 
EmailNotifier(); }
+
+    @Bean @ConditionalOnClass("redis.clients.jedis.Jedis")
+    public Cache cache() { return new RedisCache(); }
+}
+```
+
+`AppConfig` inherits `@Bean` members declared on `BaseConfig`. The `Cache` 
bean only registers if `Jedis` is on the classpath.
+
+### `Provider<T>` injection
+
+```java
+public class LazyConsumer {
+    @Inject jakarta.inject.Provider<HeavyService> provider;
+
+    void onRequest() {
+        var svc = provider.get();  // resolved from the bean store per-call
+    }
+}
+```
+
+Juneau resolves the `jakarta.inject.Provider<T>` parameter via a 
`java.lang.reflect.Proxy` over a `Supplier<T>` from the bean store — no Spring 
or `jakarta.inject-api` jar required at compile time.
+
+### `@PostConstruct` / `@PreDestroy`
+
+```java
+public class CacheService implements AutoCloseable {
+
+    @Inject Settings settings;
+
+    @PostConstruct
+    public void start() { /* warm up */ }
+
+    @PreDestroy
+    public void shutdown() { /* flush + close */ }
+}
+```
+
+`@PreDestroy` fires only on beans that were resolved from the store, in LIFO 
order, when `WritableBeanStore.close()` is called.
+
+## `@Inject` on `@Rest` resource fields
+
+REST resources have `@Inject`-annotated fields and methods populated from the 
resource's bean store during context initialization, immediately before 
`@PostConstruct` callbacks fire.
+
+```java
+@Rest
+public class MyResource {
+
+    @Bean public Repo repo() { return new JdbcRepo(); }
+
+    @Inject Repo repo;                          // populated by RestContext
+
+    @jakarta.inject.Inject Notifier notifier;    // also populated (FQN match)
+
+    @PostConstruct void init() { /* fields are ready here */ }
+}
+```
+
+## Constructor visibility
+
+When `BeanInstantiator` constructs a bean (including a `@Configuration` 
class), it searches declared constructors in this order, taking the 
most-specific resolvable constructor at each level:
+
+1. `public`
+2. `protected`
+3. **package-private** (default access)
+
+`private` constructors are deliberately excluded — they signal "do not 
instantiate". The same ladder is used for builder discovery on the bean's 
builder type. In practice this means a `@Configuration` class (or any other 
bean type instantiated through the framework) does not need to expose a 
`public` constructor purely to satisfy the bean store, as long as the class is 
reachable in the declaring package.
+
+## Deviations from full JSR-330
+
+These are intentional simplifications:
+
+- No circular-dependency resolver — cycles are caller-visible.
+- No Scope SPI beyond `@Singleton`. Custom scopes are not supported in v1.
+- No qualifier-attribute equality (Spring-style); qualifiers compare by 
**annotation type** and by `@Named` value.
+- No AOP / proxying except the `Provider<T>` adapter described above.
+- Field / method injection of `@Inject` is honored on 
**framework-instantiated** types (e.g. `@Rest` resources). The framework does 
not scan arbitrary user objects.
diff --git a/pages/topics/23.01.V9.5-migration-guide.md 
b/pages/topics/23.01.V9.5-migration-guide.md
index f10d3eb007..5e57bb04d8 100644
--- a/pages/topics/23.01.V9.5-migration-guide.md
+++ b/pages/topics/23.01.V9.5-migration-guide.md
@@ -222,5 +222,19 @@ Several previously `protected` or package-private members 
were widened to `publi
 - `BeanPropertyMeta.Builder` fields `innerField`, `getter`, `setter`, 
`rawTypeMeta`, `swap`, `readTransform`, `writeTransform`, `dictionaryClasses`, 
`isUri`, `typeMeta` widened package-private → `public`.
 - `BeanPropertyValue#properties()` widened `protected` → `public`.
 
+## JSR-330 Alignment + Spring-Lite Additions in `commons.inject` (TODO-24)
+
+The `org.apache.juneau.commons.inject` package now exposes a JSR-330–aligned 
injection surface plus a Spring-lite configuration / conditional / lifecycle 
layer. Recognition of injection-related annotations is by **fully qualified 
name** — applications can drop in `jakarta.inject.Inject`, Spring `@Autowired`, 
etc., without Juneau pulling either API onto the compile classpath.
+
+| Old | New | Notes |
+|-----|-----|-------|
+| `org.apache.juneau.annotation.Named` | 
`org.apache.juneau.commons.inject.Named` | Moved; no deprecated alias is kept 
at the old location. Update imports. |
+| Local test `@interface Inject` / `@interface Autowired` / `@interface 
PostConstruct` matched the framework by simple name. | Recognition is now 
strictly **FQN-based** via `JsrSupport`. Replace test mocks with the real 
Juneau-owned types (`org.apache.juneau.commons.inject.Inject` / 
`PostConstruct`), or declare stand-in types at the canonical FQN 
(`jakarta.inject.Inject`, 
`org.springframework.beans.factory.annotation.Autowired`) on the test 
classpath. | Affects code that relied on simple [...]
+| `@PostConstruct` discovered by simple class name. | Discovered by FQN 
(`org.apache.juneau.commons.inject.PostConstruct`, 
`jakarta.annotation.PostConstruct`, `javax.annotation.PostConstruct`). | Custom 
annotations named `PostConstruct` no longer trigger lifecycle invocation. |
+| `WritableBeanStore` did not implement `AutoCloseable`. | `WritableBeanStore 
extends AutoCloseable`. `close()` invokes `@PreDestroy` on resolved beans in 
LIFO order, aggregates failures as suppressed exceptions, and marks the store 
closed. | Custom implementations must add `void close() throws 
BeanCreationException` (usually by delegating to a wrapped `BasicBeanStore`). 
`RestContext.destroy()` now wires this in automatically. |
+| n/a | New annotations: `@Inject`, `@Qualifier` (meta), `@Singleton`, 
`@Primary`, `@Order(int)`, `@Configuration`, `@PostConstruct`, `@PreDestroy`, 
`@Conditional`, `@ConditionalOnClass`, `@ConditionalOnMissingBean`, 
`@ConditionalOnProperty`. New types: `Provider<T>`, `Condition`, 
`ConditionContext`, `JsrSupport`. | All live in 
`org.apache.juneau.commons.inject`. See the 9.5 release notes for full 
semantics. |
+| `@Bean` had only `name()` / `value()` / `methodScope()` / `description()`. | 
New `int priority() default Integer.MAX_VALUE/2;` attribute provides bean 
ordering when `@Order` is absent. | Existing usages unchanged; the attribute 
defaults to mid-range so previously-unordered collections retain stable 
behavior. |
+| n/a | `WritableBeanStore.registerConfiguration(Class)` / 
`registerConfigurations(Class...)` register a `@Configuration` class, 
recursively processing `imports`, deduplicating across calls, inheriting 
`@Bean` members from superclasses (parent-first), and honoring `@Conditional` 
annotations at both class and member level. | Duplicate `(type, name)` 
registrations throw `BeanCreationException`. Class-level conditional failures 
cascade — the configuration and its imports are silently skipped. |
+
 <!-- Additional rows will be populated as 9.5 breaking changes land. See 
todo/TODO-17 for the
 ongoing 9.5.0 audit. -->

Reply via email to