Launcher
Features
- Launcher is essentially a highly abstracted and generalized implementation of main() methods, combined with Service Graph, and support of start/stop semantics
- Takes care of full application lifecycle and logging
- Perfectly compatible with ActiveInject
Launcher is a very explicit tool. You can understand how it works by simply looking at the implementation of its main method launch in a nutshell:
public void launch(String[] args) throws Exception {
logger.info("=== INJECTING DEPENDENCIES");
Injector injector = createInjector(args);
logger.info("Eager instances: " + injector.createEagerInstances());
Set<LauncherService> services = injector.getInstance(new Key<Set<LauncherService>>() {});
Set<LauncherService> startedServices = new HashSet<>();
logger.info("Post-inject instances: " + injector.postInjectInstances());
logger.info("=== STARTING APPLICATION");
logger.info("Starting LauncherServices: " + services);
startServices(services, startedServices);
onStart();
onStartFuture.complete(null);
logger.info("=== RUNNING APPLICATION");
run();
onRunFuture.complete(null);
logger.info("=== STOPPING APPLICATION");
onStop();
stopServices(startedServices);
onCompleteFuture.complete(null);
}
Step-by-step examination:
- Create the Injector with the Modules provided in Launcher
- Inject top-level dependencies into the Launcher instance itself
- Instantiate all keys marked as @Eager multibinder keygroup, exported by Launcher’s Modules
- Start all services from Set<LauncherService> multibinder set, exported by Launcher’s Modules, and execute onStart() method
- Execute run() method
- After run() is finished (either due to finishing all app-specific computations or by responding to a shutdown request such as SIGKILL), execute onStop() method and stop all services
Here is an example of Launcher lifecycle represented as logs (particularly, HttpServerLauncher subclass that provides an AsyncHttpServer, an Eventloop and Config). Launch this example:
public final class HttpHelloWorldExample extends HttpServerLauncher {
@Provides
AsyncServlet servlet() {
return request -> HttpResponse.ok200().withPlainText("Hello World");
}
public static void main(String[] args) throws Exception {
Launcher launcher = new HttpHelloWorldExample();
launcher.launch(args);
}
}
to see alike logs:
=== INJECTING DEPENDENCIES
Created eager singletons
Post-injected instances: [HttpHelloWorldExample]
=== STARTING APPLICATION
Starting LauncherServices: [io.active.net.jmx.JmxModule, io.active.net.service.ServiceGraphModule]
Starting LauncherService: io.active.net.jmx.JmxModule
Starting LauncherService: io.active.net.service.ServiceGraphModule
Creating ServiceGraph...
Starting services
Listening on [/0.0.0.0:8080]: AsyncHttpServer{listenAddresses=[/0.0.0.0:8080]}
Started io.active.net.http.AsyncHttpServer
=== RUNNING APPLICATION
HTTP Server is listening on http://localhost:8080/
=== STOPPING APPLICATION
Stopping LauncherService: io.active.net.jmx.JmxModule
Stopping LauncherService: io.active.net.service.ServiceGraphModule
Stopping ServiceGraph...
Stopping services
- Launcher optionally requires the following dependencies from its Modules:
- @Eager multibinder keygroup
- Set<LauncherService> multibinder set
Launcher exports the following dependencies to its Modules:
class Launcher{
@NotNull
public final Injector createInjector() {
return Injector.of(getInternalModule()
.combineWith(getModule())
.overrideWith(getOverrideModule()));
}
private Module getInternalModule() {
Class<Launcher> launcherClass = (Class<Launcher>) getClass();
Key<CompletionStage<Void>> completionStageKey = new Key<CompletionStage<Void>>() {};
return Module.create()
.bind(String[].class).annotatedWith(Args.class).toInstance(args)
.bind(Launcher.class).to(launcherClass)
.bind(launcherClass).toInstance(this)
.postInjectInto(launcherClass)
.bind(completionStageKey.named(OnStart.class)).toInstance(onStartFuture)
.bind(completionStageKey.named(OnRun.class)).toInstance(onRunFuture)
.bind(completionStageKey.named(OnComplete.class)).toInstance(onCompleteFuture)
.scan(Launcher.this);
}
// this method can be overridden by subclasses which extend Launcher,
// provides business logic modules (for example, ConfigModule)
protected Module getModule() {
return Module.empty();
}
// this method can be overridden by subclasses which extend Launcher,
// overrides definitions in internal module
protected Module getOverrideModule() {
return Module.empty();
}
}
- @Args String[] arguments of its Launcher.launch(String[] args) method, corresponding to main(String[] args) method
- The Launcher instance itself
- @OnStart CompletionStage<Void> future which is completed when application is wired and started
- @OnRun CompletionStage<Void> future which is completed when Launcher.run() is complete.
- @OnComplete CompletionStage<Void> future which is completed when application is stopped
Launcher also has handy diagnostic and JMX properties:
- Instant of launch, start, run, stop, and complete
- Duration of start, run, stop, and total duration
- Throwable applicationError property
More examples
$ git clone https://github.com/activej/activej
And import it as a Maven project. Check out tag v4.0-beta1. Before running the examples, build the project.
The two following examples are located at activej -> examples -> core -> boot and activej -> examples -> core -> http respectively
Hello World Example
Here is a simple “Hello World” Launcher application using ActiveInject:
public final class HelloWorldExample extends Launcher {
@Inject
String message;
@Provides
String message() {
return "Hello, world!";
}
@Override
protected void run() {
System.out.println(message);
}
public static void main(String[] args) throws Exception {
Launcher launcher = new HelloWorldExample();
launcher.launch(args);
}
}
HTTP Server from scratch using Launcher
When creating HTTP servers or any other applications, you can either use some predefined solutions (see examples) or simple Launcher instead. In this case, you’ll need to take care of all of the needed dependencies and override some basic methods:
public final class CustomHttpServerExample extends Launcher {
private static final int PORT = 8080;
@Provides
Eventloop eventloop() {
return Eventloop.create();
}
@Provides
AsyncServlet servlet() {
return request -> HttpResponse.ok200()
.withPlainText("Hello from HTTP server");
}
@Provides
@Eager
AsyncHttpServer server(Eventloop eventloop, AsyncServlet servlet) {
return AsyncHttpServer.create(eventloop, servlet).withListenPort(PORT);
}
@Override
protected Module getModule() {
return ServiceGraphModule.create();
}
@Override
protected void run() throws Exception {
logger.info("HTTP Server is now available at http://localhost:" + PORT);
awaitShutdown();
}
public static void main(String[] args) throws Exception {
Injector.useSpecializer();
Launcher launcher = new CustomHttpServerExample();
launcher.launch(args);
}
}