microBean Helidon WebServer CDI
Overview
This project consists of a CDI portable extension that embeds a Helidon WebServer
into a CDI environment and starts it.
The portable extension will look for any Service
class it can find that is detectable. It will instantiate it and call its update(Routing.Rules)
method prior to starting the WebServer
.
Why Would I Want To Use This?
You may be thinking: why would I want to put a web server in a CDI container? This is probably because you are thinking of CDI as a component of a Java EE application server. But CDI 2.0 is usable in Java SE.
If you start the CDI container from your own main
method like so:
try (SeContainer container = SeContainerInitializer.newInstance().initialize()) {
// deliberately empty
}
...or, equivalently, use the microbean-main
project's Main
class to do this for you, then a CDI container comes up and goes down. In between all of your CDI beans are instantiated and wired appropriately.
So don't think of a CDI container as something that lives inside a web server or alongside one. Think instead of a web server as just another Java component in the CDI ecosystem.
At this point you can probably see that if Helidon SE is just another component in the CDI ecosystem, and your Service
implementations are just another component in the CDI ecosystem, then the CDI container can instantiate and autowire all of them equivalently.
This gives you all the benefits of Helidon SE and all the benefits of CDI at the expense of roughly 50 milliseconds of additional startup time while letting you write a simple Java SE application from a component-oriented perspective.
Simple Example
Suppose you write a Service
like this:
import io.helidon.webserver.Service;
import io.helidon.webserver.Routing;
import io.helidon.webserver.ServerRequest;
import io.helidon.webserver.ServerResponse;
@ApplicationScoped
public class MyService implements Service {
private final SomeClientForSomething client;
@Inject
public MyService(final SomeClientForSomething client) {
super();
this.client = Objects.requireNonNull(client);
}
@Override
public void update(final Routing.Rules rules) {
rules.get("/foo", this::handleFoo); // handle GET requests with the handleFoo method
}
private void handleFoo(final ServerRequest request,
final ServerResponse response) {
// You can use this.client here.
}
}
The presence of this class on the classpath will cause the CDI container to create an instance of MyService
, supply it with an instance of SomeClientForSomething
assuming that this bean is present in the CDI container, and call its update
method. A Helidon WebServer
will then be created and started using default configuration. The webserver will stop when it receives a CTRL-C
signal.
Customization
All intermediate objects required to create and start a WebServer
are treated as CDI beans as well, with the end user's versions of those beans (if any) being preferred over the built-in defaults. So, for example, if you were to supply your own WebServer
producer method, that WebServer
instance would be used instead.
If you do nothing, a default Config.Builder
will be created to build a Config
object. The Config
object so built will be used to create a ServerConfiguration.Builder
, which, in turn, will be used to build a ServerConfiguration
. This general pattern applies all the way up to the WebServer
itself (built by a WebServer.Builder
).
Each of these objects is treated as a CDI bean. If the CDI bean in question does not exist, then a "normal" instance of the object in question is registered as a bean and instantiated appropriately by the CDI container.
In the case of such default instantiations, you can customize them if you want. For example, maybe you don't want to actually supply an alternate Config
implementation—the default one is just fine—but you do want to slightly change how it's made. To perform simple customization of this sort, you declare an observer method that receives the relevant Builder
object as its observed parameter. For example, somewhere in your CDI application, you can write:
private static final void customizeConfigBuilder(@Observes final Config.Builder configBuilder) {
builder.disableSystemPropertiesSource(); // or whatever
}
The Builder
so customized will be used internally by this project to produce Config
objects (as CDI beans) that may be required by other Helidon objects.