功能初始化
这里首先需要配置 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 系统也是直接实现配置接口就可以简易生成.