Spring 引导组件扫描上下文

Spring Boot Component Scan Context

我有 10 多个 Spring 引导应用程序,它们可以构建为 war 文件并部署在应用程序服务器中,或者 运行 单独作为独立应用程序。每个应用程序都包含独特的代码、功能和业务运营所需的 RESTful 服务。

我的包结构如下:

WebServices (Gradle project)
|---A (Gradle project)
|---B (Gradle project)
|---C (Gradle project)

服务 A、B 和 C 都已打包,并且 运行可作为 wars。

但是,我还想提供启动一个包含所有服务的 "large" 服务器的选项,而不必单独启动每个应用程序或使主应用程序服务器因大型 Spring 开机 wars.

理想情况下,这将通过另一个 Spring 启动应用程序,该应用程序利用 ComponentScan 来包含来自所有其他服务的功能。我打算让 Spring 引导应用程序 X 引用服务 AB[=53= 中的功能], 和 C.

这个问题与上下文有关。我的每项服务在通过应用程序服务器启动时,都会根据 war 文件的名称自动分配一个上下文。例如,如果我在应用程序 A 中具有安全功能,因为它包含我登录时使用的敏感信息:

/security/login

其中 security 是 war 文件 (security.war) 的名称。

为了在 运行 独立时弥补这一点,我为上下文设置了一个应用程序 属性 以匹配 war 文件名 server.servlet.context-path: /security。这使我能够维持使用我的任一部署方法部署的相同端点天气。

现在,当我启动引用项目 ABX 的服务器时, C 与组件扫描 @ComponentScan(basePackages = {"com.package.a.*", "com.package.b.*", "com.package.c.*"}) 我在应用程序 A 中丢失了 security 的上下文并且我的端点现在被访问为:

/login

应用程序 ABC 之间没有区别。

所以,我的问题是如何根据被扫描的组件维护单独的上下文,甚至基于上下文路由?

由于 spring 引导并不是真正针对您的要求而设计的,因此您必须非常有创意。以下是一些说明:

选项 1

只要你在同一台服务器上部署了很多 wars,contextPaths 就会不同,你可以根据实际服务器使用它们,但它们必须不同,以便网络服务器可以以某种方式区分它们。 考虑到这一点,您可以 运行 您想要做的事情,然后定义一个代理(Zuul、ha-proxy 或其他),它会仔细地将每个可能的 url 映射到适当的服务器。

选项 2

与 1 几乎相同,但您可以使用 docker-compose(甚至 kubernetes)和 运行 spring 启动应用程序作为不同的容器。如果您想在 CI 或开发中 运行 (docker compose 将根据创建的 war 文件的位置为您构建所有内容,这可以说是一个更好的解决方案,无需移动到 Web 服务器、部署服务器等)。只是说这是一个选项。

选项 3

如果您真的希望它们都在同一个内容路径中。创建一个 gradle 插件,将 wars / jars 合并为一个。胖罐风格。请注意,spring 启动应用程序可能具有不同的布局(对于 Jars 来说肯定是这样),因此实现可能应该像这样:

步骤 1

插件假定模块 A、B、C 已构建。现在我没有使用 Gradle 的经验,但在 Maven 中你可以转到目标目录(我相信 gradle 有一个用于相同目的的构建目录)并提取 war/jar (类, resources,dependencies) 到另一个文件夹,比如项目 A,然后对项目 B 执行相同的操作,依此类推。依赖项可能会发生冲突(不同的版本,因此您必须确保所有应用程序都使用相同版本的依赖项来消除问题),类 不应发生冲突,因为它们应该位于不同的包中(com.package.a 而不是 com.package.b)。

步骤 2

在步骤 "a" 之后,您必须将所有内容打包到您选择的工件中,它应该仍然是一个 spring 引导项目。如果您选择 JAR,则必须了解它的构建方式。我无法对 WAR 发表评论,还没有与 spring boot.

中的那些人合作过

选项 4

与选项 3 几乎相同,但步骤 1 是在它构建的真实项目之一(如项目 A、B 等)时完成的。该插件会将生成的 类、依赖项或其他内容复制到某个预定义的文件夹中。该插件将安装在每个模块中,因此当它 运行 时,它将 "contribute" 到包含所有必需资源的文件夹中。步骤 2 与选项 3 相同。

更新

根据您的评论,考虑 Jars 和选项 1 / 2。Jars 是在 spring 引导应用程序中工作的推荐方式,wars 应该用于维护的老式组织Web 服务器,不想做 "Ops" 更改。

现在,如果端口有问题,如果您使用容器/kubernetes 编排,还有一个选项可以为每个服务分配一个虚拟主机名,这样所有服务都可以在同一个端口上访问,但可以使用虚拟主机姓名。代理选项将允许所有端口和主机相同

我用另一种方式解决了这个问题。

我在每个单独的控制器上设置了一个默认值 RequestMapping("${serviceContext}")。每个控制器的 service 发生变化。即 aContextbContextcContext.

在我已经被上下文绑定的环境中,在一个应用程序服务器中并且通过 application.yml 独立,这个 属性 没有设置并且导致已经存在的绑定。

在我的捆绑应用程序中,我能够保留 @ComponentScan(basePackages = {"com.package.a.*", "com.package.b.*", "com.package.c.*"}) 的组件扫描。然后更改变成在应用程序启动期间添加其他属性。

Properties used to bind context

Properties properties = new Properties();
properties.put("aContext", "/security");
properties.put("bContext", "/b");
properties.put("cContext", "/c");

我在 运行 应用程序之前将其添加到 Spring 的 setDefaultProperties。然后当我将每个作为一个包启动时,我得到了预期的上下文绑定。