Launcher
Overview
Launcher is essentially an abstracted and generalized implementation of the main()
method.
It integrates modules and services together and manages the lifecycle of the application.
Features
- Perfect compatibility with ActiveJ Inject
- Can be combined with the Service Graph to support start/stop of independent services
- Provides the ability to gracefully shutdown services
- Takes care of full application lifecycle and logging
Launcher in a nutshell
Launcher
is a very explicit tool. You can understand how it just by looking at the implementation of its main launch()
method 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 an
Injector
using theModules
provided inLauncher
- Inject top-level dependencies into the
Launcher
instance itself - Instantiate all keys marked as
@Eager
, exported byLauncher’s Modules
- Start all services from the
Set<LauncherService>
multibinder set, exported by Launcher’s Modules, and execute theonStart()
method - Execute
run()
method - When
run()
is finished (either by finishing all app-specific computations or in response to a shutdown request such as SIGKILL), execute theonStop()
method and stop all services
Here is an example of the Launcher
lifecycle represented as logs (particularly, HttpServerLauncher
subclass that provides an HttpServer
, an Eventloop
and Config
). Launch this example:
public final class HttpHelloWorldExample extends HttpServerLauncher {
@Provides
AsyncServlet servlet() {
return request -> HttpResponse.ok200()
.withPlainText("Hello World")
.toPromise();
}
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]: HttpServer{listenAddresses=[/0.0.0.0:8080]}
Started io.active.net.http.HttpServer
=== 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
keygroupSet<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<>() {};
return ModuleBuilder.create()
.bind(String[].class, Args.class).toInstance(args)
.bind(Launcher.class).to(launcherClass)
.bind(launcherClass).toInstance(this)
.bind(completionStageKey.qualified(OnStart.class)).toInstance(onStartFuture)
.bind(completionStageKey.qualified(OnRun.class)).toInstance(onRunFuture)
.bind(completionStageKey.qualified(OnComplete.class)).toInstance(onCompleteFuture)
.scan(Launcher.this)
.build();
}
// 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 itsLauncher.launch(String[] args)
method, corresponding tomain(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 whenLauncher.run()
is complete.@OnComplete
CompletionStage<Void>
future which is completed when application is stopped
Launcher
also has convenient diagnostic and JMX features:
- Instant of launch, start, run, stop, and complete
- Duration of start, run, stop, and total duration
- Throwable applicationError property
More examples
To run the examples, you need to clone ActiveJ from GitHub
git clone https://github.com/activej/activej
And import it as a Maven project. Check out tag v6.0-beta2. Before running the examples, build the project.
These examples are located at activej/examples/core/datastream
Hello World Example
Here is a simple "Hello World" Launcher application using ActiveJ Inject:
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 a simple Launcher instead. In this case, you will have to take care of all the required dependencies and override some basic methods:
public final class CustomHttpServerExample extends Launcher {
private static final int PORT = 8080;
@Provides
NioReactor reactor() {
return Eventloop.create();
}
@Provides
AsyncServlet servlet() {
return request -> HttpResponse.ok200()
.withPlainText("Hello from HTTP server")
.toPromise();
}
@Provides
@Eager
HttpServer server(NioReactor reactor, AsyncServlet servlet) {
return HttpServer.builder(reactor, servlet)
.withListenPort(PORT)
.build();
}
@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 {
Launcher launcher = new CustomHttpServerExample();
launcher.launch(args);
}
}
Working with program arguments
To access program arguments from the Launcher
, you have to pass those arguments to Launcher#launch
method.
Then there are two ways to access the arguments:
- Using
Launcher
'sString[] args
field - Requesting arguments using Dependency Injection. A key is
String[].class
annotated with@Args
annotation. The following example illustrates both cases:
public final class LauncherArgsExample extends Launcher {
@Inject
Injector injector;
@Override
protected void run() {
System.out.println("Received args: " + Arrays.toString(args));
String[] injectedArgs = injector.getInstance(Key.of(String[].class, Args.class));
System.out.println("Args retrieved from DI: " + Arrays.toString(injectedArgs));
}
public static void main(String[] args) throws Exception {
new LauncherArgsExample().launch(args);
}
}