Javalin 与 Java 10 和 Google Guice

您将学到什么

在本教程中,我们将学习如何在 Javalin 之上创建模块化应用程序。

我们将使用Google Guice实现模块化,并使用Java 10来完成 Java 10 的事情:

var amazingFramework = "Javalin"; // java10
// vs
String amazingFramework = "Javalin"; // not java10

依赖项

让我们创建一个包含依赖项的 Maven 项目(→ 教程)。我们将使用 Javalin 作为 Web 服务器,使用 slf4j 进行日志记录,使用 jackson 将响应渲染为 JSON,并使用 Guice 进行依赖注入:

<dependencies>
    <dependency>
        <groupId>io.javalin</groupId>
        <artifactId>javalin</artifactId>
        <version>2.8.0</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>2.0.17</version>
    </dependency>
    <dependency>
        <groupId>com.google.inject</groupId>
        <artifactId>guice</artifactId>
        <version>4.2.0</version>
    </dependency>
    <dependency>
        <groupId>com.google.inject.extensions</groupId>
        <artifactId>guice-multibindings</artifactId>
        <version>4.2.0</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.9.5</version>
    </dependency>
</dependencies>

并添加 Java 10 的属性

<properties>
    <maven.compiler.source>10</maven.compiler.source>
    <maven.compiler.target>10</maven.compiler.target>
</properties>

高级架构

控制器
负责处理请求。如果你愿意,可以充当保镖或人脸识别,仅此而已
服务
实际的业务逻辑执行器,可能需要也可能不需要其他服务
存储库
与任何数据存储进行通信,仅此而已

Java 应用程序

首先,让我们在 io.kidbank.user 包中创建一个控制器。

UserController 负责处理请求,而业务逻辑由提供 UserService。

package io.kidbank.user;

import io.javalin.Context;
import io.kidbank.user.services.UserService;

import javax.inject.Inject;
import javax.inject.Singleton;

@Singleton
class UserController {
    private UserService userService;

    @Inject
    public UserController(UserService userService) {
        this.userService = userService;
    }

    public void index(Context ctx) {
        ctx.json(userService.getAllUsersUppercase());
    }
}

现在我们有了控制器,我们应该将端点绑定到UserController。该类Routing帮助我们从 Google Guice 解析UserController。它保证存在一个方法 bindRoutes(),我们稍后会用到它。

package io.kidbank.user;

import io.alzuma.Routing;
import io.javalin.Javalin;

import javax.inject.Inject;
import javax.inject.Singleton;

import static io.javalin.apibuilder.ApiBuilder.get;
import static io.javalin.apibuilder.ApiBuilder.path;

@Singleton
class UserRouting extends Routing<UserController> {
    private Javalin javalin;
    @Inject
    public UserRouting(Javalin javalin) {
        this.javalin = javalin;
    }

    @Override
    public void bindRoutes() {
        javalin.routes(() -> {
            path("api/kidbank/users", () -> {
                get(ctx -> getController().index(ctx));
            });
        });
    }
}

安装并绑定io.kidbank.user包的所有依赖项。

看一下 Multibinder,它是一个 Google Guice 扩展。这就是我们在应用程序中启用多个路由的方法。要添加更多路由,只需添加Multibinder.newSetBinder(...)

稍后我们将注入所有路由,以便将它们绑定到JavalinWeb 服务器中。

package io.kidbank.user;

import com.google.inject.AbstractModule;
import com.google.inject.multibindings.Multibinder;
import io.alzuma.Routing;
import io.kidbank.user.repositories.UserRepositoryModule;
import io.kidbank.user.services.UserServiceModule;

public class UserModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(UserController.class);
        install(new UserServiceModule());
        install(new UserRepositoryModule());
        Multibinder.newSetBinder(binder(), Routing.class).addBinding().to(UserRouting.class);
    }
}

绑定Javalin路由并启动 Web 服务器。这不是什么黑魔法,只是注入而已,记住这一点。

仔细看看private Set<Routing> routes。这是 Google Guice 注入所有Routes受 约束的内容的地方Multibinder。

记住,我们讨论的是Routing类及其提供的保证。基于此,我们可以bindRoutes() 在 中的每条记录上调用该方法Set<Routing>。然后,我们就Javalin用路由填充了。

package io.kidbank;

import com.google.inject.Inject;
import io.alzuma.AppEntrypoint;
import io.alzuma.Routing;
import io.javalin.Javalin;

import javax.inject.Singleton;
import java.util.Collections;
import java.util.Set;

@Singleton
class WebEntrypoint implements AppEntrypoint {
    private Javalin app;

    @Inject(optional = true)
    private Set<Routing> routes = Collections.emptySet();

    @Inject
    public WebEntrypoint(Javalin app) {
        this.app = app;
    }

    @Override
    public void boot(String[] args) {
        bindRoutes();
        app.port(7000);
        app.start();
    }

    private void bindRoutes() {
        routes.forEach(r -> r.bindRoutes());
    }
}

创建WebModule我们的Kid bank项目。在模块内部,我们定义项目“以 Web 服务器身份运行”。

现在我们将使用MapBinder。它类似于我们使用,但我们使用Routing而不是,这样我们就可以将多个“运行方式”存储
MultibinderMapBinderHashMap<EntrypointType, AppEntrypoint>

package io.kidbank;

import com.google.inject.AbstractModule;
import com.google.inject.multibindings.MapBinder;
import io.alzuma.AppEntrypoint;
import io.alzuma.EntrypointType;
import io.javalin.Javalin;
import org.jetbrains.annotations.NotNull;

class WebModule extends AbstractModule {
    private Javalin app;

    private WebModule(Javalin app) {
        this.app = app;
    }

    @NotNull
    public static WebModule create() {
        return new WebModule(Javalin.create());
    }

    @Override
    protected void configure() {
        bind(Javalin.class).toInstance(app);
        MapBinder.newMapBinder(binder(), EntrypointType.class, AppEntrypoint.class).addBinding(EntrypointType.REST).to(WebEntrypoint.class);
    }
}

我们需要某种解析器来决定执行哪个“Run as”。为此,我们创建了一个类Startup并注入了所有可能的入口点。


import com.google.inject.Inject;
import io.alzuma.AppEntrypoint;
import io.alzuma.EntrypointType;

import javax.inject.Singleton;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;

@Singleton
public class Startup {
    @Inject(optional = true)
    private Map<EntrypointType, AppEntrypoint> entrypoints = Collections.emptyMap();

    public void boot(EntrypointType entrypointType, String[] args) {
        var entryPoint = Optional.ofNullable(entrypoints.get(entrypointType));
        entryPoint.orElseThrow(() -> new RuntimeException("Entrypoint not defined")).boot(args);
    }
}

至于我们的最后一个模块,我们定义AppModule。我们在哪里安装我们的项目模块。

import com.google.inject.AbstractModule;
import io.kidbank.KidBankModule;

public class AppModule extends AbstractModule {
    protected void configure() {
        bind(Startup.class);
        install(new KidBankModule());
    }
}

现在我们准备启动我们的网络服务器。

创建注入器,用于AppModule触发路径下的所有绑定和安装。Startup使用 Javalin 解析并启动 REST。

public class App {
    public static void main(String[] args) {
        var injector = Guice.createInjector(new AppModule());
        injector.getInstance(Startup.class).boot(EntrypointType.REST, args);
    }
}

在浏览器中打开 http://localhost:7000/api/kidbank/users 并等待响应[“BOB”,”KATE”,”JOHN”]

结论

我们创建了模块化应用程序,它不仅能够作为 Web 服务器自行运行。
习惯 Guice 模块需要时间,但是一旦你习惯了,天空就是你的极限!
最重要的部分。玩得开心!

作者:Jeebiz  创建时间:2025-05-04 00:37
最后编辑:Jeebiz  更新时间:2025-05-04 00:55