MeteorCat / Fortress 2 - 功能初始化

Created Wed, 27 Aug 2025 20:06:01 +0800 Modified Wed, 29 Oct 2025 23:24:54 +0800

功能初始化

这里首先需要配置 common 子项目的功能, 一切基于 protobuf 生成的代码也是由 common 项目构建生成.

pom.xml 的配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <!-- 父依赖 -->
    <parent>
        <groupId>io.fortress</groupId>
        <artifactId>boot</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../../pom.xml</relativePath>
    </parent>

    <!-- 子属性 -->
    <modelVersion>4.0.0</modelVersion>
    <artifactId>fortress-common</artifactId>
    <name>Fortress Common Module</name>
    <description>Fortress utilities and models</description>
    <packaging>jar</packaging>

    <!-- 第三方依赖 -->
    <dependencies>

        <!-- 通用字符串、集合等工具类  -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>


        <!-- 通用编码哈希工具 -->
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
        </dependency>


        <!-- Quarkus 核心依赖 -->
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-core</artifactId>
        </dependency>

        <!-- Quarkus 容器依赖 -->
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-arc</artifactId>
        </dependency>


        <!-- Pekko 核心依赖 -->
        <dependency>
            <groupId>org.apache.pekko</groupId>
            <artifactId>pekko-actor_${pekko.platform.scala-version}</artifactId>
        </dependency>

        <!-- Pekko 日志依赖 -->
        <dependency>
            <groupId>org.apache.pekko</groupId>
            <artifactId>pekko-slf4j_${pekko.platform.scala-version}</artifactId>
        </dependency>


        <!-- Pekko 序列化依赖 -->
        <dependency>
            <groupId>org.apache.pekko</groupId>
            <artifactId>pekko-remote_${pekko.platform.scala-version}</artifactId>
        </dependency>


        <!-- Pekko 集群依赖 -->
        <dependency>
            <groupId>org.apache.pekko</groupId>
            <artifactId>pekko-cluster-sharding_${pekko.platform.scala-version}</artifactId>
        </dependency>

        <!-- Protobuf 核心依赖 -->
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
        </dependency>


        <!-- Quarkus 测试依赖 -->
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-junit5</artifactId>
            <scope>test</scope>
        </dependency>


        <!-- Pekko 测试依赖 -->
        <dependency>
            <groupId>org.apache.pekko</groupId>
            <artifactId>pekko-testkit_${pekko.platform.scala-version}</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- Rest 测试依赖 -->
        <dependency>
            <groupId>io.rest-assured</groupId>
            <artifactId>rest-assured</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>


    <!-- 扩展打包编译配置 -->
    <build>
        <plugins>

            <!-- Protobuf 打包插件 -->
            <!-- https://www.xolstice.org/protobuf-maven-plugin/compile-mojo.html -->
            <plugin>
                <groupId>${protobuf.plugin.group-id}</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>${protobuf.plugin.version}</version>
                <extensions>true</extensions>
                <configuration>
                    <!-- Protobuf编译器版本 -->
                    <protocArtifact>
                        ${protobuf.compiler.group-id}:${protobuf.compiler.artifact-id}:${protobuf.compiler.version}:exe:${os.detected.classifier}
                    </protocArtifact>

                    <!-- *.proto 源文件路径, 直接默认即可, 系统事件不需要其他共享 -->
                    <!-- <protoSourceRoot>${project.parent.basedir}/proto</protoSourceRoot>-->

                    <!-- 生成Java文件, 直接默认就行, IDEA会自动识别到这部分代码 -->
                    <!--<outputDirectory>${project.basedir}/src/main/java</outputDirectory>-->

                    <!-- 是否生成之前清空目录, 最好不要随便乱动 -->
                    <clearOutputDirectory>false</clearOutputDirectory>
                </configuration>

                <!-- 执行时机 -->
                <executions>
                    <!-- 执行mvn compile的时候打包生成 Java 文件 -->
                    <execution>
                        <id>compile</id>
                        <phase>compile</phase>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                    </execution>

                    <!-- boot 调用 generate-code 的时候打包生成 Java 文件 -->
                    <execution>
                        <id>generate-resources</id>
                        <phase>generate-resources</phase>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>


        </plugins>
    </build>
</project>

这里编写针对 pekko 生成 ActorSystem 的单元测试:

package io.fortress.common;

import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import org.apache.pekko.actor.ActorSystem;

/**
 * io.fortress.common.ActorSystemTests.java
 * <p>
 * ActorSystem测试单元
 */
@QuarkusTest
public class ActorSystemTests {

    /**
     * 手动创建 Actor 系统
     */
    @Test
    public void createActor() {
        // 手动加载系统配置
        // 具体依旧引入 pekko 包配置可以查看
        // 如果引入 cluster, 则是 # 参考: pekko-cluster_2.13-1.1.5.jar!\reference.conf 内部配置重载
        Config config = ConfigFactory.defaultReference();
        Assert.assertNotNull(config);

        // 又或者直接采用字符串解析
        Config ignore = ConfigFactory.parseString("""
                  pekko {
                    loglevel = "DEBUG"  # 日志级别
                    actor {
                      provider = "local"  # 使用本地Actor提供者
                      default-dispatcher {
                        fork-join-executor {
                          parallelism-min = 2  # 线程池最小线程数
                          parallelism-max = 10 # 线程池最大线程数
                        }
                      }
                    }
                  }
                """);


        // 创建 Actor 系统, 传入自定义配置
        ActorSystem system = ActorSystem.create("ActorSystem", config);

        // 设定 Actor 系统回调
        system.whenTerminated().onComplete(terminatedTry -> {
            // 退出成功
            Assert.assertNotNull(terminatedTry.isSuccess());
            return null;
        }, system.dispatcher());

        // 执行 terminate 并且添加退出之后回调
        system.terminate().onComplete(terminatedTry -> {
            // 退出成功
            Assert.assertNotNull(terminatedTry.isSuccess());
            return null;
        }, system.dispatcher());
    }
}

但是作为系统通用集成库的 common 可能没办法接受这种冗余的定义方式, 需要改动成外部 application.properties 这类配置下就能生成全局可用的 Actor 系统, 所以这里就定义通用配置接口类就行了:

package io.fortress.common.config;

import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import io.smallrye.config.WithDefault;
import org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.actor.Terminated;
import scala.util.Try;

import java.util.Map;
import java.util.function.Function;

/**
 * io.fortress.common.config.ActorSystemConfiguration.java
 * <p>
 * 加载 Actor 系统配置, 用于传递给 pekko 的配置项
 */
@SuppressWarnings("unused")
public interface ActorSystemConfiguration {

    /**
     * ConfigFactory 加载的默认配置, 对应加载的配置
     * - ConfigFactory.defaultReference()
     * - ConfigFactory.defaultApplication()
     * - ConfigFactory.defaultOverrides()
     * - ConfigFactory.defaultReferenceUnresolved()
     */
    enum ActorReferences {
        Reference, // 默认配置: ConfigFactory.defaultReference()
        Application, // 应用配置: ConfigFactory.defaultReference()
        Overrides, // 重载配置: ConfigFactory.defaultOverrides()
        ReferenceUnresolved, // 未知: ConfigFactory.defaultReferenceUnresolved()
    }

    /**
     * ConfigFactory 加载的默认配置
     */
    @WithDefault("reference")
    ActorReferences reference();


    /**
     * 加载默认 Actor 系统名
     * 实际就是执行: ActorSystem.create(config.name(), ...)
     */
    String name();


    /**
     * 设置集合
     * 外部直接传入配置字符串不断加载到 pekko 配置项
     * 比如: fortress.actor.settings.pekko.actor.serializers.proto=org.apache.pekko.remote.serialization.ProtobufSerializer
     * 实际上就是提取 settings 之后部分做传递
     */
    Map<String, String> settings();


    /**
     * 默认创建配置的 Actor 系统
     */
    default ActorSystem createActorSystem() {
        // 构建配置
        Config originalConfig = ConfigFactory.load();

        // 选择加载配置
        originalConfig = switch (reference()) {
            case Application -> originalConfig.withFallback(ConfigFactory.defaultApplication());
            case Overrides -> originalConfig.withFallback(ConfigFactory.defaultOverrides());
            case ReferenceUnresolved -> originalConfig.withFallback(ConfigFactory.defaultReferenceUnresolved());
            default -> originalConfig.withFallback(ConfigFactory.defaultReference());
        };


        // 获取系统系统配置
        Map<String, String> settings = settings();

        // 需要检测是否有: fortress.actor.settings.pekko.logging-filter 配置项目
        // 没有的话直接切换: org.apache.pekko.event.slf4j.Slf4jLoggingFilter
        // 这样用于复用 Quarkus 的日志配置, 无需维护多套日志系统
        if (!settings.containsKey("pekko.logging-filter")) {
            settings.put("pekko.logging-filter", "org.apache.pekko.event.slf4j.Slf4jLoggingFilter");
            settings.put("pekko.loggers.0", "org.apache.pekko.event.slf4j.Slf4jLogger");
            settings.put("pekko.log-dead-letters", "off");
            settings.put("pekko.log-dead-letters-during-shutdown", "off");
        }

        // 重写配置
        Config withConfig = ConfigFactory.parseMap(settings);
        withConfig.withFallback(originalConfig);

        // 初始化 Actor 对象
        return ActorSystem.create(name(), withConfig);
    }


    /**
     * 创建配置的 Actor 系统
     *
     * @param closeable 退出时候的回调方法
     */
    default <U> ActorSystem createActorSystem(Function<Try<Terminated>, U> closeable) {
        ActorSystem system = createActorSystem();
        system.whenTerminated().onComplete(closeable::apply, system.dispatcher());
        return system;
    }
}

之后就是编写测试单元, 这里创建个测试单元专用配置文件 test/application.properties:

###############################################
# 测试单元用 application.properties
###############################################
# 测试 fortress.actor 配置
fortress.actor.name=fortress-test
fortress.actor.reference=reference
# 采用 protobuf 做序列化处理
fortress.actor.settings.pekko.actor.serializers.proto=org.apache.pekko.remote.serialization.ProtobufSerializer
# 默认序列化绑定的消息类, 采用Protobuf底层生成继承 com.google.protobuf.Message 的 Java 消息类
fortress.actor.settings.pekko.actor.serialization-bindings."com.google.protobuf.Message"=proto

之后就是编写测试单元加载这部分和生成对应的 Actor 系统:

/**
 * io.fortress.common.ActorSystemTests.java
 * <p>
 * ActorSystem测试单元
 */
@QuarkusTest
public class ActorSystemTests {
    /**
     * 扩展 Actor 系统配置接口, 读取 application.properties 的 fortress.actor 前缀配置
     */
    @ConfigMapping(prefix = "fortress.actor")
    interface ActorSystemConfigurationExt extends ActorSystemConfiguration {
        // 后续 Actor 参数需要扩展就在这里追加方法
    }

    /**
     * 加载测试单元的 application.properties 配置
     */
    @Inject
    ActorSystemConfigurationExt configuration;

    /**
     * 通用简易创建 ActorSystem
     */
    @Test
    public void easyActorSystem() {
        // 很简单的一个声明, 直接从配置项加载出一个 Actor System
        ActorSystem system = configuration.createActorSystem();
        Assert.assertNotNull(system);
    }
}

这就是将 配置即对象 的设计模式明显实现, 如果其他项目需要个 Actor 系统也是直接实现配置接口就可以简易生成.