Inject Package
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
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
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():
- Invokes
@PreDestroyon all beans that were resolved throughgetBean(...)/getBeansOfType(...), in LIFO order (most-recently resolved first). - Aggregates per-bean destroyer failures as suppressed exceptions on a single
BeanCreationException. - Marks the store closed; subsequent mutating operations throw
IllegalStateException. - 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)
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
@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
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
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.
@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:
publicprotected- 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
@Namedvalue. - No AOP / proxying except the
Provider<T>adapter described above. - Field / method injection of
@Injectis honored on framework-instantiated types (e.g.@Restresources). The framework does not scan arbitrary user objects.
In This Section
📄️ 2.1.10.1. @Value Annotation Basics
The @Value annotation
📄️ 2.1.10.2. @Value Framework-Internal Adoption
This page documents the framework-internal sites that read configuration through the
📄️ 2.1.10.3. @ConfigProperties Annotation Basics
The @ConfigProperties