网站首页 > java教程 正文
书接上回...
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());
});
}
}
在main方法中将调用 BootstrapConfig 类的 setupAndDeploy 方法。这个方法是一个回调方法,会将执行实例通过“vtx”参数回传,这样的话我们就不再需要在 main 方法中重新通过 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 工具类,里面的方法都是 static 方法方便直接调用,如下图:
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()});
}
});
}
}
这里的实现方法还有很大的优化空间(后面我的确是做了优化),但考虑到数据存放内存中且调用也不算频繁,因此这里就没有再做优化,权当凑合着用吧。
有上面代码可以得知本项目提供了两种获取配置内容的方式,一种是直接调用,譬如通过YamlUtil.getBooleanValue("xxx.yy") 调用。而另一种则是通过自定义注解获取,如下图:
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 类,其次必须在 start 方法内部加上YamlUtil.propLoadSetter(this),这个看上面代码就知道,就不再叙述为什么了。
回归到 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 会返回一个 true 判定。之后就可以进入下一步的部署调用 setupDeploy 方法。我们可以看到 setupDeploy 调用需要传入两个参数,前面的参数是 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 我们可以指定运行环境的配置信息,其中这里我只配置了 worker pool size、internal blocking pool size 和 event loop pool size。这个可以根据实际项目情况而定,里面还有其他的配置信息的。而后面的 callback 则是提供回调执行这里先不管我们先看看这个 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 实现类的自动部署。
猜你喜欢
- 2024-10-13 深度分析ClassLoader机制,不可错过这一篇
- 2024-10-13 RxJava2.X 源码解析(一):探索RxJava2分发订阅流程
- 2024-10-13 与其他语言相比,Java有多安全?(java语言有哪些缺点)
- 2024-10-13 JVM 配置参数 -D,-X,-XX 的区别
- 2024-10-13 mysql-connector-java与MySQL 8.X版本建立连接
- 2024-10-13 浙江大学终于把java整理成漫画书了,动画教学更生动,允许白嫖
- 2024-10-13 java 核心技术-12版 卷Ⅰ- 4.1 面向对象程序设计概述
- 2024-10-13 SpringBoot2.x配置多数据源(springboot如何配置多数据源)
- 2024-10-13 JAVA 中获取比X大1位数中最小的数
- 2024-10-13 玩大了!Log4j 2.x 再爆雷(log4j最新版本)
你 发表评论:
欢迎- 最近发表
-
- Java常量定义防暴指南:从"杀马特"到"高富帅"的华丽转身
- Java接口设计原则与实践:优雅编程的艺术
- java 包管理、访问修饰符、static/final关键字
- Java工程师的代码规范与最佳实践:优雅代码的艺术
- 编写一个java程序(编写一个Java程序计算并输出1到n的阶乘)
- Mycat的搭建以及配置与启动(mycat部署)
- Weblogic 安装 -“不是有效的 JDK Java 主目录”解决办法
- SpringBoot打包部署解析:jar包的生成和结构
- 《Servlet》第05节:创建第一个Servlet程序(HelloSevlet)
- 你认为最简单的单例模式,东西还挺多
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)