Docs / Javalin
DInject has moved to avaje.io/http - taking you there now ...
Javalin controllers
javalin-generator is an APT processor that allows us to write controllers
with annotations like @Path
and @Get
Goals
- Provide a similar programming style to JAX-RS and Spring
- Add no extra weight. We generate source code - no reflection, no extra overhead
- Generate code that we would otherwise write ourselves
- Controllers should be nice and readable - not overly littered with annotations
- Expose and use Javalin Context as needed
- Automatically generate Swagger/OpenAPI documentation
- Support using Bean validation on request payloads
In general our controllers should be more testable with dependencies that can be mocked or stubbed and with controller methods having less dependency on 'web constructs' like path, query and form parameters (we can more easily unit test controllers).
Examples
Example projects found in dinject/examples for Gradle and Maven for both Java and Kotlin.
Introduction
Introduction to using javalin-generator
to generate web routes for Javalin.
Example
For the controller below:
@Controller
@Path("/customers")
class CustomerController {
final MyDependency myDependency;
CustomerController(MyDependency myDependency) {
this.myDependency = myDependency;
}
@Get(":id")
Customer getById(UUID id) {
...
}
@Roles(AppRoles.ADMIN)
@Get("/type/:type")
List<Customer> findByType(String type, String orderBy) {
...
}
}
The javalin-generator
generates the code below.
- A bean annotated with
@Singleton
(A) ... picked up for Dependency injection - A bean that implements
WebRoutes
(B) ... so we can register them all with Javalin - Registers the web routes to Javalin ApiBuilder (C)
- Obtains path parameters and query parameters (D)
- Passes the path and query parameters to the controller methods (E)
- Converts to and from JSON the controller method response or body (F)
- Adapter @Roles for the web route (G)
@Generated("io.dinject.javalin.generator")
@Singleton // (A)
public class CustomerController$route implements WebRoutes { // (B)
private final CustomerController controller;
public CustomerController$route(CustomerController controller) {
this.controller = controller;
}
@Override
public void registerRoutes() {
ApiBuilder.get("/customers/:id", ctx -> { // (C)
ctx.status(200);
UUID id = asUUID(ctx.pathParam("id")); // (D)
ctx.json(controller.getById(id)); // (E), (F)
});
ApiBuilder.get("/customers/type/:type", ctx -> { // (C)
ctx.status(200);
String type = ctx.pathParam("type"); // (D)
String orderBy = ctx.queryParam("orderBy"); // (D)
ctx.json(controller.findByType(type, orderBy)); // (E), (F)
}, roles(ADMIN)); // (E)
}
}
As the generated bean is a @Singleton
DInject puts it into
the DI context. All these generated beans implement WebRoutes
so we can get them all out of the DI context to register them with Javalin.
When can register all the WebRoutes
with Javalin.
public static void main(String[] args) {
Javalin app = Javalin.create().disableStartupBanner();
// get all WebRoutes from DI Context
List<WebRoutes> webRoutes = SystemContext.getBeans(WebRoutes.class);
// register all WebRoutes with Javalin
app.routes(() -> webRoutes.forEach(WebRoutes::registerRoutes));
...
// start Javalin
app.start(8080);
}
Dependency injection
The natural way to use the generated "Javalin controller adapters" is to get a DI library to find and wire them. This is what the examples do and they use DInject to do this.
Note that there isn't a strict requirement to use DInject for dependency injection. Any DI library that can find and wire the @Singleton bean generated could be used. We could use Dagger2 or Guice to wire the controllers if we felt that was a better option (but yes I think DInject is the better option for DI and that is what I'd be recommending).