Skip to main content

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​

GoalNon-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.

ConcernJuneau typeJSR-330 / Spring equivalents recognized
Injection pointorg.apache.juneau.commons.inject.Injectjakarta.inject.Inject, javax.inject.Inject, org.springframework.beans.factory.annotation.Autowired
Named qualifierorg.apache.juneau.commons.inject.Namedjakarta.inject.Named, javax.inject.Named
Qualifier markerorg.apache.juneau.commons.inject.Qualifierjakarta.inject.Qualifier, javax.inject.Qualifier
Singleton scopeorg.apache.juneau.commons.inject.Singletonjakarta.inject.Singleton, javax.inject.Singleton
Providerorg.apache.juneau.commons.inject.Provider<T> (single T get())jakarta.inject.Provider, javax.inject.Provider (resolved via java.lang.reflect.Proxy)
Post-construct hookorg.apache.juneau.commons.inject.PostConstructjakarta.annotation.PostConstruct, javax.annotation.PostConstruct
Pre-destroy hookorg.apache.juneau.commons.inject.PreDestroyjakarta.annotation.PreDestroy, javax.annotation.PreDestroy

Juneau-only extensions:

AnnotationPurpose
@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.
@PrimaryDisambiguates 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():

  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)​

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:

  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.

In This Section​