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(...).
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:
| Type | Notes |
|---|---|
| Microservice | The microservice instance itself. |
| Args | Resolved CLI args. |
| ManifestFile | Resolved manifest. |
| Config | Resolved configuration. |
| VarResolver | Resolved var resolver with $A, $M, $C and standard vars wired up. |
| MicroserviceListener | Either the builder-supplied listener, a @Bean-supplied one, or the default. |
When JettyConfiguration.class is added to configurations(...) the store additionally contains:
| Type | Notes |
|---|---|
| JettyServerComponent | Owns the Jetty lifecycle; participates as a MicroserviceListener. |
| JettyServerFactory | Factory used to build the Jetty Server from jetty.xml. |
| JettySettings | Programmatic overrides for ports, jetty.xml content, and var resolution. |
org.eclipse.jetty.server.Server | The created Jetty server (after Microservice.start()). |
Resolution priority
Beans for these core types resolve in this order:
- Explicit builder call — e.g.
.config(myConfig),.args(myArgs). Always wins. @Bean-supplied value — picked up if no builder call was made.- Built-in default — typical fallbacks: empty
Args, classpathMETA-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@Beanmethod return type asServlet(not the concrete subclass) so the bean is registered underServlet.classand 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
Microservicefield-resolution order (builder > bean store > default) means explicit builder calls always win, so existing apps cannot be silently altered by an injected@Bean Configor@Bean Args.