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 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 DEPENDENCIESCreated eager singletonsPost-injected instances: [HttpHelloWorldExample]
=== STARTING APPLICATIONStarting LauncherServices: [io.active.net.jmx.JmxModule, io.active.net.service.ServiceGraphModule]Starting LauncherService: io.active.net.jmx.JmxModuleStarting LauncherService: io.active.net.service.ServiceGraphModuleCreating ServiceGraph...Starting servicesListening on [/0.0.0.0:8080]: AsyncHttpServer{listenAddresses=[/0.0.0.0:8080]}Started io.active.net.http.AsyncHttpServer
=== RUNNING APPLICATIONHTTP Server is listening on http://localhost:8080/
=== STOPPING APPLICATIONStopping LauncherService: io.active.net.jmx.JmxModuleStopping LauncherService: io.active.net.service.ServiceGraphModuleStopping 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<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 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
note
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 v5.3. 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 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 { 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); }}