实例代码:https://github.com/wang007/vertx-start-example
<dependency>
<groupId>com.github.wang007</groupId>
<artifactId>vertx-start</artifactId>
<version>1.0.1</version>
<dependency>
- @Route
- @Deploy
- 免copy Json, JsonArray
- profiles.active
public class Main {
public static void main(String[] args) {
Vertx vertx = Vertx.vertx();
VertxBoot boot = VertxBoot.create(vertx);
boot.start();
}
}
就是这么简单把vertx-start启动起来。 这种启动方式确保你的配置文件中有base.paths属性。默认从启动类开始扫描。 base.paths属性用于指定vertx-start扫描component(组件)的路径。 可以指定多个,用逗号","间隔。 vertx-start是不管你的vertx怎么获取的,即是说,用Main方式启动也行,也用Launcher也可以。
当然你也可以在hook方法中把VertxBoot保存出来。
public class Main {
public static void main(String[] args) {
Vertx vertx = Vertx.vertx();
VertxBoot.create(vertx)
.afterDeployedHook(VertxBootHolder::setVertxBoot)
.start();
}
public static final class VertxBootHolder {
public volatile static VertxBoot vertxBoot;
public synchronized static void setVertxBoot(VertxBoot boot) {
vertxBoot = vertxBoot;
}
public static VertxBoot getVertxBoot() {
return vertxBoot;
}
}
}
大部分情况下,你的Router代码是在Verticle中组织的,当Route的数量少时还好,如果Route数量大的话,所有的Router代码组织到一个Verticle中,这是会让开发者很头痛的事情。
如果Router通过hack方式传到其他的Verticle中,其实这是没用的。Router最终运行所在的eventLoop是httpServer listen的那个Verticle。 这样会有很明显的“意识”问题,以为Router会在其他Verticle的上下文执行,其实不然。
把Router组织到LoadRouter上,按功能划分不同的功能的Router到LoadRouter中。
@Route( mountPath = "/demo")
public class DemoRouter implements LoadRouter {
@Override
public void start(Router router, Vertx vertx) {
router.route("/wang").handler(rc -> {
rc.response().end("hello world");
});
}
}
@Deploy(instances = 16)
public class HttpServer extends HttpServerVerticle {}
就通过这样简单的几行代码,就可以把httpServer启动起来。 而instances是Verticle的实例数,HttpServer对应的Verticle实例数 = eventLoop count,充分发挥Vert.x的性能。
public interface LoadRouter {
/**
* {@link LoadRouter}生命周期方法。
*
* 当且仅当{@link #start(Future)}成功完成, 调用入参中的{@link Future#complete()}
*
*/
void start(Future<Void> future);
/**
*
* @return 用于 {@link LoadRouter} 排序, 升序。 默认: 0.
*/
default int order() {
return 0 ;
}
/**
* {@link LoadRouter}创建好后调用。
*
* 例如:权限相关的route实现,可以放到该方法中。
*
* @param router 当使用{@link Route#mountPath()} 挂载路径, router为subRouter, (子路由)
* @param server {@link HttpServerVerticle}定义client组件,然后在这里获取,达到所有LoadRouter共享。
*/
default void init(Router router, Vertx vertx, HttpServerVerticle server) {}
}
- init方法在start方法之前执行。 例如一些前置Route(像权限校验的Route)可以在init方法创建。HttpServerVerticle子类中声明一些client,然后通过server.self()转成对应的子类,获取Client。
- order方法用于LoadRouter实现类排序,order越小,越前面。意味着越先把LoadRouter中调用Route加到MainRouter容器中。
- 绝大多数情况下,推荐使用AbstractLoadRouter,需要协程的kt请使用CoroutineRouter。
首先声明一点,@Route只能加到LoadRouter实现类上,否则报错。
- value -> 路径前缀。默认是"",即没有路径前缀。像上面的实例代码, 最终的访问路径是 /demo/wang。即会把value的值拼接到Router定义route的路径中。
- mountPath -> 挂载路径。默认是"",即不挂载subRouter,直接挂载到MainRouter上。 先声明一点, mountPath跟value不冲突,如果两者同时存在,最终的访问路径是 /mountPath/value/path。
- sharedMount -> 是否共享挂载subRouter,默认是true。即共享。 绝大多数情况下,都是true。
如果不熟悉vertx-web的话,会对这个挂载路径有疑问。 Router是Route的容器,里面有skipList保存了所有的Route。如果访问后面的Route的话,需要跟前面的Route逐一匹配。Route数量大的话,对性能有所损失。 而mountPath就是Route进行了分类。把一些route加到subRouter上,最后把subRouter加到MainRouter。 更详细的解读,请参考vertx-web官方文档关于mount的解释。 mountPath是强烈推荐使用一个属性。
当写完一个Verticle的时候,需要手动调用vertx#deployVerticle方法来部署Verticle。尽管你可能很不情愿,但是你必须这么做。
@Deploy
public class DemoVerticle extends AbstractVerticle {
@Override
public void start() throws Exception {
vertx.eventBus().<JsonSend>consumer("jsonSend", msg -> {
System.out.println(msg.body());
msg.reply(new JsonSend().put("name", "wang007"));
});
}
}
对,就是这么简单,通过@Deploy注解,就把Verticle部署了。
- instances -> 默认是1。Vertilce的实例数,这个没啥好说的了吧。
- worker -> 默认是false。是否为workVerticle
- order -> 默认是0。 部署Verticle时的顺序,值越小,越先部署。假如verticle之间有依赖的话,可以使用该属性。
也许你会说,这么属性还不够啊,vertx部署Verticle的时候,有很多属性可选呢, 甚至包括部署完成时的操作。 别急,都有, 听我娓娓道来。
public interface VerticleConfig {
/**
* 部署verticle的参数
* {@link Deploy}中值 != 默认值 将会设置到options中
* @return 部署参数
*/
default DeploymentOptions options() {
return new DeploymentOptions();
}
/**
* 确保该verticle是单实例的 即{@link DeploymentOptions#instances} = 1
* @return true: verticle必须单利,如果{@link Deploy#instances()} != 1 或 {@link #options()}中的instances != null 报错
* false: 允许多利的
* @throws IllegalStateException
*/
default boolean requireSingle() {
return false ;
}
/**
* 部署verticle完成之后的回调
* {@link io.vertx.core.Vertx#deployVerticle(String, Handler)} 中的Handler
* @return handler
*/
default Handler<AsyncResult<String>> deployedHandler() {
return null;
}
}
让verticle实现类实现VerticleConfig接口
- options方法,设置部署参数。如果@Deploy有设置数,且不等于默认参数。那么会设置到options中。
- requireSingle方法,确保Verticle单例。默认是false。如果设置返回true。且设置多实例数的话,报错。
- deployedHandler方法,设置部署完成后操作。默认是null。
是的,你没看错,要启动一个httpServer,必须继承HttpServerVerticle。并用@Deploy注解。骨灰级推荐httpServer对应Verticle的实例数等于eventLoop的实例数。才能充分发挥vert.x的性能。 HttpServerVerticle有多个拓展方法。
- addressAndPort方法。默认启动端口:8080,如果8080不合你的胃口。你只需要覆盖该方法,提供你的端口即可。
- **可以在init方法初始化一些client,并且initFuture#complete方法通知初始化完成,且public对应的client。然后可以LoadRouter#init方法中获取。
- before方法(敲黑板)。传入的参数是MainRouter。在执行所有的LoadRouter方法之前执行,可以覆盖该方法,做一些全局的Route操作。 例如BodyHandler等。
- doStop方法。传入的参数是httpServer(Vert.x中的)实例,做Verticle stop时的操作。
- beforeAccept方法。传入的参数是request。在请求来临时,进入MainRouter之前执行。这一步可以做请求之前拦截操作。
熟悉的vert.x的朋友,都知道。eventBus send json,jsonArray的时候,会发生一次copy操作。尽管你的代码中是能确保线程安全的。 实现JsonSend,JsonArraySend, 大费周折,最后发现还是不够理想。 这里的不够理想是指send的时候必须要设置codecName。因为我的实现中走不到最后defaultCodecMap中。这个在实现之前没发现。瞎眼程序员。
@Deploy
public class DemoVerticle extends AbstractVerticle {
@Override
public void start() throws Exception {
vertx.eventBus().<JsonSend>consumer("jsonSend", msg -> {
System.out.println(msg.body());
msg.reply(new JsonSend().put("name", "wang007"));
});
vertx.eventBus().<JsonArraySend>consumer("jsonArraySend", msg -> {
System.out.println(msg.body());
msg.reply(new JsonArraySend().add("wang007"));
});
JsonSend json = new JsonSend().put("name", "wang007").put("hobby", "girl");
vertx.eventBus().<JsonSend>send("jsonSend", json, JsonSend.options(), msg -> {
System.out.println(msg.result().body());
});
JsonArraySend array = new JsonArraySend().add("xiaowang");
vertx.eventBus().<JsonArraySend>send("jsonArraySend", array, JsonArraySend.options(), msg -> {
System.out.println(msg.result().body());
});
}
}
其中JsonSend.options(), JsonArraySend.options()是必须的。 否则还是会发生copy。 就是说,你可以把JsonSend当成JsonObject来用,JsonArraySend当成JsonArray来用是没问题的。
- 其实关于JsonSend,JsonArraySend是一种妥协。
- JsonSend,JsonArraySend维护着一个属性,会自动判断是否调用了eventBus send。如果调用了JsonSend,JsonArraySend就变成不可变。同时这个不可变的json也传到Consumer中, consumer使用这个json也不可变。只能从里面读取数据。
- 同时JsonSend,JsonArraySend的使用有一定的限制。例如不能存Map,List,Map可以用JsonObject代替,List可以用JsonArray。还有存进send中的JsonObject,JsonArray将的不可变。切记。尝试存的话,会报错。
- 即是说JsonSend,JsonArraySend免copy的实现方式是通过send之后不可变实现的。 jsonSend,JsonArraySend没有100%不可变。但是正常使用是没问题的。还是那句话:你要做傻逼,没人拦得住你。
- vertx-start默认加载classpath下的application.properties文件。
- 可以调用VertxBoot #setConfigFilePath方法设置classpath下的其他路径
- 如果以上都不满足或者想要添加一些额外的属性, 可以在vertxBoot #start方法之前调用vertxboot #getContainer方法,然后强制成InternalContainer。 再调用appendProperties方法,添加属性。同样,也可以调用appendComponent方法来添加Component到容器中。
- 可以添加多个path。
- base.paths就是组件的路径。确保注解的Component都包含在base.paths中
-
关于profiles.active,相信使用过spring的朋友都知道,用于指定不同环境的配置文件。
-
啰嗦一下,profiles.active文件的前缀、后缀必须跟主配置文件一样。 例如:主配置文件:application.properties, profiles.active文件:application-dev.properties
-
profiles.active加载方法且有先后顺序。先去System属性文件中找(通过启动jvm的时候添加-Dbase.paths参数添加)。找不到再去主属性文件中找。找不到就是没有。即不加载profiles.active文件。
-
调用vertxBoot.loadFor方法把属性加到到pojo中。同时pojo使用@Properties注解上。