Skip to main content

Inject-Aware Microservice

Starting with 10.0.0, the Microservice class is inject-aware. Every microservice owns an internal WritableBeanStore (accessible via getBeanStore()), populated from @Configuration classes registered on the builder. Beans contributed this way can be consumed by other @Bean factory methods (via constructor injection), by companion modules (e.g. JettyConfiguration contributes a JettyServerComponent that auto-mounts @Rest servlets at startup), and by user code via getBeanStore().getBean(...).

Microservice.Builderconfigurations(Class...) beanStore(WritableBeanStore)MicroservicegetBeanStore()

Quick example​

import org.apache.juneau.commons.inject.*;
import org.apache.juneau.microservice.*;

@Configuration
public class AppConfig {

@Bean
public MyService myService() {
return new MyService("hello");
}
}

public class App {
public static void main(String[] args) throws Exception {
Microservice.create()
.args(args)
.configurations(AppConfig.class)
.build()
.start()
.join();
}
}

Inside the running microservice, the bean is available via:

MyService svc = Microservice.getInstance().getBeanStore().getBean(MyService.class).orElseThrow();

What lands in the bean store​

After bootstrap completes, the store contains every bean registered by your @Configuration classes plus the microservice's own resolved values:

TypeNotes
MicroserviceThe microservice instance itself.
ArgsResolved CLI args.
ManifestFileResolved manifest.
ConfigResolved configuration.
VarResolverResolved var resolver with $A, $M, $C and standard vars wired up.
MicroserviceListenerEither the builder-supplied listener, a @Bean-supplied one, or the default.

When JettyConfiguration.class is added to configurations(...) the store additionally contains:

TypeNotes
JettyServerComponentOwns the Jetty lifecycle; participates as a MicroserviceListener.
JettyServerFactoryFactory used to build the Jetty Server from jetty.xml.
JettySettingsProgrammatic overrides for ports, jetty.xml content, and var resolution.
org.eclipse.jetty.server.ServerThe created Jetty server (after Microservice.start()).

Resolution priority​

Beans for these core types resolve in this order:

  1. Explicit builder call — e.g. .config(myConfig), .args(myArgs). Always wins.
  2. @Bean-supplied value — picked up if no builder call was made.
  3. Built-in default — typical fallbacks: empty Args, classpath META-INF/MANIFEST.MF, etc.

Once resolved, the final value is registered into the bean store, overwriting any @Bean contribution of the same (type, name). This means downstream consumers (other @Bean factory methods, subclasses) always see the same Args / Config / etc. that Microservice.getArgs() returns.

Discovering @Rest servlets in Jetty​

JettyServerComponent.onStart(...) walks the bean store for entries registered under Servlet.class, filters them to those whose runtime class carries @Rest, and mounts each one at the path declared in @Rest(path = "...") (falling back to / when no path is set).

@Rest(path = "/api")
public class ApiServlet extends RestServlet { /* ... */ }

@Configuration
public class AppConfig {
@Bean
Servlet apiServlet() { return new ApiServlet(); } // mounted automatically at /api/*
}

public class App {
public static void main(String[] args) throws Exception {
Microservice.create()
.args(args)
.configurations(JettyConfiguration.class, AppConfig.class)
.build()
.start()
.join();
}
}

Note on declared return types — Juneau's BeanStore.getBeansOfType(Class) is exact-type (not assignable-to). Declare your @Bean method return type as Servlet (not the concrete subclass) so the bean is registered under Servlet.class and visible to the auto-mount logic.

Path collisions are a hard startup failure​

Mounting two servlets at the same pathspec — whether from Jetty/servlets, Jetty/servletMap, or @Bean discovery — raises a RuntimeException from Microservice.start() with a message identifying both contributors:

Servlet mount path collision: '/api/*' is already mounted by @Bean ApiServletA;
refused by @Bean ApiServletB[apiServletDup].

Bypassing jetty.xml entirely​

If your @Configuration provides a @Bean Server whose attribute "ServletContextHandler" is set, JettyServerComponent will skip the jetty.xml factory step and use that server directly:

@Configuration
public class JettyConfig {

@Bean
public Server jettyServer() {
var server = new Server();
var ctx = new ServletContextHandler();
ctx.setContextPath("/");
server.setAttribute("ServletContextHandler", ctx);
server.setHandler(ctx);
return server;
}
}

This is useful in tests and in programs that want fully-code-driven Jetty configuration.

Lifecycle: @PostConstruct and @PreDestroy​

@PostConstruct callbacks fire automatically on any bean instantiated through the inject framework (e.g. when a @Bean method constructs a class via constructor injection, or when BeanStore.instantiate(X) is used). Note that beans you return new MyBean() directly from a @Bean method do not pass through that path and will not receive a @PostConstruct callback for the returned instance.

@PreDestroy callbacks fire on Microservice.stop(), which closes the bean store and walks every resolved bean (anything that was retrieved via getBean / getBeansOfType at least once) in LIFO order. Errors thrown from @PreDestroy are logged at WARNING level and do not abort the rest of the shutdown sequence.

External bean stores​

Microservice.Builder.beanStore(WritableBeanStore) lets you supply an externally-owned store rather than letting the microservice construct a fresh one. This is the integration point for composing a microservice into a larger application that already owns a bean store, including the Spring-application-context bridge provided by juneau-rest-server-springboot.

WritableBeanStore parent = new BasicBeanStore();
parent.addBean(MyService.class, sharedService);

Microservice.create()
.beanStore(parent)
.configurations(AppConfig.class)
.build()
.start();

Back-compat​

The inject-aware bootstrap is fully additive:

  • Every existing builder method continues to work unchanged.
  • Microservices that never call .configurations(...) get an empty bean store and behave identically to pre-10.0.0 behavior.
  • The Microservice field-resolution order (builder > bean store > default) means explicit builder calls always win, so existing apps cannot be silently altered by an injected @Bean Config or @Bean Args.