4. プロジェクトの開始(垂直抽象デプロイ) + Yaml 設定の読み込み
現在のシステムは、次の図に示すように、App.java の main メソッドによって起動されます。
public class App {
private static final Logger LOGGER = LogManager.getLogger(App.class);
public static void main(String[] args) {
BootstrapConfig.setupAndDeploy(vtx -> {
LOGGER.info(" --------------- 定时器开始执行 --------------- ");
SysUserBuriedService userAccess = new SysUserBuriedService();
vtx.setPeriodic(CommonConstants.CRON, id -> userAccess.cron2SaveRedisAccess());
});
}
}
BootstrapConfig クラスの setupAndDeploy メソッドが main メソッドで呼び出されます。このメソッドはコールバック メソッドであり、「vtx」パラメータを通じて実行インスタンスを返すため、メイン メソッドの Vertx.vertx() を通じてオブジェクトを再度取得する必要がなくなりました。
コールバックメソッドでタイマーの開始操作を行いました。Vert.x のタイマーは、setPeriodic メソッドを使用して開始されます。この例の CommonConstants.CRON は、単位として「ミリ秒」を使用するタイマー間隔です。次の id はタイマー開始後にシステムによって与えられる識別子で、次の「userAccess.cron2SaveRedisAccess()」は特定のタイマーの実装です。
次のように、BootstrapConfig.setupAndDeploy メソッドを振り返ってみましょう。
public static void setupAndDeploy(BootstrapCallback callback) {
// 先部署yml配置,为了能够拿到配置文件
LOGGER.debug("In order to get the startup configuration first initialize the YamlConfig class...");
Vertx.vertx().deployVerticle(new YamlConfig(), yaml -> {
// 第一次配置获取成功
if (yaml.succeeded()) {
setupDeploy(Vertx.vertx(setupOptions()), callback);
} else {
LOGGER.error("func[BootstrapConfig.setupAndDeploy] Main method error Exception [{} - {}]",
new Object[] {
yaml.cause(), yaml.cause().fillInStackTrace()});
}
});
}
これは Vert.x によって実行される最初のメソッドであるため、ここでは Vertx.vertx() メソッドを使用して vertx のインスタンスを作成します。vertx インスタンスを作成した後、次の図に示すように、YamlConfig クラスをデプロイします (これは、YamlConfig クラスが AbstractVerticle クラスの実装を継承するためです)。
public class YamlConfig extends AbstractVerticle {
private static final Logger LOGGER = LogManager.getLogger(YamlConfig.class);
private static final String CONFIG_FOLDER = "configs";
private static final String SERVER_CONFIG = "server";
private static final String ACTIVE_CONFIG = "active";
private static final String BOOTSTRAP_PATH = CONFIG_FOLDER + CommonConstants.HTTP_SLASH + "bootstrap.yml";
/**
*
* @MethodName: start
* @Description: 将配置信息加载到内存
*
* ...
* | | `-- resources
* | | |-- configs
* | | | |-- bootstrap.yml
* | | | `-- master
* | | | |-- application-datasource.yml
* | | | `-- application-redis.yml
* ...
*
* @author yuanzhenhui
* @see io.vertx.core.AbstractVerticle#start()
* @date 2023-04-13 04:29:42
*/
@Override
public void start() {
// ---------------------------------------------------------------------------------
// 通过 vertx.fileSystem() 方法获取 bootstrap 文件的内容缓冲(可以简单理解成获取了文件内容) -
// ---------------------------------------------------------------------------------
Buffer bootBfr = vertx.fileSystem().readFileBlocking(BOOTSTRAP_PATH);
try {
if (null != bootBfr) {
// ---------------------------------------------
// 通过 Yaml 类的 load 方法将内容缓冲转换成 Map 集合 -
// ---------------------------------------------
Map<?, ?> baseMap = (Map<?, ?>)new Yaml().load(bootBfr.toString());
// -------------------------------------------------------------------------------------
// 由于 Map<?,?> 会默认以 Map<Object,Object> 进行处理,因此需要转换成 Map<String,Object> 格式 -
// -------------------------------------------------------------------------------------
baseMap.entrySet().stream()
.forEach(entry -> YamlConstants.CONF_JSON.put(String.valueOf(entry.getKey()), entry.getValue()));
// ---------------------------------------------------------------------------------------------
// 由于已经习惯了 springboot 的配置方式,这里将读取 bootstrap 文件中 server.active 配置来获取环境配置信息 -
// ---------------------------------------------------------------------------------------------
String envStr = YamlConstants.CONF_JSON.getJsonObject(SERVER_CONFIG).getString(ACTIVE_CONFIG);
// -----------------------------------
// 根据配置环境名字找到对应目录下的所有文件 -
// -----------------------------------
vertx.fileSystem().readDir(CONFIG_FOLDER + CommonConstants.HTTP_SLASH + envStr, dirHeader -> {
if (dirHeader.succeeded()) {
// ---------------------------
// 获取到目录下的所有文件路径集合 -
// ---------------------------
List<String> fileList = dirHeader.result();
if (null != fileList && !fileList.isEmpty()) {
fileList.stream().forEach(pathName -> {
// ------------------------------------------------------------------------------------
// 采用 vertx.fileSystem() 遍历这个路径集合并获取文件中的配置信息,获取方式跟 bootstrap 获取一致 -
// ------------------------------------------------------------------------------------
Buffer pluginBfr = vertx.fileSystem().readFileBlocking(pathName);
if (null != pluginBfr) {
Map<?, ?> pluginMap = (Map<?, ?>)new Yaml().load(pluginBfr.toString());
pluginMap.entrySet().stream().forEach(entry -> YamlConstants.CONF_JSON
.put(String.valueOf(entry.getKey()), entry.getValue()));
}
});
}
}
});
}
} catch (Exception e) {
LOGGER.error("func[YamlConfig.start] Exception [{} - {}]", new Object[] {
e.getCause(), e.getMessage()});
}
}
}
上記のコードに示されているように、すべての構成情報は最終的に YamlConstants.CONF_JSON 定数に追加されます。なんと呼びますか?
以下に示すように、コード ディレクトリで「io.kida.components.utils.yaml」パスを見つけることができます。
...
|-- src
| |-- main
| | |-- java
| | | `-- io
| | | `-- kida
| | | |-- components
| | | | `-- utils
| | | | `-- yaml
| | | | |-- YamlUtil.java # yaml工具类
| | | | |-- annotations
| | | | | `-- PropLoader.java # yaml配置注解
| | | | `-- constants
| | | | `-- YamlConstants.java # yaml常量类
...
このうち、PropLoader.java は以下に示すカスタム アノテーションです。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PropLoader {
String key();
}
このうち key は設定情報を取得するために変数名を設定するために使用されます。また、YamlConstants.java には CONF_JSON 定数が格納されます。以下に示すように:
public class YamlConstants {
public static final JsonObject CONF_JSON = new JsonObject();
}
また、YamlUtil.java は yaml ツール クラスであり、以下の図に示すように、そのメソッドはすべて直接呼び出し用の静的メソッドです。
public class YamlUtil {
private static final Logger LOGGER = LogManager.getLogger(YamlUtil.class);
/**
* 返回Integer类型配置
*
* @param key
* @return
*/
public static Integer getIntegerValue(String key) {
return Integer.valueOf(getStringValue(key));
}
/**
* 返回Boolean类型配置
*
* @param key
* @return
*/
public static Boolean getBooleanValue(String key) {
return Boolean.valueOf(getStringValue(key));
}
/**
* 返回String类型配置
*
* @param key
* @return
*/
public static String getStringValue(String key) {
return String.valueOf(getValue(key));
}
/**
* 返回List类型配置
*
* @param key
* @return
*/
public static List<?> getListValue(String key) {
return new JsonArray(getStringValue(key)).getList();
}
/**
* 返回Map类型配置
*
* @param key
* @return
*/
public static Map<?, ?> getMapValue(String key) {
return new JsonObject(getStringValue(key)).getMap();
}
/**
*
* @MethodName: getValue
* @Description: 获取Object值
* @author yuanzhenhui
* @param value
* @return Object
* @date 2022-08-11 01:38:35
*/
public static Object getValue(String value) {
String[] values = value.split("\\.");
int len = values.length - 1;
JsonObject json = null;
// ----------------------------------------------------------
// 这里用了一个本方法通过 while 循环等待CONF_JSON静态变量的加载完成 -
// ----------------------------------------------------------
while (true) {
if (null != YamlConstants.CONF_JSON && !YamlConstants.CONF_JSON.isEmpty()) {
json = YamlConstants.CONF_JSON;
break;
}
}
// --------------------
// 使用遍历获取到配置信息 -
// --------------------
for (int i = 0; i < len; i++) {
if (json.containsKey(values[i])) {
json = json.getJsonObject(values[i]);
} else {
return null;
}
}
return json.getValue(values[len]);
}
/**
*
* @MethodName: propLoadSetter
* @Description: 使用自定义注解获取到变量内容
* @author yuanzhenhui
* @param <T>
* @param t
* void
* @date 2022-08-11 01:38:17
*/
public static <T> void propLoadSetter(T t) {
Arrays.asList(t.getClass().getDeclaredFields()).stream().filter(f -> f.isAnnotationPresent(PropLoader.class))
.forEach(f -> {
f.setAccessible(true);
PropLoader pl = f.getDeclaredAnnotation(PropLoader.class);
try {
Object obj = getValue(pl.key());
if (obj instanceof JsonArray) {
obj = ((JsonArray)obj).getList();
} else if (obj instanceof JsonObject) {
obj = ((JsonObject)obj).getMap();
}
f.set(t, obj);
} catch (IllegalArgumentException | IllegalAccessException e) {
LOGGER.error("func[YamlUtil.propLoadSetter] Exception [{} - {}]",
new Object[] {
e.getCause(), e.getStackTrace()});
}
});
}
ここの実装方法にはまだ最適化の余地がたくさんありますが(後で最適化しました)、データがメモリに保存されることと呼び出しがそれほど頻繁ではないことを考慮すると、ここではこれ以上の最適化はありません。
上記のコードから、このプロジェクトでは構成コンテンツを取得する 2 つの方法が提供されていることがわかります。1 つは、YamlUtil.getBooleanValue(“xxx.yy”) を介して呼び出すなど、直接呼び出す方法です。もう 1 つは、以下に示すように、カスタム アノテーションを通じて取得されます。
public class RouterConfig extends AbstractVerticle {
...
@PropLoader(key = "server.port")
private int port;
@PropLoader(key = "server.http.header")
private List<String> httpHeader;
@Override
public void start() {
YamlUtil.propLoadSetter(this);
...
}
...
}
アノテーションを通じて構成情報を取得する場合、まずこのクラスが AbstractVerticle クラスを継承する必要があり、次に YamlUtil.propLoadSetter(this) を start メソッド内に追加する必要がありますが、これは上記のコードからもわかるため、ここではその理由は説明しません。
BootstrapConfig の setupAndDeploy メソッドに戻ると、deployVerticle を通じて YamlConfig クラスをデプロイした後、歩数ステータスの yaml 変数を取得します。以下に示すように:
...
if (yaml.succeeded()) {
setupDeploy(Vertx.vertx(setupOptions()), callback);
} else {
LOGGER.error("func[BootstrapConfig.setupAndDeploy] Main method error Exception [{} - {}]",
new Object[] {
yaml.cause(), yaml.cause().fillInStackTrace()});
}
...
デプロイメントが成功した場合、yaml.succeeded は真の判定を返します。その後、デプロイメントの次のステップに進み、setupDeploy メソッドを呼び出すことができます。setupDeploy 呼び出しでは 2 つのパラメーターを渡す必要があることがわかります。前のパラメーターは vertx インスタンスですが、このインスタンスは特別です。次の図に示すように、これは「調整 (setupOptions メソッドの呼び出し)」されたインスタンスです。
private static VertxOptions setupOptions() {
VertxOptions options = new VertxOptions();
options.setWorkerPoolSize(YamlUtil.getIntegerValue(THREAD_WORKER));
options.setInternalBlockingPoolSize(YamlUtil.getIntegerValue(THREAD_INIT_POOL_SIZE));
options.setEventLoopPoolSize(YamlUtil.getIntegerValue(THREAD_EVENTLOOP_POOL_SIZE));
return options;
}
setupOptions では実行環境の構成情報を指定できますが、ここではワーカー プール サイズ、内部ブロッキング プール サイズ、イベント ループ プール サイズのみを構成します。これは実際のプロジェクトの状況に応じて決定でき、その他の構成情報も含まれます。
後者のコールバックは、コールバックの実行を提供するもので、最初に次の図に示すように、setupDeploy メソッドの実行内容を見てみましょう。
private static void setupDeploy(Vertx vtx, BootstrapCallback callback) {
DeploymentOptions deploymentOptions = new DeploymentOptions();
deploymentOptions.setWorker(true);
deploymentOptions.setInstances(YamlUtil.getIntegerValue(THREAD_DEPLOY_INIT));
deploymentOptions.setWorkerPoolSize(YamlUtil.getIntegerValue(THREAD_DEPLOY_MAX_SIZE));
deploymentOptions.setWorkerPoolName(YamlUtil.getStringValue(THREAD_DEPLOY_POOL_NAME));
// ----------------------------------------------------------------------
// 在重新生成Vertx实例之后需要重新部署所有Verticle类,于是第二次获取YamlConfig类 -
// ----------------------------------------------------------------------
if (null != vtx) {
try {
// -----------------------------------------------------------------
// 提供一个路径并递归扫描下面所有继承了 AbstractVerticle 的类并进行并行部署 -
// -----------------------------------------------------------------
List<Class<Verticle>> clazzSet = ReflectUtil.getVerticleClasses(CommonConstants.ROOT_LEVEL, true);
List<CompletableFuture<Void>> futures =
clazzSet.stream().filter(clazz -> !clazz.getSimpleName().contains(VTX_VERTICLE))
.map(clazz -> CompletableFuture.runAsync(() -> vtx.deployVerticle(clazz, deploymentOptions)))
.collect(Collectors.toList());
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
// -----------------------------------------------------------------------------------------------------------------------
// 由于部署是采用多线程的方式进行处理,因此这里采用了 CompletableFuture 进行处理,最终通过 allOf 方法确保vertx实例在多线程全部执行后再执行 -
// -----------------------------------------------------------------------------------------------------------------------
callback.afterBootup(vtx);
} catch (ClassNotFoundException | IOException e) {
LOGGER.error("func[BootstrapConfig.setupDeploy] Exception [{} - {}] stackTrace[{}] ",
new Object[] {
e.getCause(), e.getMessage(), Arrays.deepToString(e.getStackTrace())});
}
}
}
これまでに、すべての AbstractVerticle 実装クラスの自動デプロイメントが完了しました。