【Java】初学Vert.x(2)

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 方法的执行内容,如下图:

扫描二维码关注公众号,回复: 15787873 查看本文章
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 实现类的自动部署。

猜你喜欢

转载自blog.csdn.net/kida_yuan/article/details/131720133